Script: Copy Attachments to Default Folder for Library

Users asking other users for AppleScripts that work with Bookends.
Post Reply
DrJJWMac
Posts: 138
Joined: Sat Jun 22, 2019 8:04 am
Location: south east USA

Script: Copy Attachments to Default Folder for Library

Post by DrJJWMac »

This script will copy attachments into the default folder for an open library. This does NOT remove the links to the source attachment that may be in a different folder.

Code: Select all

(*
version 1.0
author jjw
- open a library
- run this script
- searches over all publications
- when attachments are not in the default folder for the library, copies attachments to the default folder for the library
----» ASSUMPTION: the default folder name is the same as the library name «----
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application "Bookends"
	
	activate
	-- collect information
	set the LName to name of the front library window
	set LName to (text items 1 thru -5 of LName) & "/" as text
	set theDPath to the default attachments path & LName
	
	-- collect the bibliographies to process
	set thePList to every publication item of front library window
	
	-- process
	repeat with thePub in thePList
		if the attachment items of thePub is not {} then
			set theAttachmentPath to the path of the first attachment item of thePub as text
			set theDesiredPath to theDPath & the name of the attachment item of thePub as text
			if theAttachmentPath is not equal to theDesiredPath then
				set sourceRef to (POSIX file theAttachmentPath) as alias
				set destFolder to (POSIX file theDPath) as alias
				-- copy fails if file already exists, so just try
				try
					tell application "Finder" to copy file sourceRef to folder destFolder
				end try
			end if
		end if
	end repeat
	return
end tell


DrJJWMac
Posts: 138
Joined: Sat Jun 22, 2019 8:04 am
Location: south east USA

Re: Script: Copy Attachments to Default Folder for Library

Post by DrJJWMac »

This version has the following features:

* choose whether to report, do, or cancel
* choose whether to change label on items that have "ghost" (missing) attachments
* define (by scripting) the format for the desired file name
--> default is LFOLDER_YYYY Authors{_sN} where LFOLDER is a trimmed version of the library folder name, Authors takes the first two (with etal if more) and _sN is a sequence number for more than one attachments
* MOVES the attachment file if it is already in the desired folder, otherwise COPIES the attachment file

What remains is to have the ability to determine the library attachment sub-folder for a given library window. The default is the library name. If the default library sub-folder does not exist, this code will likely crash.

If you have ScriptDebugger (and probably ScriptEditor), I encourage you to run this in Report mode with the editor open. You can see the accumulation of variables and decode what will and will not be copied/moved.

Code: Select all

(*
Create Stand-Alone BE Library
version 2021-07-06
author jjw
- open a library
- run this script
- searches over all publications
- when attachments are not in the default folder for the library, copies attachments to the default folder for the library
----» ASSUMPTION: the default folder name is the same as the library name «----
- renames the attachement with a prefix for the default folder name
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- set this to the label value for an error
-- use -1 to keep current label intact
property errLabel : 3

-- set this to the default action
property DoIt : false

-- the function to return the desired name for the attachment file
-- LibraryName is the library name
-- thePub is the publication item
on setDesiredName(LibraryName, thePub, NAttached)
	
	set rtnStr to ""
	
	-- remove spaces from LibraryName and add suffix
	set prefixStr to my TrimSpaces(LibraryName) & "_"
	-- capture only the year
	tell application "Bookends" to set thePubDateStr to the publication date string of thePub as text
	try
		set theYear to (text items 1 thru 4 of thePubDateStr) as text
	on error
		set theYear to "0000"
	end try
	-- get only the first author from the Authors
	tell application "Bookends" to set AuthorList to authors of thePub as text
	set AuthorStr to my getAuthorString(AuthorList)
	-- handle multiple attachments after the first (0 valued)
	if (NAttached is not equal to 0) then set AuthorStr to AuthorStr & "_s" & NAttached
	
	-- set the return
	set rtnStr to prefixStr & theYear & " - " & AuthorStr & ".pdf" as text
	return rtnStr
	
end setDesiredName

on TrimSpaces(inputText)
	set AppleScript's text item delimiters to {space}
	set trimedText to text items of inputText
	set AppleScript's text item delimiters to {}
	set trimedText to trimedText as string
	return trimedText
end TrimSpaces

on getAuthorString(AuthorList)
	set AppleScript's text item delimiters to {"
"}
	set theEAStr to ""
	set theAList to every text item of AuthorList as list
	set NAuthors to the number of items in theAList
	if NAuthors > 1 then
		if NAuthors = 2 then
			set theFirstA to (the first item of theAList) as text
			set theSecondA to (the second item of the theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) & " and " & (the first text item of theSecondA) as text
		else
			set theFirstA to (the first item of theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) as text
			set theEAStr to " etal"
		end if
	else
		if NAuthors = 0 then
			set theANames to {"unknown"}
		else
			set theFirstA to (the first item of theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) as text
		end if
	end if
	set theRStr to theANames & theEAStr as text
	return theRStr
end getAuthorString

on setAttachmentsNames(AList)
	set theNameList to the first item of AList as text
	set the AList to the rest of the AList
	repeat with theItem in AList
		set theNameList to theNameList & "
" & theItem as text
	end repeat
	return theNameList
end setAttachmentsNames

on run {}
	
	set theListBlank to {}
	set theListCopied to {}
	set theListIgnored to {}
	set theListEmpty to {}
	
	set theAction to the button returned of (display dialog "Report or Do?" buttons {"Report", "Do", "Cancel"} default button "Report" cancel button "Cancel")
	if theAction is "Cancel" then end
	if theAction is "Do" then set DoIt to true
	
	tell application "Bookends"
		activate
		set LName to the (name of the front library window) as text
		set theLibraryName to (text items 1 thru -5 of LName) as text
		set theRootAttachmentFolder to the default attachments path as text
		set theLibraryFolder to theRootAttachmentFolder & theLibraryName as text
		
		set thePList to every publication item of front library window
		set nTotal to the number of items in thePList
		
		-- process
		repeat with thePub in thePList
			
			-- check for attachments
			if the attachment items of thePub is not {} then
				
				-- process publications that should have attachments
				
				set NAttached to 0
				set theAttachmentsNameList to {}
				set theAttachmentNames to ""
				repeat with theAttachment in the attachment items of thePub
					
					set theAttachmentFullPath to (the path of theAttachment) as text
					if theAttachmentFullPath is "" then
						
						-- store info when attachment file path is blank
						set thePubDate to the publication date string of thePub as text
						set thePubAuthors to the authors of thePub as text
						copy {PubDate:thePubDate, pubAuthors:thePubAuthors, pubPath:theAttachmentFullPath} to the end of theListBlank
						set the label color of thePub to errLabel
						
					else
						
						-- process first attachment
						
						-- set the desired path
						set theDesiredName to my setDesiredName(theLibraryName, thePub, NAttached) as text
						set theDesiredFullPath to theLibraryFolder & "/" & theDesiredName
						
						if theAttachmentFullPath is not equal to theDesiredFullPath then
							set CopyIt to false
							set theSourceFolder to my getSourceFolder(theAttachmentFullPath)
							if theSourceFolder is not equal to theLibraryFolder then set CopyIt to true
							-- attached file is not in desired location
							if CopyIt then
								set theShellCmd to "cp " & quoted form of theAttachmentFullPath & " " & quoted form of theDesiredFullPath
								set theAction to "copy"
							else
								set theShellCmd to "mv " & quoted form of theAttachmentFullPath & " " & quoted form of theDesiredFullPath
								set theAction to "move"
							end if
							-- try to copy ... failure means file already exists in destination
							if DoIt then
								try
									do shell script theShellCmd
									--set the attachments of thePub to theDesiredName
									copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"copied/moved", action:theAction} to the end of theListCopied
								on error
									copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"already exists", action:theAction} to the end of theListIgnored
								end try
							else
								copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"just reporting", action:theAction} to the end of theListCopied
							end if
						else
							-- attached file is already desired file
							copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"destination = source", action:theAction} to the end of theListIgnored
						end if
						copy theDesiredName to the end of theAttachmentsNameList
						
					end if
					set NAttached to NAttached + 1
					set NAttachedStr to " " & NAttached as text
				end repeat
				if DoIt then
					set the attachments of thePub to my setAttachmentsNames(theAttachmentsNameList)
				else
					set theAttachmentNames to my setAttachmentsNames(theAttachmentsNameList)
				end if
			else
				
				-- store info for publications without attachments
				set thePubDate to the publication date string of thePub as text
				set thePubAuthors to the authors of thePub
				set thePubID to the id of thePub as text
				copy the {PubDate:thePubDate, pubAuthors:thePubAuthors, pubID:thePubID} to the end of theListEmpty
				
			end if
		end repeat
	end tell
	my reportValues(nTotal, theListBlank, theListCopied, theListEmpty, theListIgnored)
