kcw | journal | 2001 << Previous Page | Next Page >>

Once again, we'll start out with the actual file, then notes. The code is getting quite large. But we'll still keep it all on one journal.

Synchronize Folder
Kevin C. Wong -- 20011026 -- v 1.0b4
Synchronize destination folder to match source folder.

Issues
May not copy Application frameworks that don't end with ".app".

20011026 1.0b4
-- Use "list folder" instead of "get items of folder alias". Then use file specifications instead of
actual files, use "info for " to get most if item info we need. These changes
work with applications and invisible items, which didn't work with "get items of folder alias".
Lots of little changes to support the new mechanism.

20011025 1.0b3
-- Bug: No date by comparison if m_copy_newer_files_only is false
-- Fix: Delete or move non-folder file before we create folder to replace it.
-- Fix: Close m_log_file when done.
-- Fix: Close m_log_file before opening it again in case it's already open.
-- Fix: Test for log write should be "class of m_log_file is file specification".
-- Fix: Start writing to log file at the end of the file, not the beginning.
-- Fix: Added line endings to log output.
-- Workaround: Skip Application files.

----------------------------------------------------------------------------

set m_source_folder to ""
set m_destination_folder to ""
set m_deleted_folder to true
set m_copy_newer_files_only to false
set m_create_index2_files to false -- !!! EXTRA !!!
set m_log_file to false

set m_quit to false

tell application "Finder"

-- Get source and destination folders
if (m_source_folder = "") then
try
set m_source_folder to choose folder with prompt "Items will be copied FROM this folder (Cancel to quit):"
on error l_error
set m_quit to true
end try
end if

if (m_quit is false) then
if (m_destination_folder = "") then
try
set m_destination_folder to choose folder with prompt
"Items will be copied TO this folder (Cancel to quit):"
on error l_error
set m_quit to true
end try
end if
end if

if (m_quit is false) then
if (m_deleted_folder is true) then
try
set m_deleted_folder to choose folder with prompt "Deleted items will be moved to this folder
(Cancel to delete items instead):"
on error l_error
set m_deleted_folder to false
end try
end if
if (m_log_file is false) then
try
set m_log_file to Â
choose file name with prompt Â
"Write synchronize log to this file (Cancel for no logging):"
default name "Synchronize Folder Log File"
on error l_error
end try
end if

-- Should we only copy newer files?
set l_record to display dialog "Copy only if source file is newer?" buttons {"yes", "no"}
if (button returned of l_record is "yes") then
set m_copy_newer_files_only to true
end if

-- !!! EXTRA !!!
-- Should we create index2.html files?
set l_record to display dialog "Copy index.html to index2.html?" buttons {"yes", "no"}
if (button returned of l_record is "yes") then
set m_create_index2_files to true
end if

-- Log start time
set l_start_time to current date
log "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
log "Backup source: " & m_source_folder as string
log "Backup destination: " & m_destination_folder as string
log "Deleted folder: " & m_deleted_folder as string
log "Copy only if source is newer: " & m_copy_newer_files_only
log "Copy index.html to index2.html: " & m_create_index2_files
log "Backup start time: " & l_start_time as string
log "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
if (class of m_log_file is file specification) then
try
close access m_log_file
on error l_error
end try

try
open for access m_log_file with write permission
on error l_error
log l_error as string
log "Disabling log file..."
set m_log_file to false
end try

write "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
" starting at (get eof m_log_file) + 1 to m_log_file
write "Backup source: " & m_source_folder & "
" to m_log_file
write "Backup destination: " & m_destination_folder & "
" to m_log_file
write "Deleted folder: " & m_deleted_folder & "
" to m_log_file
write "Copy only if source is newer: " & m_copy_newer_files_only & "
" to m_log_file
write "Copy index.html to index2.html: " & m_create_index2_files & "
" to m_log_file
write "Backup start time: " & l_start_time & "
" to m_log_file
write "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
" to m_log_file
end if

-- Get source folder length so that we can strip it out of each source folder's path
set l_source_length to (length of (m_source_folder as string)) + 1

-- Convert folder class to string (":...")
set l_destination_folder_string to m_destination_folder as string

-- Set queue items and start main repeat loop.
set l_pending_folders to {m_source_folder}

-- While items remain in queue, process each one
repeat while ((length of l_pending_folders) > 0)

-- Remove the first item from l_pending_folders
set l_current_folder to first item of l_pending_folders
if (length of l_pending_folders = 1) then
set l_pending_folders to {}
else
set l_pending_folders to rest of l_pending_folders
end if

-- Log current folder
log "Checking folder " & l_current_folder as string
if (class of m_log_file is file specification) then
write "Checking folder " & l_current_folder & "
" to m_log_file
end if

-- Determine destination folder string
set l_string to l_current_folder as string
try
set l_string to (text l_source_length through (length of l_string) of l_string)
on error l_error
-- error if this is the source folder base string
set l_string to ""
end try
set l_string to l_destination_folder_string & l_string

-- Determine destination folder.
-- Note that destination folder must exist.
set l_dest_folder to alias l_string

-- Cycle through items in the current folder and process each item
set l_items to list folder of l_current_folder

repeat with l_source_item_name in l_items

set l_source_invalid to false

try
set l_source_item to (item (l_source_item_name as string) of l_current_folder) as file specification
set l_source_info to my addIsFolderAttribute(info for l_source_item)
set l_copy_item to false
set l_dest_item to ""
set l_dest_info to ""
set l_source_app to false
on error l_error
set l_source_invalid to true
log "*****Error processing source item " & l_current_folder & l_source_item_name
log "*****" & l_error
if (class of m_log_file is file specification) then
write "*****Error processing source item " & l_current_folder & l_source_item_name & "
" to m_log_file
write "*****" & l_error & "
" to m_log_file
end if
end try

if (l_source_invalid is false) then
-- See if the item exists at the destination
try
set l_dest_item to (item (name of l_source_info) of l_dest_folder) as file specification
set l_dest_info to my addIsFolderAttribute(info for l_dest_item)
on error l_error
set l_copy_item to true
end try

-- Check to see if source and destination differ.
if (l_copy_item is false) then
if (file type of l_source_info is not equal to file type of l_dest_info) then
-- First check class of items
set l_copy_item to true
else if (m_copy_newer_files_only is true and Â
isFolder of l_source_info is false and Â
modification date of l_source_info is after modification date of l_dest_info) then
-- Second, check source item is newer than destination. Only for non-folders
set l_copy_item to true
else if (m_copy_newer_files_only is false and Â
isFolder of l_source_info is false and Â
modification date of l_source_info is not equal to modification date of l_dest_info) then
-- Or, check date of source item is different than destination. Only for non-folders
set l_copy_item to true
end if
end if

-- If we need to copy the item...
if (l_copy_item is true) then

-- Log event
log "----Copying item " & l_source_item as string
if (class of m_log_file is file specification) then
write "----Copying item " & l_source_item & "
" to m_log_file
end if

-- Copy source to destination
if (isFolder of l_source_info is false) then
-- If source is not a folder then copy it to destination
with timeout of 86400 seconds -- 1 day timeout
try
duplicate l_source_item to l_dest_folder with replacing
on error l_error
log "****" & l_error
if (class of m_log_file is file specification) then
write "****" & l_error & "
" to m_log_file
end if
end try
end timeout
else
try
-- move or delete non-folder item
if (class of l_dest_item is not string and isFolder of l_dest_info is false) then
if (class of m_deleted_folder is alias) then
move l_dest_item to m_deleted_folder with replacing
else
delete l_dest_item
end if
end if
-- If source is a folder then create destination folder
set l_folder to make new folder at l_dest_folder
set name of l_folder to (name of l_source_info)
on error l_error
log "****" & l_error
if (class of m_log_file is file specification) then
write "****" & l_error & "
" to m_log_file
end if
end try
end if

-- !!! EXTRA !!!
-- If this item is index.html, duplicate it to index2.html and copy it over.
-- We only need to create index2.html if we're copying over index.html.
-- Extra copy covers case when index2.html did not initially exist.
if (m_create_index2_files is true and name of l_source_info is "index.html") then
try
delete file "index2.html" of l_current_folder
on error l_error
end try
set l_dup to (duplicate l_source_item)
set name of l_dup to "index2.html"

log "----Copying item index2.html"
if (class of m_log_file is file specification) then
write "----Copying item index2.html
" to m_log_file
end if

with timeout of 86400 seconds -- 1 day timeout
try
duplicate file "index2.html" of l_current_folder to l_dest_folder with replacing
on error l_error
log "****" & l_error
if (class of m_log_file is file specification) then
write "****" & l_error & "
" to m_log_file
end if
end try
end timeout
end if

end if

-- If source item is a folder, add it to pending folders list
if (isFolder of l_source_info is true) then
-- We know l_source_item is a folder, not an application
-- But it is a file specification, we need queue the actual item
set l_pending_folders to l_pending_folders & {get folder l_source_item as alias}
end if
end if
end repeat

end repeat

-- Log delete start time
set l_delete_time to current date
log "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
log "Starting delete sync at " & l_delete_time as string
log "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
if (class of m_log_file is file specification) then
write "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
" to m_log_file
write "Starting delete sync at " & l_delete_time & "
" to m_log_file
write "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
" to m_log_file
end if

-- Get destination folder length so that we can strip it out of each destination folder's path
set l_destination_length to (length of (m_destination_folder as string)) + 1

