Tweak Needed With AppleScript in Smart Rule to Copy Item

I made a smart rule so I could select items in DEVONthink and copy them to a location in the Finder. The rule is set to be such that ‘kind’ is ‘Any Document’, and then to run, ‘On Demand’, this applescript:

on performSmartRule(theRecords)
	tell application id "DNtp"
		repeat with theRecord in theRecords
			set loc to (location of theRecord)
			set thePath to "/Users/avatar/Documents/TargetFolder" & loc
			set dtsPath to quoted form of POSIX path of thePath as string
			export record theRecord to thePath
			do shell script "find " & dtsPath & " -name DEVONtech_storage -delete"
			display notification "Copied OK" with title "Copied to Target folder"
		end repeat
	end tell
end performSmartRule

After that script I also add a tag so that the item’s marked in DEVONthink as having been copied at least once.

But I’d prefer the applescript to be able to check that the item to be copied doesn’t already exist in the target Finder location, and to alert the user if it does, and stop execution.

(The folder in the target location that takes the name of the group is no problem, because, for the first run, it’s created if it’s not there; and if it’s part of the target for subsequent runs, its name forms part of the target path, so it’s not overwritten, so older files inside it won’t get deleted).

I have a stop-gap “solution” at the moment, where the last condition before “Perform the following actions” is “if tag is not [the tag I used]”, which causes the rule to fail silently if the item’s already been copied once. But that ‘tag check’ isn’t satisfactory - obviously it would be better to have a check made in the applescript to see if the file actually exists in the target location, and if it does, for the script to alert the user, and then not copy, or just exit somehow.

But I don’t know of any way of breaking out of the “tell”, with error number -128 or whatever; nothing like that seems to work. With the tweaks I’ve tried the script still copies the file and I get a duplicate file with “-1” etc appended to its name.

I’m hoping someone may be able to help me here.

You could do something like
do shell script "cp -n " & quoted form of POSIX path (path of theRecord) &" " & quoted form of thePath
That will only copy the file if it’s not there already.

not sure about the parenthesis nor the “quoted form of” thingies.
And why do you do this
do shell script "find " & dtsPath & " -name DEVONtech_storage -delete"
inside the loop (i.e. for every file) instead of once outside the loop?

Yes the deletion of the DEVONtech_storage thing should strictly be outside the loop, but it doesn’t make any difference practically.

But that do shell script doesn’t compile, and as far as I can see, if it did, it wouldn’t alert the user either.

Why would it alert the user? It’s only meant to replace your export call with something that does check for the existence of the target before copying. Never said that it would do anything else. The line compiles fine if you add an of before (path of the Record).

You mean try to copy the actually specified path of the file in the DEVONthink package by something like:

set p to (path of theRecord)

So using cp -n is copying rather than exporting the record but it won’t do files that are really directories like .RTFDs.

It’s just the “alert and stop if already exists” that I was looking for.

What’s the difference between “copying” and “exporting”? And why would cp not be able to copy folders, for example using the -r flag?
But you can of course use Finder or perhaps System Events to check for existence of the target and then pop up an alarm.

cp just makes it more complicated since you have to introduce some logic to distinguish between say text files and files that are directories like RTFDs. With export you don’t need to.

That is what I’ve been trying to do. Do you have a working example?

What makes you think that? cp -R works for files and folders (I was wrong in writing -r before). The exit value of cp is 0 if everything went ok (i.e. the target didn’t exist before) and something > 0 else – here, it’s 1 if the target exists already.


might tell you more about using shell commands from AS.

No. I don’t touch Finder scripting with a ten foot pole. All this POSIX path (in 2023, really?) and alias stuff is just insane. But I’m sure that your favorite search engine will reveal AS code to check for a file’s existence.

Here’s a JavaScript version of your script with cp. I didn’t test it, though. But it compiles ok.