end run

on getSourceFolder(AttachmentPath)
	set AppleScript's text item delimiters to "/"
	set FolderParts to (text items 1 thru -2 of AttachmentPath)
	--set AppleScript's text item delimiters to ""
	set FolderName to every item of FolderParts as text
	return FolderName
end getSourceFolder

on reportValues(nTotal, theListBlank, theListCopied, theListEmpty, theListIgnored)
	
	set nBlank to the number of items in theListBlank
	set nCopied to the number of items in theListCopied
	set nEmpty to the number of items in theListEmpty
	set nIgnored to the number of items in theListIgnored
	
	set theDialog to "Total: " & nTotal & return ¬
		& "Blank: " & nBlank & tab ¬
		& "Empty: " & nEmpty & tab & tab ¬
		& "Ignored: " & nIgnored & tab ¬
		& "Copied/Moved: " & nCopied & tab
	
	display dialog theDialog buttons {"OK"}
	
end reportValues

DrJJWMac
Posts: 138
Joined: Sat Jun 22, 2019 8:04 am
Location: south east USA

Re: Script: Copy Attachments to Default Folder for Library

Post by DrJJWMac »

This version fixes an oversight. Prior to changing the AppleScript text item delimiters, I now store the default and restore the default.

Code: Select all

(*
Create Stand-Alone BE Library
version 2021-07-12
author jjw
- open a library
- run this script
- searches over all publications
- when attachments are not in the default folder for the library, copies attachments to the default folder for the library
----» ASSUMPTION: the default folder name is the same as the library name «----
- renames the attachement with a prefix for the default folder name
*)

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- set this to the label value for an error
-- use -1 to keep current label intact
property errLabel : 3

