hipSync.scpt (AppleScript)

Sunday, May 25, 2003

Prototype sync script between Palm Desktop and iPod.
(* hipSync 1.0d2 / Nicholas Riley <hipsync@sabi.net> / 25 May 2003
This script is in the public domain.
*)

property this_iPod : ""
property events_file : "Palm Desktop Events"
property todos_file : "Palm Desktop To Dos"
property addresses_file : "Palm Desktop Addresses"
property section_length : 3950
property crlf : return & "
"

property ical_header : "BEGIN:VCALENDAR" & crlf & ¬
   "
CALSCALE:GREGORIAN" & crlf & ¬
   "
METHOD:PUBLISH" & crlf & ¬
   "
VERSION:2.0" & crlf & ¬
   "
PRODID:hipSync" & crlf

on run
   
try
      
if this_iPod is "" then select_iPod()
      
sync_memos()
      
sync_todos()
      
sync_events()
      
sync_addresses()
      
sync_music()
   
on error error_message number the error_number
      
if the error_number is not -128 then
         
display dialog error_message buttons {"OK"} default button 1
      
end if
   
end try
end run

--
•••••••••••••••••••••••••••••••• Sync Music ••••••••••••••••••••••••••••••••

on sync_music()
   
local playlists_folder, playlist_name, iPod_name
   
local iPod_source, iPod_playlist, iPod_library, iPod_track
   
local library_source, local_library, local_playlist, local_track, local_tracks, db_id
   
   
tell application "Finder"
      
set iPod_name to this_iPod's name
      
