I had a task that recursively walks folders and processes the files in finds in those folders.
If one of the files is a zip or a rar file I decompress it into a temporary folder and then process it.
Unfortunately when I shell out to unzip or unrar in shell mode 0 the application blocks.
This is expected behavior. However if I simply change to mode 1 then the blocking should go away.
However my code was expecting to have all its data available when I return from unzip or unrar.
In async mode this isn’t true… So… How do I restructure this recursive method to handle the async event ‘completed’ now?
Sub ParseDirectory(f as FolderItem) // This is called from Thread.Run
dim i as integer
dim z as zip
dim r as rar
for i=1 to f.Count
if f.item(i).Directory then
rel = f.item(i).ShellPath
if right(rel, 4) = ".zip" or right(rel, 4) = ".ZIP" then
z = new zip
if z.unzip(f.item(i)) then
RaiseEvent NewRow(rel, "Unable to decompress zip file.", "", "")
elseif Right(rel, 4) = ".rar" or right(rel, 4) = ".RAR" then
r = new rar
if r.unrar(f.item(i)) then
RaiseEvent NewRow(rel, "Unable to decompress rar file.", "", "")
You don’t. You’ll need to restructure your code so that the files that have been unzipped are processed in response to the Shell.Completed event. Your main code will proceed while the unzipping is done, and you will have to watch for the Shell.Completed events.
This is a fairly complex issue that is bug-prone. Test your code carefully.
as ParseDirectory is essentially the run method of the thread, can I put a loop after the unzip?
zip1 is now on the window as an object and it’s mode is 1
in zip1 completed event a flag is set to done.
while not zip1.done
While that may work, it is inefficient. The Shell emits a Completed event when the zip command ends. Whatever you need to do after that command ends, you should to do it from the Completed command. This is how you keep the user interface responsive in Xojo, and incidentally, it’s also how you utilize multiple processor cores using Xojo. It’s a very useful skill to have.
Check out the asynchronous shell example in the Advanced section of Examples. It’s worth understanding.
Yep I know that example well. Its very simple.
I think this could be made more robust if there was a thread that parsed folders and populated a queue of file-names to be processed.
When it finds a file it adds the file to a queue. (If its not a zip or rar file.)
If it’s a zip or rar file it creates an async shell that decompresses the files into a unique folder.
In the shell complete event it goes through that folder and processes all the files adding them to the queue or spawning off another de-compressor if needed. (de-compressor is a subclass of an async shell)
The application has a timer on the window that services the file-name queue.
The file-name in the queue needs to be added to the application’s listbox in the timer event.
does this sound like a sequence of events that might not block up the user interface?
Good job! Yes, that’s the idea. The trick is getting it right, especially if you have to do something once all the folders and zip files are processed.
I had a similar situation with a query that generated sub queries… all done with shells…
So now I’m wondering if I should be making a design pattern, because all that is really different between these two classes is what shell commands get executed and what happens in the shells data available and complete events.
Does that make sence?
There is probably some potential for generalization here. I’d get one of these processes tested and working before trying to generalize.
I’ve gotten hung up in a Run method that seems to block the application…
I don’t know why.
This works, but the run method may take hours to complete and then the application at then end responds just as I expected…
[quote=46082:@Brian O’Brien]I’ve gotten hung up in a Run method that seems to block the application…
I don’t know why.[/quote]
I glanced at your project and I think I see the problem. You have overridden the Thread.Run method in your FileIOCntrl thread subclass but you aren’t running the thread. In order for code to be run on a thread, it must be called from the
Move your code from your
FileIOCntrl.Run method into the
FileIOCntrl.Run event, and delete the Run method. Note that the
FileIOCntrl.Item event will be raised on the same thread, so you can’t update the GUI from it.
If you noticed there was a model / view / controller paradigm being used in that example.
So basically I’m able to update the model but not the view. OK that makes sense.
My question now is how to properly update the view. I have a timer that takes the model and passes it to the view.
But It should only add to the view what is new since the last update…
Would you queue the ‘new item events’ in the model or in a none UI Element of the view?