-- Convert folder class to string (":...")
set l_source_folder_string to m_source_folder as string

-- Set queue items and start main repeat loop.
set l_pending_folders to {m_destination_folder}

-- While items remain in queue, process each one
repeat while ((length of l_pending_folders) > 0)

-- Remove the first item from l_pending_folders
set l_current_folder to first item of l_pending_folders
if (length of l_pending_folders = 1) then
set l_pending_folders to {}
else
set l_pending_folders to rest of l_pending_folders
end if

-- Log current folder
log "Checking folder " & l_current_folder as string
if (class of m_log_file is file specification) then
write "Checking folder " & l_current_folder & "
" to m_log_file
end if

-- Determine source folder string
set l_string to l_current_folder as string
try
set l_string to (text l_destination_length through (length of l_string) of l_string)
on error l_error
-- error if this is the destination folder base string
set l_string to ""
end try
set l_string to l_source_folder_string & l_string

-- Determine source folder.
-- Note that source folder must exist.
set l_source_folder to alias l_string

-- Cycle through items in the current folder and process each item
set l_items to list folder of l_current_folder

repeat with l_destination_item_name in l_items

set l_dest_invalid to false

try
set l_destination_item to (item (l_destination_item_name as string)
of l_current_folder) as file specification
set l_destination_info to my addIsFolderAttribute(info for l_destination_item)
set l_delete_item to false
set l_source_item to ""
set l_dest_app to false
on error l_error
set l_dest_invalid to true
log "*****Error processing destination item " & l_current_folder & l_destination_item_name
log "*****" & l_error
if (class of m_log_file is file specification) then
write "*****Error processing destination item " & l_current_folder & l_destination_item_name & "
" to m_log_file
write "*****" & l_error & "
" to m_log_file
end if
end try

if (l_dest_invalid is false) then
-- See if the item exists at the destination
try
set l_source_item to get item (name of l_destination_info) of l_source_folder
on error l_error
set l_delete_item to true
end try

-- If we need to delete the item...
if (l_delete_item is true) then

-- Log event
log "----Deleting item " & l_destination_item as string
if (class of m_log_file is file specification) then
write "----Deleting item " & l_destination_item & "
" to m_log_file
end if

-- Delete source
try
if (class of m_deleted_folder is alias) then
move l_destination_item to m_deleted_folder with replacing
else
delete l_destination_item
end if
on error l_error
log "****" & l_error
if (class of m_log_file is file specification) then
write "****" & l_error & "
" to m_log_file
end if
end try

end if

-- If source item is a folder and wasn't deleted, add it to pending folders list
if (l_delete_item is false and isFolder of l_destination_info is true) then
-- We know l_source_item is a folder, not an application
-- But it is a file specification, we need queue the actual item
set l_pending_folders to l_pending_folders & {get folder l_destination_item as alias}
end if
end if
end repeat

end repeat

-- Log end time
log "============================================================="
log "Backup source: " & m_source_folder as string
log "Backup destination: " & m_destination_folder as string
log "Deleted folder: " & m_deleted_folder as string
log "Copy only if source is newer: " & m_copy_newer_files_only
log "Copy index.html to index2.html: " & m_create_index2_files
log "Backup start time: " & l_start_time as string
log "Backup delete time: " & l_delete_time as string
log "Backup end time: " & (current date)
log "============================================================="
if (class of m_log_file is file specification) then
write "=============================================================
" to m_log_file
write "Backup source: " & m_source_folder & "
" to m_log_file
write "Backup destination: " & m_destination_folder & "
" to m_log_file
write "Deleted folder: " & m_deleted_folder & "
" to m_log_file
write "Copy only if source is newer: " & m_copy_newer_files_only & "
" to m_log_file
write "Copy index.html to index2.html: " & m_create_index2_files & "
" to m_log_file
write "Backup start time: " & l_start_time & "
" to m_log_file
write "Backup delete time: " & l_delete_time & "
" to m_log_file
write "Backup end time: " & (current date) & "
" to m_log_file
write "=============================================================
" to m_log_file
close access m_log_file
end if
end if

end tell

on addIsFolderAttribute(l_record)
if (class of l_record is record) then

-- Add isFolder attribute so we can refer to it instead of folder
set l_return to l_record & {isFolder:(folder of l_record)}

-- Add dummy file type if there is none
try
file type of l_return
on error l_error
set l_return to l_return & {file type:false}
end try

-- Check to see if this an application, in which case treat it as a non-folder
if (name of l_return ends with ".app") then
set isFolder of l_return to false
end if

return l_return
else
return l_record
end if
end addIsFolderAttribute

Copyright (c) 2001 Kevin C. Wong
Page Created: August 20, 2004
Page Last Updated: August 20, 2004