set playlists_folder to my get_folder(my get_folder(home, "Music"), ¬
         "
Playlists for iPod “" & iPod_name & "")
   
end tell
   
tell application "iTunes"
      
set library_source to source 1 whose kind is library
      
set local_library to library playlist 1 of library_source
      
set local_playlist to my get_playlist(the library_source, "hipSync To Upload")
      
set iPod_source to source 1 whose name is iPod_name and kind is iPod
      
set iPod_library to library playlist 1 of iPod_source
      
repeat with playlist_name in list folder playlists_folder without invisibles
         
set playlist_name to playlist_name as string
         
set iPod_playlist to my get_playlist(iPod_source, playlist_name)
         
repeat with local_track in tracks of local_playlist
            
delete (tracks of local_library whose database ID = (local_track's database ID))
         
end repeat
         
add alias ((playlists_folder as string) & playlist_name) to local_playlist
         
repeat with iPod_track in iPod_playlist's tracks
            
set local_tracks to ((local_playlist's tracks) whose name = (iPod_track's name as string) ¬
               
and size = (iPod_track's size as real))
            
if length of local_tracks is 0 then
               
set db_id to iPod_track's database ID
               
delete (tracks of iPod_library whose database ID = db_id)
            
else
               
set db_id to database ID of item 1 of local_tracks
               
delete (tracks of local_library whose database ID = db_id)
            
end if
         
end repeat
         
if size of local_playlist > free space of iPod_source then
            
error "Playlist “" & playlist_name & "” will not fit on your iPod. To upload this playlist, delete " & ¬
               (
round ((size of local_playlist) - (free space of iPod_source)) / 1024 / 1024 * 100 rounding up / 100 & ¬
                  "
MB of music and/or files from your iPod.")
         
end if
         
duplicate local_playlist's tracks to iPod_playlist
      
end repeat
      
delete local_playlist
   
end tell
end sync_music

on get_playlist(the_source, playlist_name)
   
local the_playlist
   
tell application "iTunes"
      
set the_playlist to (playlists of the_source whose name is playlist_name)
      
if the_playlist's length is 1 then
         
set the_playlist to item 1 of the_playlist
      
else
         
set the_playlist to (make user playlist at the_source with properties {name:playlist_name})
      
end if
   
end tell
end get_playlist

--
•••••••••••••••••••••••••••••••• Sync Addresses ••••••••••••••••••••••••••••••••

on sync_addresses()
   
local contacts_folder
   
set contacts_folder to get_iPod_folder("Contacts")
   
   
try
      
tell application "Finder" to delete file addresses_file of contacts_folder
   
end try
   
   
do_export()
   
tell application "System Events" to tell window "Export: Palm Desktop" of process "Palm Desktop"
      
keystroke "a" with command down
      
keystroke POSIX path of contacts_folder & (ASCII character 13) & addresses_file
      
tell pop up button 3
         
click
         
tell menu 1 to click menu item "Addresses"
      
end tell
      
tell pop up button 1
         
click
         
tell menu 1 to click menu item "vCard"
      
end tell
      
keystroke (ASCII character 13)
   
end tell
end sync_addresses


--
•••••••••••••••••••••••••••••••• Sync Events ••••••••••••••••••••••••••••••••

on sync_events()
   
local calendars_folder
   
set calendars_folder to get_iPod_folder("Calendars")
   
try
      
tell application "Finder" to delete file events_file of calendars_folder
   
end try
   
do_export()
   
tell application "System Events" to tell window "Export: Palm Desktop" of process "Palm Desktop"
      
keystroke "a" with command down
      
keystroke POSIX path of calendars_folder & (ASCII character 13) & events_file
      
if value of pop up button 3 is not "Date Book" then error "“Date Book” not selected"
      
tell pop up button 1
         
click
         
tell menu 1 to click menu item "vCal"
      
end tell
      
keystroke (ASCII character 13)
   
end tell
end sync_events

on do_export()
   
tell application "Palm Desktop"
      
activate
   
end tell
   
tell application "System Events"
      
with timeout of 10 seconds
         
tell menu bar 1 of process "Palm Desktop" to click menu item "Export…" of menu "File"
      
end timeout
   
end tell
end do_export

--
•••••••••••••••••••••••••••••••• Sync To Dos ••••••••••••••••••••••••••••••••

on sync_todos()
   
local target_file, open_target_file, write_result, the_todo, todo_properties, ical_data
   
set target_file to get_iPod_folder("Calendars") & todos_file
   
try
      
set the target_file to the target_file as text
      
set the open_target_file to ¬
         
open for access file target_file with write permission
      --
XXX want no creator instead...
      
tell application "Finder" to set properties of file target_file to {file type:"iCal", creator type:"iCal"}
      
set eof of the open_target_file to 0
      
write ical_header to the open_target_file starting at eof
      
tell application "Palm Desktop"
         
repeat with the_todo in to dos
            
set todo_properties to the_todo's properties
            
set todo_category to my get_category(todo_properties's primary category)
            --
SHOULD fold at 75 characters, but I'm lazy (RFC 2445 §4.1)
            
set ical_data to "BEGIN:VTODO" & crlf & "SUMMARY:" & todo_properties's title & crlf
            
set ical_data to ical_data & ¬
               
my get_iCal_categories(todo_properties's primary category, todo_properties's secondary category)
            
if todo_properties's completed then
               
set ical_data to ical_data & "STATUS:COMPLETED" & crlf
               
set ical_data to ical_data & my get_iCal_datetime("COMPLETED", todo_properties's completion date)
            
end if
            
if todo_properties's private then set ical_data to ical_data & "CLASS:PRIVATE" & crlf
            
set ical_data to ical_data & my get_iCal_priority(todo_properties's priority)
            
set ical_data to ical_data & my get_iCal_datetime("DUE", todo_properties's due date)
            --
reminder value is in days
            
if todo_properties's reminder is not missing value then ¬
               
set ical_data to ical_data & "BEGIN:VALARM" & crlf & ¬
                  "
TRIGGER:-P" & todo_properties's reminder & "D" & crlf & ¬
                  "
ACTION:DISPLAY" & crlf & ¬
                  "
DESCRIPTION:" & todo_properties's title & crlf & ¬
                  "
END:VALARM" & crlf
            --
XXX handle repeating to-dos
            
set ical_data to ical_data & "END:VTODO" & crlf
            
my write_data_to_file(ical_data, open_target_file)
         
end repeat
      
end tell
      
write "END:VCALENDAR" & crlf to the open_target_file starting at eof
      
close access the open_target_file
   
on error error_message number error_number
      
try
         
close access file target_file
      
end try
      
error "There was a problem writing to dos to a file on the iPod: " & error_message & " (" & error_number & ")"
   
end try
end sync_todos

on write_data_to_file(the_data, open_file)
   
write the_data to open_file starting at eof
end write_data_to_file

--
•••••••••••••••••••••••••••••••• Sync Memos ••••••••••••••••••••••••••••••••

on sync_memos()
   
global notes_folder
   
local the_memo, memo_properties, memo_category
   
set notes_folder to get_iPod_folder("Notes")
   
tell application "Palm Desktop"
      
repeat with the_memo in memos
         
set memo_properties to the_memo's properties
         
set memo_category to my get_category(memo_properties's primary category)
         
my add_note(my fix_title(memo_properties's title), memo_properties's contents, memo_category)
      
end repeat
   
end tell
end sync_memos

on fix_title(the_title)
   
return my replace_chars(the_title, "Handheld Note: ", "")
end fix_title

on add_note(this_name, this_data, this_category)
   
global notes_folder
   
set the data_length to the length of this_data
   
set this_name to make_filename(this_name)
   
if the data_length is less than 4000 then
      
set the target_file to get_folder(notes_folder, make_filename(this_category)) & this_name
      
set the write_result to my write_to_file(this_data, target_file, false)
      
if the write_result is false then
         
error "There was a problem writing the note “" & this_name & "” to the iPod."
      
end if
   
else
      
set the file_count to round (data_length / section_length) rounding up
      
repeat with i from 1 to the file_count
         
set the file_number to my add_leading_zeros(i, 1)
         
set the next_number to my add_leading_zeros((i + 1), 1)
         
set the filename to this_name & "." & file_number
         
set the nextname to this_name & "." & next_number
         
if i is 1 then -- first list item
            
set the section_text to text from character 1 to section_length of this_data & "..." & return & return & tab & "[[<a href=\"" & nextname & "\">NEXT PAGE</a>]]"
            
set the target_file to (notes_folder as string) & this_name
         
else if i is the file_count then -- last list item
            
set the section_text to "[[PAGE " & (i as string) & " of " & (file_count as string) & "]]" & return & return & "..." & (text from character (section_length * (i - 1)) to -1 of this_data)
            
set the target_file to (notes_folder as string) & filename
         
else -- other list items
            
set the section_text to "[[PAGE " & (i as string) & " of " & (file_count as string) & "]]" & return & return & "..." & (text from character (section_length * (i - 1)) to (section_length * i) of this_data) & "..." & return & return & tab & "[[<a href=\"" & nextname & "\">NEXT PAGE</a>]]"
            
set the target_file to (notes_folder as string) & filename
         
end if
         --
set the target_file to (notes_folder as string) & filename
         
set the write_result to my write_to_file(section_text, target_file, false)
         
if the write_result is false then error "There was a problem writing note file “" & filename & "” to the iPod."
      
end repeat
   
end if
   
end add_note

--
•••••••••••••••••••••••••••••••• Find iPod ••••••••••••••••••••••••••••••••

on select_iPod()
   --
check for iPods
   
set the mounted_iPods to my locate_iPods()
   --
check for iPod count
   
if the mounted_iPods is {} then
      
error "No iPod is connected to this computer."
   
else if the (count of the mounted_iPods) is greater than 1 then
      --
choose iPod
      
set the ipod_names to {}
      
repeat with i from 1 to the count of the mounted_iPods
         
set this_iPod to item i of the mounted_iPods
         
tell application "Finder"
            
set the end of the ipod_names to the name of this_iPod
         
end tell
      
end repeat
      
set this_name to (choose from list ipod_names with prompt "Pick the iPod to use:") as string
      
if this_name is "false" then error number -128
      
repeat with i from 1 to the count of the ipod_names
         
if item i of the ipod_names is this_name then
            
set this_iPod to item i of the mounted_iPods
            
exit repeat
         
end if
      
end repeat
   
else
      
set this_iPod to item 1 of the mounted_iPods
   
end if
   
   
return this_iPod
end select_iPod

on locate_iPods()
   
set mounted_iPods to {}
   
repeat with this_disk in list disks
      
set these_items to list folder (this_disk as alias)
      
if "iPod_Control" is in these_items then
         
set the end of the mounted_iPods to (this_disk as alias)
      
end if
   
end repeat
   
return mounted_iPods
end locate_iPods

--
•••••••••••••••••••••••••••••••• File utilities ••••••••••••••••••••••••••••••••

on get_folder(parent_item, folder_name)
   
tell application "Finder"
      
if not (exists folder folder_name of parent_item) then
         
make new folder at parent_item with properties {name:folder_name}
      
end if
      
return (folder folder_name of parent_item) as alias
   
end tell
end get_folder

on get_iPod_folder(sub_name)
   
local iPod_folder
   
set iPod_folder to get_folder(get_folder(this_iPod, sub_name), "Palm Desktop")
   
tell application "Finder" to delete every item of iPod_folder
   
return iPod_folder
end get_iPod_folder

on write_to_file(this_data, target_file, append_data)
   
try
      
set the target_file to the target_file as text
      
set the open_target_file to ¬
         
open for access file target_file with write permission
      
if append_data is false then ¬
         
set eof of the open_target_file to 0
      
write this_data to the open_target_file starting at eof
      
close access the open_target_file
      
return true
   
on error
      
try
         
close access file target_file
      
end try
      
return false
   
end try
end write_to_file

--
•••••••••••••••••••••••••••••••• String utilities ••••••••••••••••••••••••••••••••

on get_category(cat)
   
if cat is not missing value then
      
return cat's name
   
else
      
return "«uncategorized»"
   
end if
end get_category

on get_iCal_categories(cat1, cat2)
   
if cat1 is missing value then
      
if cat2 is missing value then return ""
      
return "CATEGORIES:" & cat2's name & crlf
   
end if
   
if cat2 is missing value then return "CATEGORIES:" & cat1's name & crlf
   
return "CATEGORIES:" & cat1's title & "," & cat2's name & crlf
end get_iCal_categories

--
XXX should cut off >255 chars, need to convert to Unicode first
on make_filename(str)
   --
XXX until Apple fixes bug in 'open for access', need to replace slashes too
   
return my replace_chars(my replace_chars(str, "/", "-"), ":", "-")
end make_filename

on replace_chars(this_text, search_string, replacement_string)
   
set AppleScript's text item delimiters