Script: Import Finder aliases, resolve them to replicants and reference external aliases

Hi folks,

this script imports a Finder folder including aliases and resolves them to replicants.

It

  • initiates a standard DEVONthink import

which imports files and subfolders but no aliases, so afterwards the script

  • gets all aliases
  • resolves them
  • checks whether the alias’s original file or folder was already imported

if so it

  • creates replicants

if not it

  • creates bookmarks
  • and a Smart Group.

So what’s in your Finder folder is either

  • imported
  • replicated or
  • referenced.

Bookmarks are created if an alias points to a path outside the imported folder, only in this case the Smart Group is created and opened at the end. Opening it automatically acts as reminder that not all files or folders were imported. In case of bookmarks for PDFs or images the content can be viewed directly in DEVONthink, in case of bookmarks for folders make sure to open them via Data > Launch URL, if you double click them they are added to the download manager.

A second script lets you import the bookmarks if need be, if that worked the bookmark gets label 2/green, if it failed it’s label 1/red (e.g. original deleted or an unmounted disk). If you import a folder and it contains aliases make sure to check the Smart Group again and import what’s necessary. However bookmarks should only act as a reminder in the first place, it’s not meant to import everything :wink:

Skipped aliases:

Aliases resolved to replicants:

Script: Import, resolve Finder aliases to replicants and reference external aliases

-- Import, resolve Finder aliases to replicants and reference external aliases

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

tell application id "DNtp"
	try
		activate
		set thePath to POSIX path of (choose folder with prompt "Choose folder" default location (path to desktop folder) without invisibles)
		set theGroup to import thePath to display group selector
		set theDatabase to database of theGroup
		set theLocationAndName to ((location of theGroup) & my replaceString(((name of theGroup) as string), "/", "\\/")) as string
		set createSmartGroup to false
		
		repeat with i in {true, false}
			set theAliasPaths_record to my getDescendingAliasPaths(thePath, i as boolean)
			repeat with thisItem in theAliasPaths_record
				set theAliasParent_Location to (theLocationAndName & "/" & my replaceString(pathParent of thisItem, thePath, "")) as string
				set theAliasParent_Group to create location theAliasParent_Location in theDatabase
				set theOriginalPath to pathOriginal of thisItem
				if theOriginalPath starts with thePath then
					set theAliasOriginal_Location to (theLocationAndName & "/" & my replaceString(pathOriginal of thisItem, thePath, "")) as string
					if (exists record at theAliasOriginal_Location in theDatabase) then
						set theAliasOriginal_Group to get record at theAliasOriginal_Location in theDatabase
						replicate record theAliasOriginal_Group to theAliasParent_Group
					else
						import pathOriginal of thisItem to theAliasParent_Group
					end if
				else
					set {theName, theURL, thePOSIXPath} to {item 1, item 2, item 3} of my getFileProperties(pathOriginal of thisItem)
					create record with {name:theName, URL:theURL, type:bookmark, comment:thePOSIXPath} in theAliasParent_Group
					set createSmartGroup to true
				end if
			end repeat
		end repeat
		
		if createSmartGroup = true then
			set theSmartGroup to create record with {type:smart group, search predicates:"url:file:///", name:"-- Not imported --"} in theGroup
			set search group of theSmartGroup to theGroup -- this is a necessary workaround in DEVONthink 3.6
			open window for record theSmartGroup
			activate
		end if
		
	on error error_message number error_number
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on getDescendingAliasPaths(thePath, isDirectory)
	try
		-- Based on: https://forum.latenightsw.com/t/searching-for-alias-files-recursively-not-descending-into-packages/1997/5
		set theFolderURL to current application's class "NSURL"'s fileURLWithPath:thePath
		set isAliasKey to current application's NSURLIsAliasFileKey
		set isDirectoryKey to current application's NSURLIsDirectoryKey
		set theFileManager to current application's NSFileManager's defaultManager()
		set allURLs to (theFileManager's enumeratorAtURL:theFolderURL includingPropertiesForKeys:{isAliasKey} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants) + ((current application's NSDirectoryEnumerationSkipsHiddenFiles) as integer)) errorHandler:(missing value))'s allObjects()
		set theAliasPaths_record to {}
		repeat with thisURL in allURLs
			set aliaskeyValue to end of (thisURL's getResourceValue:(reference) forKey:isAliasKey |error|:(missing value))
			if (aliaskeyValue as boolean) = true then
				set thisURL_path to (thisURL's valueForKeyPath:"path")
				set thisOriginalURL_path to my resolveAlias(thisURL_path)
				if thisOriginalURL_path ≠ missing value then
					set thisOriginalURL to (current application's class "NSURL"'s fileURLWithPath:thisOriginalURL_path)
					set directorykeyValue to end of (thisOriginalURL's getResourceValue:(reference) forKey:isDirectoryKey |error|:(missing value))
					if (directorykeyValue as boolean) = isDirectory then
						set thisParentURL_path to POSIX path of ((thisURL's valueForKeyPath:"URLByDeletingLastPathComponent") as string)
						set end of theAliasPaths_record to {pathParent:thisParentURL_path, pathOriginal:thisOriginalURL_path}
					end if
				end if
			end if
		end repeat
		return theAliasPaths_record
	on error error_message number error_number
		activate
		display alert "Error: Handler \"getDescendingAliasPaths\"" message error_message as warning
		error number -128
	end try
end getDescendingAliasPaths

on resolveAlias(thePath)
	try
		set thePath_URL to (current application's class "NSURL"'s fileURLWithPath:thePath)
		set thePath_Original_URL to (current application's class "NSURL"'s URLByResolvingAliasFileAtURL:thePath_URL options:0 |error|:(missing value))
		if thePath_Original_URL ≠ missing value then set thePath_Original to (POSIX path of (thePath_Original_URL as string))
	on error error_message number error_number
		activate
		display alert "Error: Handler \"resolveAlias\"" message error_message as warning
		error number -128
	end try
end resolveAlias

on replaceString(theText, oldString, newString)
	try
		set theString to current application's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:oldString withString:newString
		return newString as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"replaceString\"" message error_message as warning
		error number -128
	end try
end replaceString

on getFileProperties(thePath)
	tell application "System Events"
		try
			set theItem to disk item thePath
			set theName to name of theItem
			if class of theItem = folder then set theName to "_Folder: " & theName
			set theURL to URL of theItem
			set thePOSIXPath to POSIX path of theItem
			return {theName, theURL, thePOSIXPath}
		on error error_message number error_number
			activate
			display alert "Error: Handler \"getFileProperties\"" message error_message as warning
			error number -128
		end try
	end tell
end getFileProperties


Script: Import via bookmark, resolve Finder aliases to replicants and reference external aliases

-- Import via bookmark, resolve Finder aliases to replicants and reference external aliases

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

tell application id "DNtp"
	try
		if not (exists think window 1) then error "Please select a record"
		set theRecords to selection of think window 1
		if theRecords = {} then error "Please select a record"
		
		repeat with thisRecord in theRecords
			set thisURL to URL of thisRecord
			if thisURL starts with "file:///" then
				set theURL to (current application's class "NSURL"'s URLWithString:thisURL)
				if theURL ≠ missing value then
					set theURL_path to (theURL's valueForKeyPath:"path") as string
					set theGroup to import theURL_path to (parent 1 of thisRecord)
					set theDatabase to database of theGroup
					set theLocationAndName to ((location of theGroup) & my replaceString(((name of theGroup) as string), "/", "\\/")) as string
					set createSmartGroup to false
					
					repeat with i in {true, false}
						set theAliasPaths_record to my getDescendingAliasPaths(theURL_path, i as boolean)
						repeat with thisItem in theAliasPaths_record
							set theAliasParent_Location to (theLocationAndName & "/" & my replaceString(pathParent of thisItem, theURL_path, "")) as string
							set theAliasParent_Group to create location theAliasParent_Location in theDatabase
							set theOriginalPath to pathOriginal of thisItem
							if theOriginalPath starts with theURL_path then
								set theAliasOriginal_Location to (theLocationAndName & "/" & my replaceString(pathOriginal of thisItem, theURL_path, "")) as string
								if (exists record at theAliasOriginal_Location in theDatabase) then
									set theAliasOriginal_Group to get record at theAliasOriginal_Location in theDatabase
									replicate record theAliasOriginal_Group to theAliasParent_Group
								else
									import pathOriginal of thisItem to theAliasParent_Group
								end if
							else
								set {theName, theURL, thePOSIXPath} to {item 1, item 2, item 3} of my getFileProperties(pathOriginal of thisItem)
								create record with {name:theName, URL:theURL, type:bookmark, comment:thePOSIXPath} in theAliasParent_Group
								set createSmartGroup to true
							end if
						end repeat
					end repeat
					
					if createSmartGroup = true then
						set theSmartGroup to create record with {type:smart group, search predicates:"url:file:///", name:"-- Not imported --"} in theGroup
						set search group of theSmartGroup to theGroup -- this is a necessary workaround in DEVONthink 3.6
						open window for record theSmartGroup
						activate
					end if
					
					set label of thisRecord to 2
				else
					set label of thisRecord to 1
				end if
			end if
		end repeat
		
	on error error_message number error_number
		if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
		return
	end try
end tell

on getDescendingAliasPaths(thePath, isDirectory)
	try
		-- Based on: https://forum.latenightsw.com/t/searching-for-alias-files-recursively-not-descending-into-packages/1997/5
		set theFolderURL to current application's class "NSURL"'s fileURLWithPath:thePath
		set isAliasKey to current application's NSURLIsAliasFileKey
		set isDirectoryKey to current application's NSURLIsDirectoryKey
		set theFileManager to current application's NSFileManager's defaultManager()
		set allURLs to (theFileManager's enumeratorAtURL:theFolderURL includingPropertiesForKeys:{isAliasKey} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants) + ((current application's NSDirectoryEnumerationSkipsHiddenFiles) as integer)) errorHandler:(missing value))'s allObjects()
		set theAliasPaths_record to {}
		repeat with thisURL in allURLs
			set aliaskeyValue to end of (thisURL's getResourceValue:(reference) forKey:isAliasKey |error|:(missing value))
			if (aliaskeyValue as boolean) = true then
				set thisURL_path to (thisURL's valueForKeyPath:"path")
				set thisOriginalURL_path to my resolveAlias(thisURL_path)
				if thisOriginalURL_path ≠ missing value then
					set thisOriginalURL to (current application's class "NSURL"'s fileURLWithPath:thisOriginalURL_path)
					set directorykeyValue to end of (thisOriginalURL's getResourceValue:(reference) forKey:isDirectoryKey |error|:(missing value))
					if (directorykeyValue as boolean) = isDirectory then
						set thisParentURL_path to POSIX path of ((thisURL's valueForKeyPath:"URLByDeletingLastPathComponent") as string)
						set end of theAliasPaths_record to {pathParent:thisParentURL_path, pathOriginal:thisOriginalURL_path}
					end if
				end if
			end if
		end repeat
		return theAliasPaths_record
	on error error_message number error_number
		activate
		display alert "Error: Handler \"getDescendingAliasPaths\"" message error_message as warning
		error number -128
	end try
end getDescendingAliasPaths

on resolveAlias(thePath)
	try
		set thePath_URL to (current application's class "NSURL"'s fileURLWithPath:thePath)
		set thePath_Original_URL to (current application's class "NSURL"'s URLByResolvingAliasFileAtURL:thePath_URL options:0 |error|:(missing value))
		if thePath_Original_URL ≠ missing value then set thePath_Original to (POSIX path of (thePath_Original_URL as string))
	on error error_message number error_number
		activate
		display alert "Error: Handler \"resolveAlias\"" message error_message as warning
		error number -128
	end try
end resolveAlias

on replaceString(theText, oldString, newString)
	try
		set theString to current application's NSString's stringWithString:theText
		set newString to theString's stringByReplacingOccurrencesOfString:oldString withString:newString
		return newString as string
	on error error_message number error_number
		activate
		display alert "Error: Handler \"replaceString\"" message error_message as warning
		error number -128
	end try
end replaceString

on getFileProperties(thePath)
	tell application "System Events"
		try
			set theItem to disk item thePath
			set theName to name of theItem
			if class of theItem = folder then set theName to "_Folder: " & theName
			set theURL to URL of theItem
			set thePOSIXPath to POSIX path of theItem
			return {theName, theURL, thePOSIXPath}
		on error error_message number error_number
			activate
			display alert "Error: Handler \"getFileProperties\"" message error_message as warning
			error number -128
		end try
	end tell
end getFileProperties

In case you tried to run the script you’ve found that it doesn’t work as expected in DEVONthink 3.6.

There’s a problem with creating Smart Groups via AppleScript, see Are Smart Groups in DEVONthink 3.6 broken?.

Updated the script with a workaround.