Is there a way to make the stop button more responsive?

i have a loop that shows a progress bar. That loop updates the progress bar, then does a shell command that takes about 4 seconds to complete. Then it repeats. It is really hard to click that stop button, the applescript’s GUI is mostly locked up most of this time.

Is there a way to make it more clickable? One option might be a delay, but this loop is running thousands of times so I really don’t want to slow it down. Even a 1 second delay is going to reduce the amount of work this script does in an hour by 25%. Is there any other technique?

Apples implementation of this progress bar has been so disappointing. Nothing like Mike’s Scriptable Progress Bar!!!

If you are using repeat for the loop, that is preventing events from being processed, so the UI is getting blocked. Using a separate application avoids the issue because it has its own run loop that you aren’t blocking.

1 Like

What do you mean by another application? Have script A do the work, but open script B and send messages to IT so B displays the progress bar etc?

So if I broke apart my repeat loop and put it’s contents in an idle handler that returned 0, would that make it a continuously running loop but with a more responsive ‘stop’ button on the progress bar?

Is an idle 0 loop even possible? And I would have to control it so it wouldn’t just run randomly, only when called.

Post some code please

1 Like

I don’t have it handy but its pretty simple. Its currently a repeat loop that updates the progress bar, then runs a shell script that takes about 4 seconds to complete, then repeats to infinity.

It looks like the built-in progress isn’t going to work with idle, timer, or notification - it dismisses when the run handler completes, even in a stay-open app. Progress indicators, for whatever reason, are a royal pain to implement unless your application is built for them. It looks like you might have to go with processing the events yourself, but seeing a basic code layout or minimal example would go a long way in figuring out the best place to squeeze in a progress indicator that works the way you want.

ok here we go

on run
	set progress total steps to 100
	set progress description to "Doing the Thing!..."
	set i to 1

	repeat
		try
			set progress completed steps to i
			set progress additional description to "Specific sub-task in process"
			do shell script "rm 'path/to/file'; cp 'file1' 'file2';"
		on error
			if err_num = -128 then
				display dialog "You have quit the script, have a good day" buttons "Quit" default button 1 with icon stop with title MyName
				quit me
			end if
		end try
	end repeat

end run

This is basically the setup. The shell command takes about 4 seconds. And with this setup, its hard to click on the stop button in the progress bar. Its very laggy.

I agree that apples implementation of the progress bar is terrible. And strangely very different from all of the other interact-able windows AppleScript can create.

Its not a deal breaker, but it would be nice if there were a way to make it easier to stop this script - nicely - without slowing it down.

You have an infinite repeat loop

Yes, I have an infinite repeat loop. That runs until you press the stop button in the progress window.

Your example showed files. Are there files or different shell scripts? If so, how are you handling those? I have a sample that doesn’t use repeat statements, idle handlers, or timers, but I still don’t know what you are doing.

My sample code above is the whole thing. I’m deleting and duplicating files, lots of files. Hundreds of GB worth. If the files are small, the stop button becomes very responsive. If the files are large like 1 GB each, the stop button is very hard to press.

OK, now we are getting somewhere. My current solution uses NSTask to launch a task in the background and a notification when it completes, so the UI stays responsive. NSTask is not quite the same set up as do shell script but the commands would be the same. How are you dealing with the file paths to put in the shell script? Reading from a file or something?

try my example…

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

property myFiles : {}
property fileNames : {}

on run
	local pid, errMsg, errNum
	tell application "System Events"
		set {myFiles, fileNames} to {POSIX path, name} of (files of folder "Crucial SSD:" whose name extension is "mp4")
	end tell
	set progress total steps to count myFiles
	set progress description to "Doing the Thing!..."
	
	try
		repeat with i from 1 to count myFiles
			set progress completed steps to i
			set progress additional description to item i of fileNames
			set pid to do shell script "cp '" & (item i of myFiles) & "' '/Volumes/Test SSD/" & (item i of fileNames) & "' &>/dev/null & echo $!"
			repeat until pid is not in (words of (do shell script "ps -x -o pid= "))
				delay 0.5
			end repeat
		end repeat
	on error errMsg number errNum
		if errNum = -128 then
			display dialog "You have quit the script, have a good day" buttons "Quit" default button 1 with icon stop with title MyName
			return
		end if
	end try
end run

This will copy 1 file at a time, and has a timer loop to check if copy is done by looking for the copy processes Process-ID.

The other valid suggestions notwithstanding, the easiest way to ‘fix’ your script is to change the one line:

			do shell script "rm 'path/to/file'; cp 'file1' 'file2';"

to:

			do shell script "rm 'path/to/file'; cp 'file1' 'file2' > /dev/null 2>&1"

done.

The issue stems from the fact that when you invoke the shell script in your original script, AppleScript waits for that to complete before proceeding. It has to do this in case you want to wait for the result of the shell command (you might want to check for an error code, wait for completion status, etc.)

By appending " >/dev/null 2>&1" to the end of the shell script, this suppresses the output of the command and returns control to the script immediately. This means that you don’t have to wait for the shell command to finish before displaying the next dialog (and thus opportunity for the user to click Stop).

Now, this might be a problem if iteration 2 requires that iteration 1 is complete. If that’s the case there are also options such as having the shell script return the PID of the process it launched, then you can amend the second iteration to check if the previous PID is done before it kicks off the second copy. It all depends on the workflow and your intent/needs.

1 Like

Now, this might be a problem if iteration 2 requires that iteration 1 is complete. If that’s the case there are also options such as having the shell script return the PID of the process it launched, then you can amend the second iteration to check if the previous PID is done before it kicks off the second copy.

That’s what my script above does.

That’s what my script above does.

Right… I was just pointing out that there are multiple levels of ‘solution’ here, from the quick-and-dirty, through the more robust/flexible, through a complete rewrite. :slight_smile:

I err on the quick and dirty side :slight_smile: