Search for file in folders and subfolders

For those, who like plain AppleScript solution:


-- file to search
property fileName : "Tless0.tiff"
-- file to copy
set fileToCopy to "HARD_DISK:Users:123:Downloads:TEST files:Abstract Background.ai"
-- were to search
set chosenFolder to (path to downloads folder) as text

-- coerce HFS path to System Events folder reference
tell application "System Events" to set chosenFolder to folder chosenFolder

-- maybe, the file doesn't exist at all. So, we use here TRY block
try
	set parentFolder to my recurse(chosenFolder)
	tell application "Finder" to duplicate file fileToCopy to folder parentFolder
end try

-- recursive handler
on recurse(chosenFolder)
	tell application "System Events"
		if (fileName is in (name of files of chosenFolder)) then
			return path of chosenFolder -- local exit from handler
		else
			repeat with subFolder in (get folders of chosenFolder)
				my recurse(subFolder)
				try
					return result -- global exit from handler
				end try
			end repeat
		end if
	end tell
end recurse

Very interesting, thank you for the help.
I made some progress by studying the Predicate Programming Guide by Apple.

But in the samples there the ‘predicateWithFormat’ expression is always followed by ‘:@’, like ‘predicateWithFormat:@’

Whereas in the given line…

set thePred to current application’s NSPredicate’s predicateWithFormat_(“(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)”, theName, (“.?/" & theCompanyPrefix & ".”))

the expression ist follwoed by ‘(’ like 'predicateWithFormat(’

Can you help me once more?

Cheers
Carl

«underscore-parentheses» and «colon» are synonyms, the former is legacy syntax, the latter is more convenient (and ObjC-compatible), however sometimes with complex expressions the former is still useful.

Thank you Stefan.

I experimented a little bit.
I tried to use the techniques to firstly search identify a folder with the syntax ‘companyPrefix-anything’. The folder is located directly inside another given folder.

Therefore I wanted to make sure, that the search is not extended to the subdirectories of the given folder. I included the term

  • (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants)

in the following line.

set folderContents to (fileManager’s enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:((current application’s NSDirectoryEnumerationSkipsPackageDescendants) + (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (current application’s NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()

But I get an error message:

error “NSDirectoryEnumerationSkipsSubdirectoryDescendants kann nicht in Typ number umgewandelt werden.” number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number

English translation of the error message:

error “NSDirectoryEnumerationSkipsSubdirectoryDescendants can not be transformed to the type of a number.” number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number

Can you find my mistake?

Thanks for any help
Carl

NSDirectoryEnumerationOptions is an enum, actually an option set. In ObjC the options are compounded with the Bitwise OR Operator (|).

This particular operator is not available in AppleScriptObjC. The workaround is to add the integer values.

Either coerce each option to integer to be able to add them or simply specify literal 7 (4 + 2 + 1) in the options parameter.

Übrigens verstehe ich die deutschen Fehlermeldungen (by the way I understand the German error messages) :wink:

Great - danke sehr - it works - just added ‘as integer’ to the term:

  • (current application’s NSDirectoryEnumerationSkipsSubdirectoryDescendants as integer)

Dachte mir schon, dass ich es für Dich nicht übersetzen muss, aber vielleicht will ja jemand mitlesen.

FWIW, what you posted should work; the fact it doesn’t is a bug.

This is not a bug, but how AppleScript guesses the desired type in the result (what to get). With arithmetic operations, it considers an integer to be expected when the expression contains only an explicit integer (or explicit integers). If there is at least one real operand, the result is also real.


use scripting additions
use framework "Foundation"

property NSDirectoryEnumerationSkipsPackageDescendants : a reference to NSDirectoryEnumerationSkipsPackageDescendants of current application
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to NSDirectoryEnumerationSkipsSubdirectoryDescendants of current application
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to NSDirectoryEnumerationSkipsHiddenFiles of current application

(get NSDirectoryEnumerationSkipsPackageDescendants + 0) + (get NSDirectoryEnumerationSkipsSubdirectoryDescendants + 0) + (get NSDirectoryEnumerationSkipsHiddenFiles + 0)
--> 7
(get NSDirectoryEnumerationSkipsPackageDescendants + 0.0) + (get NSDirectoryEnumerationSkipsSubdirectoryDescendants + 0) + (get NSDirectoryEnumerationSkipsHiddenFiles + 0)
-- 7.0

In the case of the OP’s code, everything should go smoothly if you tell AppleScript to begin calculations from the end. Pay attention to the brackets. They 1) implicitly imply get keyword 2) change the order of adding:


use scripting additions
use framework "Foundation"

property NSDirectoryEnumerationSkipsPackageDescendants : a reference to NSDirectoryEnumerationSkipsPackageDescendants of current application
property NSDirectoryEnumerationSkipsSubdirectoryDescendants : a reference to NSDirectoryEnumerationSkipsSubdirectoryDescendants of current application
property NSDirectoryEnumerationSkipsHiddenFiles : a reference to NSDirectoryEnumerationSkipsHiddenFiles of current application

(NSDirectoryEnumerationSkipsPackageDescendants + ((NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (NSDirectoryEnumerationSkipsHiddenFiles as integer)))
--> 7

Sorry, but according one of the AppleScript team at Apple when I reported it several years back, it is a bug.

This:

(current application's NSDirectoryEnumerationSkipsPackageDescendants) 

returns an integer. This:

(current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)

also returns an integer. Putting them together like this:

(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)

should return an integer.

Hello Shane.
Is what I get the normal behavior ?

use scripting additions
use framework "Foundation"

(current application's NSDirectoryEnumerationSkipsPackageDescendants)
--> 2

(current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)
--> 1

(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (get current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)
--> 3
(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)
--> error "Impossible de convertir NSDirectoryEnumerationSkipsSubdirectoryDescendants en type number." number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) vendredi 26 février 2021 16:43:43

Yvan,

Your example probably clarifies something. As far as I know, parentheses should implicitly mean getting the value of an object instead of a reference to an object. The fact that this is not happening really looks like a bug:


use scripting additions
use framework "Foundation"

(get current application's NSDirectoryEnumerationSkipsPackageDescendants) + (get current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (get current application's NSDirectoryEnumerationSkipsHiddenFiles)
--> 7
(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants) + (current application's NSDirectoryEnumerationSkipsHiddenFiles)
--> Error: "Can’t make NSDirectoryEnumerationSkipsSubdirectoryDescendants into type number."

In your code, the very first get is useless.
My understanding is that we are facing a code which doesn’t achieve all what we assume it must do.

If my memory is OK, there is something related to reference but I don’t know the exact English word.

use scripting additions
use framework "Foundation"

-- Here, no need for parentheses
current application's NSDirectoryEnumerationSkipsPackageDescendants --> 2

set v1 to current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants --> 1

-- Below parentheses are required
(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (get current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)
--> 3

(current application's NSDirectoryEnumerationSkipsPackageDescendants) + v1 --> 3

try
	v1 + (current application's NSDirectoryEnumerationSkipsPackageDescendants)
on error errMsg
	log errMsg --> error "Impossible de convertir NSDirectoryEnumerationSkipsPackageDescendants en type number." number -1700 from NSDirectoryEnumerationSkipsPackageDescendants to number
end try

try
	(current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsSubdirectoryDescendants)
on error errMsg
	log errMsg --> error "Impossible de convertir NSDirectoryEnumerationSkipsSubdirectoryDescendants en type number." number -1700 from NSDirectoryEnumerationSkipsSubdirectoryDescendants to number
end try

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) vendredi 26 février 2021 18:24:52

Yes. The get resolves things.

Thank you Shane.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) samedi 27 février 2021 07:33:00

What makes you think this ?

I was confused by the fact that very often the expression with the get keyword has to be enclosed in parentheses.

Now I figured out what my confusion was: parentheses have to be used not in connection with get, but when the get keyword follows immediately after some other keyword. Here’s an example that wouldn’t work if I removed the parentheses (because “get” keyword follows “in” keyword) :

set URLs to {}
tell application "Finder"
	repeat with folder_ref in (get every folder of home)
		set end of URLs to URL of folder_ref
	end repeat
end tell

Here, as I understand, get keyword works as Finder command and not as AppleScript command. But my confusion above led to resolving the AsObjC issue (by Yvan Koenig). That is good :smiley:

Hi,

the following script of Peavine helped a lot but I’d like to ask a question for better understanding:
At the end of the script (see below) there ist a line:

if (count theFiles) = 0 then display dialog "A location file was not found for company " & theCompanyPrefix buttons {"OK"} cancel button 1 default button 1 with icon stop
	return ((theFiles's URLByDeletingLastPathComponent) as alias)

It displays an error message if no flag file was found.
Question: What does the ‘return’ command do?

As far as I understand ‘getFile’ is some kind of subroutine or handler that searches for the flag file.
If it finds the flag file, it returns the path and helps the main routine define the targetFolder.
So what exactly happens instead, if the flag file was not found?

I’m asking because I’d like to copy the sourceFile to another folder (for example called ‘orphans’) if no flag file was found. I tried to figure this out by myself by simply setting the targetFolder to an absolute path, but it didn’t work. If no file was found, everything simply stops.

Can anybody help?

Thanks again,
Carl

Carl. The AppleScript Language Guide succinctly answers your question as follows:

https://developer.apple.com/library/archive/documentation/AppleScript/Conceptual/AppleScriptLangGuide/reference/ASLR_handlers.html#//apple_ref/doc/uid/TP40000983-CH7g-163937

The following further illustrates what return does:

set returnedItem to testHandler() -- call handler and set returnedItem to "This is a test"

on testHandler()
	set someText to "This is a test" -- set variable
	return someText -- exit handler and return the contents of someText
	display dialog "This is a dialog" -- this line is never seen
end testHandler

In the code snippet you quote, if no files are found and the dialog is shown, an error number -128 is returned and the script is exited. In this instance, the return line does nothing. For example:

set returnedItem to testHandler() -- call handler but returnedItem is not set

on testHandler()
	display dialog "Some text" buttons {"OK"} cancel button 1 -- exits script
	return "This is a test" -- this line is never executed
end testHandler

Just as a final point, a return statement in the main body of a script exits the script.

Your understanding is correct–the script quits if the flag file is not found.

I’m not sure where you are with the script and if you’ve resolved the script’s copy-to-server issue discussed earlier in this thread. However, changing the code at the end of the handler to something like the following would appear to do what you want. I have used the desktop as the orphans folder just for simplicity sake, and I have omitted the dialog, which may be unnecessary. I ran a few tests and the script seems to work as desired. As noted before, I do not have a server for testing and put the target folder on an external drive instead.

use scripting additions
use framework "Foundation"

on main()
	set searchPath to "/Volumes/Store/Test/" -- this should be path to folder on server
	set sourceFiles to (choose file with multiple selections allowed)
	
	set ATID to AppleScript's text item delimiters
	repeat with aFile in sourceFiles
		set AppleScript's text item delimiters to {":"}
		set sourceFileName to text item -1 of (aFile as text)
		set AppleScript's text item delimiters to {"-"}
		set theCompanyPrefix to text item 1 of sourceFileName
		set theCompanyName to text item 2 of sourceFileName
		set locationFileName to theCompanyName & "-here_i_am.txt"
		set targetFolder to getFiles(searchPath, locationFileName, theCompanyPrefix)
		tell application "Finder" to duplicate aFile to targetFolder with replacing
	end repeat
	set AppleScript's text item delimiters to ATID
end main

on getFiles(theFolder, theName, theCompanyPrefix)
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set fileManager to current application's NSFileManager's defaultManager()
	set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()
	set thePred to current application's NSPredicate's predicateWithFormat_("(lastPathComponent ==[c] %@) AND (path MATCHES[c] %@)", theName, (".*?/" & theCompanyPrefix & ".*"))
	set theFiles to (folderContents's filteredArrayUsingPredicate:thePred)
	if (count theFiles) = 0 then
		return (path to desktop)
	else
		return ((theFiles's URLByDeletingLastPathComponent) as alias)
	end if
end getFiles

main()

Thank you peavine again, very much.

I implemented your suggestion into my script and it just works.
I love the simplicity and now I understand the ‘mechanics’ thanks to your kind explanation.