-- set this to the default action
property DoIt : false

-- the function to return the desired name for the attachment file
-- LibraryName is the library name
-- thePub is the publication item
on setDesiredName(LibraryName, thePub, NAttached)
	
	set rtnStr to ""
	
	-- remove spaces from LibraryName and add suffix
	set prefixStr to my TrimSpaces(LibraryName) & "_"
	-- capture only the year
	tell application "Bookends" to set thePubDateStr to the publication date string of thePub as text
	try
		set theYear to (text items 1 thru 4 of thePubDateStr) as text
	on error
		set theYear to "0000"
	end try
	-- get only the first author from the Authors
	tell application "Bookends" to set AuthorList to authors of thePub as text
	set AuthorStr to my getAuthorString(AuthorList)
	-- handle multiple attachments after the first (0 valued)
	if (NAttached is not equal to 0) then set AuthorStr to AuthorStr & "_s" & NAttached
	
	-- set the return
	set rtnStr to prefixStr & theYear & " - " & AuthorStr & ".pdf" as text
	return rtnStr
	
end setDesiredName

on TrimSpaces(inputText)
	set oDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {space}
	set trimedText to text items of inputText
	set AppleScript's text item delimiters to {}
	set trimedText to trimedText as string
	set AppleScript's text item delimiters to oDelimiters
	return trimedText
end TrimSpaces