function performsmartrule(records) {
	const app = Application("DEVONthink 3");
    const currentApplication = Application.currentApplication();
    currentApplication.includeStandardAdditions = true;
	const basePath = "/Users/avatar/Documents/TargetFolder/";
	records.forEach(r => {
	    const targetFolder = `${basePath}${r.location()}`;
		const result = currentApplication.doShellScript(`cp -nR '${r.path}' '${targetFolder}'`);
		if (result) app.log(`${} already exists in target ${$targetFolder}`);
   currentApplication.doShellScript(`find '${basePath}' -name DEVONtech_storage -delete`);

No, if I use cp -R it will produce a Finder folder with a text file inside for an RFTD.

As far as I can see AppleScript is pretty much alive and well in 2023. Not that we’re experts :grimacing:

What else would it produce? RTFD is a folder (well, Apple probably calls it package to confuse users):

cp -nR Neuer\ RTF-Text\ 1.rtfd ~/Develop/test
ls -l ~/Develop/test
total 11856
drwxr-xr-x@ 4 ck  staff      128 23 Jul 15:56 Neuer RTF-Text 1.rtfd
ls -l Neuer\ RTF-Text\ 1.rtfd
total 72
-rw-r--r--  1 ck  staff    670 23 Jul 15:56 TXT.rtf
-rw-r--r--  1 ck  staff  29760 18 Jun  2022 Table-blue-header.jpg

Double-clicking on the copied package/folder/whatever opens it just fine in TextEditor.

As alive as something can be that hasn’t got any new relevant functionality in the last decade. And that was, btw, not what I was saying at all: I said that this Posix path and alias stuff in Finder is way out of date.

There is nothing but Posix paths nowadays anymore. And the company which has no problems getting rid of outdated hardware (think parallel and serial interfaces, FireWire, whatnot) is still hanging on the file system naming conventions from the 90s…

Interestingly, there’s no such thing as Posix path in JavaScript (nor in NSFoundation) – all paths are Posix.

Nope. It opens a folder, within which you can see said text file (“TXT.rtf”). I have tried it. No doubt if I suffixed the output (folder) with .rtfd it would open, but that’s just more unnecessary logic.

I’m curious what the purpose of the smart rule is.

To copy selected items into a different Finder folder (but also reconstructing the directory structure where it doesn’t already exist in that folder).

If I cp -Rn an RTFD document (which always is only a folder with the extension .rtfd) to another place, I get a new folder with the extension .rtfd. And I’ve shown you the output of my shell commands. No need to add a suffix to anything here.

But suit yourself – you can certainly figure out how to do what you want any other way.

To copy selected items into a different Finder folder

And these are indexed items? I don’t see where you specified that.

And again, for what purpose: academic curiosity, tax time, end-of-life procedures, …?

PS: Handling things in System Events is trivial in this case.

No the items are not indexed.

The term “different Finder folder” may have been misleading, sorry: I meant just “a Finder folder” (different to the one in which the file resides in the DEVONthink file structure).

Purpose—possibly all three of the ones you mention.

I did try a System Events check to see if the file already existed in the target folder via “alias” but clearly it wasn’t nearly as trivial as I would have hoped.

You mentioned cp -n, cp -R, cp -nR and cp -Rn, but I settled on cp -R, not -Rn as I would want it to be overwritten if it’s a new (edited) version. I tried:

on performSmartRule(theRecords)
tell application id "DNtp"
repeat with theRecord in theRecords
set loc to (location of theRecord)
set p to (path of theRecord)
set thePath to "/Users/avatar/Documents/TargetFolder" & loc
set recordName to (name of theRecord as string)		
set extensionOffset to (offset of "." in (reverse of characters of p as string)) - 1
set fileExtension to (characters -1 through -extensionOffset of p) as string
do shell script "cp -R " & p & " " & thePath & recordName & "." & fileExtension
end repeat
do shell script "find " & thePath & " -name DEVONtech_storage -delete"
end tell
end performSmartRule

However in the case of cp -R, if the ‘directory’ (.rtfd) already exists, then the copied ‘directory’ (.rtfd) becomes a sub-directory under the destination, which messes up an .rtfd on every subsequent overwrite.

You can see this by looking inside the package contents of an .rtfd that has been overwritten and seeing the ‘extra’ .rtf file, in addition to the usual regular “TXT.rtf” file. (The .rtfd will actually open in the Finder, but obviously it’s not right, and subsequent overwrites will balloon the file size.)

And cp -R works only on ‘directories’ (i.e., .rtfd). A MyFile.numbers file, for instance, will show the error that the source is not a directory, and would need cp instead.

So how to prevent the creation of sub-directories in the case of .rtfd files? An exhaustive list in the logic somewhere of files that are in fact ‘directories’ (packages) for one cp -R condition, and a cp condition for all other file-types?

And how to accommodate both files and ‘directories’ (.rtfd)?

This is probably why I initially saw AppleScript and export as potentially less troublesome.

It :100: is less troublesome.

Here is a working script in pure AppleScript, ideally used in a smart rule with criteria of Kind is Any Document and Tag is not exported

property exportFolder : "/Users/andromedax/Documents/TargetFolder"
property exportTag : "exported"

on performSmartRule(theRecords)
	set od to AppleScript's text item delimiters
	tell application id "DNtp"
		set incr to 0
		set recCount to (count theRecords)
		repeat with theRecord in theRecords
			set recTags to (tags of theRecord)
			(* In your smart rule, make sure to set criteria of Kind is Any Document and Tag is not exported. *)
			set recLoc to (location of theRecord) -- e.g., /Inbox/My Stuff
			set recPath to (path of theRecord) -- 'name' may not return the file's extension, which would be problematic downstream
			-- Get the filename with the extension via AppleScript's text item delimiters
			set AppleScript's text item delimiters to "/"
			set recName to last item of (text items of recPath) as string
			set thePath to exportFolder & recLoc
			if not my filecheck(thePath & recName) then -- Do a file check and act if the file doesn't exist	
				export record theRecord to thePath
				set tags of theRecord to (recTags & exportTag)
				set incr to incr + 1 -- Incrementing on successful export
				(* Check for and delete the DT storage file. Make sure you want to do this. 
If not included in the loop, this will not remove the DT Storage files in subfolders. 
				Ideally, this could be handled once per exported group but I don't think there's much overhead added here.*)

				if my filecheck(thePath & "DEVONtech_storage") then tell application "System Events" to delete file (thePath & "DEVONtech_storage")
			end if
			set AppleScript's text item delimiters to od
		end repeat
		--- User-facing messages
		if incr ≥ 1 then
			display notification "copied to target folder" with title (incr & " of " & recCount & " items" as string)
			display notification "copied to target folder" with title "No items"
		end if
	end tell
end performSmartRule

on filecheck(theRecordPath)
	tell application "System Events" to return exists file theRecordPath
end filecheck

Thank you.

But I find it won’t compile, saying, “Expected end of line but found class name” and highlighting the word record in the line: export record theRecord to thePath .

PS I assume by your caveats “Kind is Any Document and Tag is not exported” you mean:

The filename property could be used instead. In addition, instead of using the export command and deleting the DEVONtech_Storage file, why not simply copy the file to the destination as the smart rule processes only documents? This avoids the second filecheck.

1 Like