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?
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.
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.
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.