on getAuthorString(AuthorList)
	set oDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {"
"}
	set theEAStr to ""
	set theAList to every text item of AuthorList as list
	set NAuthors to the number of items in theAList
	if NAuthors > 1 then
		if NAuthors = 2 then
			set theFirstA to (the first item of theAList) as text
			set theSecondA to (the second item of the theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) & " and " & (the first text item of theSecondA) as text
		else
			set theFirstA to (the first item of theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) as text
			set theEAStr to " etal"
		end if
	else
		if NAuthors = 0 then
			set theANames to {"unknown"}
		else
			set theFirstA to (the first item of theAList) as text
			set AppleScript's text item delimiters to ","
			set theANames to (the first text item of theFirstA) as text
		end if
	end if
	set theRStr to theANames & theEAStr as text
	set AppleScript's text item delimiters to oDelimiters
	return theRStr
end getAuthorString

on setAttachmentsNames(AList)
	set theNameList to the first item of AList as text
	set the AList to the rest of the AList
	repeat with theItem in AList
		set theNameList to theNameList & "
" & theItem as text
	end repeat
	return theNameList
end setAttachmentsNames

on run {}
	
	set theListBlank to {}
	set theListCopied to {}
	set theListIgnored to {}
	set theListEmpty to {}
	
	set theAction to the button returned of (display dialog "Report or Do?" buttons {"Report", "Do", "Cancel"} default button "Report" cancel button "Cancel")
	if theAction is "Cancel" then end
	if theAction is "Do" then set DoIt to true
	
	tell application "Bookends"
		activate
		set LName to the (name of the front library window) as text
		set theLibraryName to (text items 1 thru -5 of LName) as text
		set theRootAttachmentFolder to the default attachments path as text
		set theLibraryFolder to theRootAttachmentFolder & theLibraryName as text
		
		set thePList to every publication item of front library window
		set nTotal to the number of items in thePList
		
		-- process
		repeat with thePub in thePList
			
			-- check for attachments
			if the attachment items of thePub is not {} then
				
				-- process publications that should have attachments
				
				set NAttached to 0
				set theAttachmentsNameList to {}
				set theAttachmentNames to ""
				repeat with theAttachment in the attachment items of thePub
					
					set theAttachmentFullPath to (the path of theAttachment) as text
					if theAttachmentFullPath is "" then
						
						-- store info when attachment file path is blank
						set thePubDate to the publication date string of thePub as text
						set thePubAuthors to the authors of thePub as text
						copy {PubDate:thePubDate, pubAuthors:thePubAuthors, pubPath:theAttachmentFullPath} to the end of theListBlank
						set the label color of thePub to errLabel
						
					else
						
						-- process first attachment
						
						-- set the desired path
						set theDesiredName to my setDesiredName(theLibraryName, thePub, NAttached) as text
						set theDesiredFullPath to theLibraryFolder & "/" & theDesiredName
						
						if theAttachmentFullPath is not equal to theDesiredFullPath then
							set CopyIt to false
							set theSourceFolder to my getSourceFolder(theAttachmentFullPath)
							if theSourceFolder is not equal to theLibraryFolder then set CopyIt to true
							-- attached file is not in desired location
							if CopyIt then
								set theShellCmd to "cp " & quoted form of theAttachmentFullPath & " " & quoted form of theDesiredFullPath
								set theAction to "copy"
							else
								set theShellCmd to "mv " & quoted form of theAttachmentFullPath & " " & quoted form of theDesiredFullPath
								set theAction to "move"
							end if
							-- try to copy ... failure means file already exists in destination
							if DoIt then
								try
									do shell script theShellCmd
									--set the attachments of thePub to theDesiredName
									copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"copied/moved", action:theAction} to the end of theListCopied
								on error
									copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"already exists", action:theAction} to the end of theListIgnored
								end try
							else
								copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"just reporting", action:theAction} to the end of theListCopied
							end if
						else
							-- attached file is already desired file
							copy {source:theAttachmentFullPath, destination:theDesiredFullPath, why:"destination = source", action:theAction} to the end of theListIgnored
						end if
						copy theDesiredName to the end of theAttachmentsNameList
						
					end if
					set NAttached to NAttached + 1
					set NAttachedStr to " " & NAttached as text
				end repeat
				if DoIt then
					set the attachments of thePub to my setAttachmentsNames(theAttachmentsNameList)
				else
					set theAttachmentNames to my setAttachmentsNames(theAttachmentsNameList)
				end if
			else
				
				-- store info for publications without attachments
				set thePubDate to the publication date string of thePub as text
				set thePubAuthors to the authors of thePub
				set thePubID to the id of thePub as text
				copy the {PubDate:thePubDate, pubAuthors:thePubAuthors, pubID:thePubID} to the end of theListEmpty
				
			end if
		end repeat
	end tell
	my reportValues(nTotal, theListBlank, theListCopied, theListEmpty, theListIgnored)
end run

on getSourceFolder(AttachmentPath)
	set oDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "/"
	set FolderParts to (text items 1 thru -2 of AttachmentPath)
	set FolderName to every item of FolderParts as text
	set AppleScript's text item delimiters to oDelimiters
	return FolderName
end getSourceFolder

on reportValues(nTotal, theListBlank, theListCopied, theListEmpty, theListIgnored)
	
	set nBlank to the number of items in theListBlank
	set nCopied to the number of items in theListCopied
	set nEmpty to the number of items in theListEmpty
	set nIgnored to the number of items in theListIgnored
	
	set theDialog to "Total: " & nTotal & return ¬
		& "Blank: " & nBlank & tab ¬
		& "Empty: " & nEmpty & tab & tab ¬
		& "Ignored: " & nIgnored & tab ¬
		& "Copied/Moved: " & nCopied & tab
	
	display dialog theDialog buttons {"OK"}
	
end reportValues
[\code]

Post Reply