Determining if a File is Busy

Hi All,
I’m trying to watch a folder for files being added to it. The difficulty that I’m running into is that the files can be very large, and so can take a while to either be copied to the folder, or they could be downloaded from the internet. I need to make sure that the transferring process is complete before I do anything with the file. I thought I’d found a solution with NSFileBusy from the dictionary generated by NSFileManager’s attributesOfItemAtPath_error_ but I don’t see it in the resulting dictionary (deprecated??). Is there some other solution for this available within obj-c or do I have to grind it out with something like determining when the file size stops changing? Thanks.

There’s no simple way. Checking size is the most common way, and trying to open with write access is another.

I was afraid that’s what the answer was going to be. The open for access method seemed a little more straight forward, but when I tried this:

	on theFileIsBusy_(theFile)
		try
			open for access file (theFile) with write permission
			close access result
			return false
		on error errMsg
			log errMsg
			return true
		end try
	end theFileIsBusy_

I get the error: Can’t make current application into type «class fsrf», and so true is always returned.

Any ideas?

Go here: www.macosxautomation.com/applescript/apps/gotchas.html

Here’s something I have been using with pretty good success. It’s a size getter function and uses shell, since that (for me, using network volumes) is way more reliable. I had tried to use Finder’s info for files before, but it had problems.

All you need to do is something like:

set fileStabilized to my delayUntilFileSizeStopsChanging(myWatchedFolder & myIDFileName)

then it calls this handler:

on delayUntilFileSizeStopsChanging(theFile)
		logStatement_("checking size fn")
		logStatement_("theFile= " & myWatchedFolder & myIDFileName)
		
		try
			--get initial size	
			set sizeThen to (do shell script "stat " & quoted form of (POSIX path of theFile) & " | awk '{print $9}'")
			logStatement_("sizeThen= " & sizeThen)
			
			repeat
				do shell script "sleep 10" --wait between runs
				
				--get new size
				set sizeNow to (do shell script "stat " & quoted form of (POSIX path of theFile) & " | awk '{print $9}'")
				logStatement_("sizeNow=  " & sizeNow)
				
				if sizeNow = "" then error --file is not there any more
				logStatement_("subtract= " & (sizeNow - sizeThen))
				
				if sizeNow - sizeThen is less than or equal to 0 then exit repeat
				--set old size to compare on another loop through
				set sizeThen to sizeNow
			end repeat
		on error
			error "file size check failed" number 4004
		end try
		
		return 0
	end delayUntilFileSizeStopsChanging

if it works OK then you just have a variable of 0, if the handler doesn’t work then it returns an error, so you might have to edit to work how you like, it could return some other number. But internally the handler repeats a number of times, so it pretty much makes the whole script wait till either it succeeds, or fails on the file or the file is then gone/unavailable etc.

Thanks Shane and SuperMacGuy,
I did finally get the open access to run without error, but unfortunately, it seems that the no error was generated even when trying to open the file in the middle of a copy. The files are typically videos, so I’m guessing there is some streaming aspect to the files that allows them to be opened.

So I’ll be heading in the check file size route as shown in SuperMacGuy’s template, though I’ll have to adjust as I don’t want the processing to stop between file size checks.

This is probably a good case for a timer. This is untested,but something like it should work:

set theTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(5, me, "timerFired:", {theURL, 0}, false)

on timerFired_(timer)
	set {theURL, lastSize} to timer's userInfo()
	set theSizeRec to theURL's resourceValuesForKeys_error_({"NSURLFileSizeKey"}, missing value)
	if theSizeRec = missing value then
		-- file has gone
	end if
	set theSize to theSizeRec's valueForKey_("NSURLFileSizeKey")
	if theSize is not lastSize then
		set theTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(5, me, "timerFired:", {theURL, theSize, timerCount + 1}, false)
	else
		-- do stuff to theURL
	end if
end timerFired_

The examples above (with info for, open with write permission) doesn’t work.

The example with file size checking is wrong solution because the file may be busy when some application reads the data from it (and not only when some application writes the data to it).

I found the next solution (on the internet) as the best:


set theAlias to choose file
set thePath to quoted form of (POSIX path of theAlias)
isFileBusy(thePath) of me

on isFileBusy(thePath)
	--Tests to see if a file is in use
	try
		set myscript to "if ( lsof -Fp " & thePath & " | grep -q p[0-9]* ) then echo 'file is busy'; else echo 'not busy';fi"
		set myResult to do shell script myscript
		if myResult is "file is busy" then return true
		return false
	on error err
		display dialog ("Error: isFileBusy " & err) giving up after 5
	end try
end isFileBusy

It depends what you mean by busy. In the context of the original poster, it effectively means no longer being written to. This was also what info for used to return in the pre-OS X days.

All approaches have their pitfalls. For example, lsof doesn’t cope with the situation where a writing process opens and closes repeatedly.

FWIW, Apple’s Folder Actions use a size-checking algorithm.

Other approach do what you already know and make 2 subfolder… complete and uncompleted.
When files have been downloaded to uncompleted folder you move them or copy to complete.

Make a folder action on the complete folder and run your ASOC Script.

Your message didn’t say how you download you files but instead of subfolders you could maybe
do log files. If the log file is empty its means no files to process…
AppleScript could check the log file before it process any files…

My thought.

Example.

If a dummy file with the name ‘complete’ is find in the folder display dialog will be true.
When a file have been download to the watch folder it will create a dummy file.
Process the file and after delete the dummy file.

on adding folder items to this_folder after receiving these_items
	set listFolder to list folder this_folder
	set countItems to count listFolder
	repeat with i from 1 to (countItems)
		if (item i of listFolder = "complete") then
			display dialog "Start doing something"
		else
			log "waiting"
		end if
	end repeat
end adding folder items to

Shane and Fredrik71.

Thank you for your attention to my article. I agree with your reasoning - all approaches have their drawbacks.

However, I tested them and found the lsof approach not as ideal of course but as the most optimal and most stable. I would use the approach with checking the file size as more optimal in some special cases. For example, when downloading a file and providing a progress bar. These are my recommendations for users.

I did some research how Safari does it when download files…

Safari add ‘.download’ to the end of the file.

When I run the command ‘file my_down_load_filename.ext.download’
it say its directory… but I guess its a bundle… when the file is complete it remove .download
So it come to my mind… if Apple’s Folder Action check for specific format it will not
find any file before its complete.

I believe that should work.

Different apps have different approaches. And the reason they do that is because Unix simply doesn’t have a universal concept equivalent to the old busy flag.

Use the hide flag

If Apple’s Folder Action can’t see a file in directory it couldn’t process it.
The dummy file (empty) only tell the user its a file with the name in that directory.
When download is complete the hide flag is removed.

Apple’s Folder Action see the file and process it.
Delete the dummy file
Make a log file with status.

Would that work do you think?..