I have a hierarchy of folders that I need to merge with another hierarchy of folders. 211560 items in 83 top level folders make up the target folder. The source folder is 25GB for 5951 items. Loads of folder and file duplications in the source folder and it’s subfolders.
I need code to merge these two folders. It needs to recursively merge any subfolders as well as the top-level. Before I begin re-inventing this wheel does anyone already have code that does this well?
Well that looks great! Perhaps a bit more capabilities than I need for this task and priced a bit too high for this task alone. If I needed all the functionality it offers it would be reasonable.
FYI Carbon Copy Cloner can achieve this merge with the correct settings. Preserving newer versions of files and it’s fine with deep hierarchies and tons of files.
CCC takes a bit more setup than I want but it’s very fast, documents its actions and I have full confidence in its abilities. It solved my current need but I’m still going to give an AppleScript solution a go.
I had music libraries on several macs and I used an appleScript to merge them into one on a server.
It would look in every folder in the mac’s music folder, find that same hierarchy on the server, add any new files, and replace any older files with newer ones. Would that work?
I built that using Shane’s Filemanager.lib.
Ed that sounds like it’s exactly what I need! Built on Filemanager.lib is a bonus.
OK, here it is. You’d need to make some changes to test it. I wrote this 5 years ago, just updated it to account for changes to the way apple saves music files, but the main thing, duplicating and syncing a directory, is in place and should work on any files and directories, not just music files.
use framework "Foundation"
use scripting additions
use script "FileManagerLib" version "2.3.5"
property sharedFolder : "Ed:Music:"
property sharedRoot : ""
property localRoot : ""
set localRoot to path to music folder as text
--set localMusicFolder to localRoot & "iTunes:iTunes Media:Music:"
set allLocalMusicFolders to {localRoot & "Music:Media.localized:Music:"}
set the beginning of allLocalMusicFolders to localRoot & "Music:Media.localized:Apple Music:"
repeat with localMusicFolder in allLocalMusicFolders
set localMusicFolder to localMusicFolder as alias
tell application "Finder" to open localMusicFolder
set sharedRoot to sharedFolder
try
set sharedFolder to sharedFolder as alias
on error errMsg number errNum
if errNum is -43 then
set sharedFolder to my PickNewSharedFolder()
end if
end try
set visualLocalMusicFolder to VisualizePath(localMusicFolder as text)
set progress description to "Gathering music files from " & visualLocalMusicFolder
set LocalFileList to FolderEntireContents(localMusicFolder)
set progress description to "Syncing music files from " & visualLocalMusicFolder
SyncTheseFiles(LocalFileList, localRoot, sharedFolder, sharedRoot)
end repeat
--set sharedFileList to FolderEntireContents(sharedFolder)
--SyncTheseFiles(sharedFileList, sharedRoot, localMusicFolder, localRoot)
on SyncTheseFiles(sourceFilePaths, sourceRoot, syncDestination, destinationRoot)
local syncDestination
local sourceFile, sharedFileMod, fileName
local sharedFile, sourceFileMod
set syncReport to {}
set x to 0
set fileCount to count of sourceFilePaths
set copiedToCount to 0
set copiedFromCount to 0
set replacedToCount to 0
set replacedFromCount to 0
set alreadySyncedCount to 0
set progress total steps to fileCount
set progress completed steps to 0
repeat with thisFilePath in sourceFilePaths
set sourceFile to (thisFilePath as item) as alias
set AppleScript's text item delimiters to {sourceRoot}
set filePathElements to text item 2 of (sourceFile as text)
set {syncDestination, fileName} to EstablishPaths(filePathElements, destinationRoot)
set x to x + 1
if fileName is not "" then
set destinationFile to (syncDestination as text) & fileName as text
set destinationFileExists to exists object destinationFile
if destinationFileExists then
set destinationFileMod to GetModDate(destinationFile as alias)
set sourceFileMod to GetModDate(sourceFile as alias)
if destinationFileMod < sourceFileMod then
set syncedFile to copy object sourceFile ¬
to folder syncDestination ¬
replacing true ¬
without return path
set replacedto to replacedto + 1
--else if destinationFileMod > sourceFileMod then
--set syncedFile to copy object destinationFile ¬
-- to folder fileSubLocation ¬
-- replacing true ¬
-- without return path
--set replacedFrom to replacedFrom + 1
else
set alreadySyncedCount to alreadySyncedCount + 1
end if
else
set syncedFile to copy object sourceFile ¬
to folder syncDestination ¬
replacing true ¬
without return path
set copiedToCount to copiedToCount + 1
end if
end if
set progress additional description to "Processing:" & " File " & x & " of " & fileCount
set progress completed steps to x
end repeat
return {copiedToCount, copiedFromCount, replacedToCount, replacedFromCount, alreadySyncedCount}
end SyncTheseFiles
on GetModDate(aPath)
local aPosixPath
set dateInfo to date info for aPath ¬
without returning dictionary
set modDate to modification_date of dateInfo
return the modDate
end GetModDate
on FolderEntireContents(aFolder)
set folderContents to objects of aFolder ¬
searching subfolders true ¬
include invisible items false ¬
include folders false ¬
include files true ¬
result type files list
--A record with the following labels: full_name, name_extension, name_stub, ¬
--parent_folder_path, parent_folder_name, displayed_name, file_kind.
set filteredContents to {}
repeat with thisFile in folderContents
set fileInfo to parse object thisFile as item ¬
with HFS results
if name_extension of fileInfo is in {"wav", "band", "mp3", "m4p", "m4a"} then
set the end of filteredContents to thisFile as item
end if
end repeat
return filteredContents
end FolderEntireContents
on PickNewSharedFolder()
set folderPrompt to "Select the shared folder you want to sync with"
set chosenFolder to choose folder with prompt folderPrompt ¬
invisibles false ¬
multiple selections allowed false ¬
without showing package contents
return chosenFolder
end PickNewSharedFolder
on EstablishPaths(pathToReCreate, destinationPath)
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {":"}
set newPath to destinationPath as text
set pathToReCreate to every text item of (pathToReCreate as text)
if the last item of pathToReCreate is "" then
set fileName to ""
--apps, libraries and bundles
else
set fileName to the last item of pathToReCreate
end if
set folderLocation to (items 1 thru -3 of pathToReCreate)
set folderLocation to (destinationPath as text) & folderLocation as text
set folderName to item -2 of pathToReCreate
set destinationFolder to create folder at folderLocation ¬
use name folderName
set AppleScript's text item delimiters to saveTID
set destinationFolder to (POSIX file destinationFolder) as alias
return {destinationFolder, fileName}
end EstablishPaths
on VisualizePath(aPath)
set saveTID to AppleScript's text item delimiters
set AppleScript's text item delimiters to {":"}
set visualPath to text items of aPath
set AppleScript's text item delimiters to {">"}
set visualPath to visualPath as text
set AppleScript's text item delimiters to saveTID
return visualPath
end VisualizePath
1 Like