On lines 1 and 2, I define some names that I will use later. My directories for this task start with Nokia, and I'm going to move imported images to Nokia-Imported.

On line 4, I start with the Folder Action handler. I want to run only when I add files to this folder. AppleScript puts the name of the folder and the list of items in this_folder and these_items.

On line 5, I log the arguments so I can inspect them later. This handler is defined in line 100. I simply convert the paths to POSIX paths (because I like Unix paths more than Mac paths) and give them as arguments to an external Perl script. Alternatively, I just call /bin/false, which is virtually a no-op. I've commented out this line, hoping that you won't have the same problem. If you do, though, try uncommenting it. If iPhoto is going crazy on you, logging out and logging back in should fix that.

On line 7, I pause the script. The first image triggers the Folder Action, but I don't want the rest of the script to run until all the files have transferred. It takes 1 or 2 seconds for each file to transfer, so I pause for the number of files multiplied by 3 just to be safe.

One line 10, I ensure that I have some files to process. As I said earlier, the Folder Action was springing into action almost randomly, and I don't want to start up iPhoto if I don't need to, especially if I am trying to work. As a side note, although the current form of the script works for me, I have also considered breaking the script into separate files and running each file as needed. That way, the main script doesn't need to do anything with iPhoto, including looking in its dictionary, if it has nothing for iPhoto to do.

On line 12, I start the actual meat of the task. I have at least one thing in these_items, and I want to process it. On line 13, I select my iPhoto library, since I store all my phone's pictures in the same library. The handler is defined on line 27 and is very simple: iPhoto Library Manager has a nice AppleScript library. Before I tell iPhoto Library Manager what to do, I quit iPhoto, just to be safe. I get a list of the libraries that iPhoto Library Manager knows about and select the one that matches my argument in library_name.

On line 15, I get the name of the folder where I will put the images after I have imported them. I could simply trash the images once I import them, but I know that the second I do, the import will have gone awry, I will have already deleted the images from my phone, and they will be nowhere. That will also be the photo set that has proof positive of intelligent extraterrestrial life or Britney Spears (which now apparently go for a couple of thousand dollars if you're the lucky photographer who can snap her picture).

This folder combines the base name, in the myLibrary property with the string in the extension property. Once I delete pictures from my phone, the numbering starts all over again, and I haven't found a way to change that. I have a lot of Image-(01).jpg files in iPhoto. If you know how to change that, please let me know.

On line 17, I import the photos, and on line 19, I delay again. I have to wait while iPhoto works its magic. Importing the images is a programmatic pain. As I said earlier, this basic operation doesn't show up in iPhoto's dictionary, so I have to use GUI Scripting.

Believe it or not, I have to simulate typing and mousing to make this work, and System Events is the application I actually talk to, which then talks to AppleScript for me. Luckily the process isn't so bad, and I even learned some iPhoto shortcuts. If I type Apple-Shift-I, I get a sheet asking me for a path from which to import photos. I just have to fill in that path.

The tricky part is knowing how to address the windows, sheets, and buttons, but Apple's UI Element Inspector makes that easy. I hover the mouse over any user interface element, and the Inspector window gives me everything I need to fill in the names and numbers of the elements.

Once I import all the files, I need to move them into a backup directory that I can recover later. On line 21, I call the move_files handler, which simply moves the files. In this case, the Finder is doing the work, so I have to tell the Finder to do these things. I often forget that AppleScript is just scripting other applications and can't do these things on its own.

That's all of the work! There is still a Bluetooth File Exchange window, though, which tells me which files I just transferred; I want to close that. I don't have a way to tell the actual application OBEXAgent to quit, so I have to use GUI Scripting again. Using UI Element Inspector, I figure out the window name and the appropriate button to press to make this window go away.

Using It

Now that I have everything in place, I can make things happen simply by transferring files from my phone. As a side note, I took the screenshots of my phone with ScreenTaker, a free application from Symbianware. Despite what the screenshot says, on the Nokia 3650 I use the pencil button with the * key to grab the screen image.

Screentaker grabs screenshots from my Nokia 3650
Screentaker grabs screenshots from my Nokia 3650
Marking the files to send
Marking the files to send
Sending the files via Bluetooth
Sending the files via Bluetooth
Sending the files via Bluetooth
Sending the files via Bluetooth

Wrapping Up

I've been using this script for about six months, and although there is a lot of room for improvement, I stopped thinking about it when it accomplished the task without messing up or being too annoying.

There are some things it could use, though. I assumed that every file in the folder would be an image file. That's OK, because iPhoto will simply ignore those in the import. I do import the entire folder, so I would have to move any other files out of the way first. For instance, I can transfer text, sound, and video files from my phone to my PowerBook, but this script is going to move them all to Nokia-Imported unless I do something with them first.

I keep thinking I should rewrite this in Perl using the Mac::Glue module. AppleScript almost does a decent job of the application control, but it doesn't make file and folder handling very easy. I can still use AppleScript and Folder Actions to invoke the future Perl script, but I'm ready to not think about this for a while. As long as it leaves me alone, I'll leave it alone.


  1 property extension : "Imported"
  2 property myLibrary : "Nokia"
  3 
  4 on adding folder items to this_folder after receiving these_items
  5 	--log_arguments(this_folder, these_items)
  6 	
  7 	delay 30 -- wait for everything to transfer from the phone
  8 	
  9 	---XXX if count of these_items is zero, do nothing
 10 	if (count of these_items) is 0 then
 11 		set count to 0 --null statement
 12 	else
 13 		select_iphoto_library(myLibrary) (* ensure we have the right library *)
 14 		
 15 		set done_folder to get_destination_folder(this_folder)
 16 		
 17 		import_photos(this_folder)
 18 		
 19 		delay 60 -- wait for the import to finish
 20 		
 21 		move_files(this_folder, done_folder)
 22 		
 23 		close_bluetooth_file_exchange()
 24 	end if
 25 end adding folder items to
 26 
 27 on select_iphoto_library(library_name)
 28 	tell application "iPhoto" to quit
 29 	tell application "iPhoto Library Manager"
 30 		set current of item 1 of (get every library whose name is library_name) to true
 31 		quit
 32 	end tell
 33 	
 34 	tell application "iPhoto" to activate
 35 end select_iphoto_library
 36 
 37 on get_destination_folder(this_folder)
 38 	tell application "Finder"
 39 		set my_parent to (container of this_folder)
 40 		set my_base to (name of this_folder) as text
 41 		
 42 		set done_folder_name to my_base & "-" & extension
 43 		set done_folder_path to (my_parent as text) & done_folder_name
 44 		
 45 		if (exists folder done_folder_path) then
 46 			set done_folder to done_folder_path
 47 		else
 48 			set done_folder to 
 49 				(make new folder in (my_parent) 
 50 					with properties {name:done_folder_name})
 51 		end if
 52 	end tell
 53 	
 54 	return done_folder
 55 end get_destination_folder
 56 
 57 on import_photos(this_folder)
 58 	tell application "System Events"
 59 		tell process "iPhoto"
 60 			keystroke "I" using {command down, shift down}
 61 			delay 1
 62 			keystroke "/" using {control down}
 63 			tell window "Import Photos"
 64 				tell sheet 1
 65 					set value of text field 1 to POSIX path of this_folder
 66 					click button 1
 67 				end tell
 68 				click button 1
 69 			end tell
 70 		end tell
 71 	end tell
 72 end import_photos
 73 
 74 on move_files(this_folder, that_folder)
 75 	tell application "Finder"
 76 		set the_files to every file in folder (this_folder as string)
 77 		repeat with i from (count of the_files) to 1 by -1
 78 			move item i of the_files to folder that_folder with replacing
 79 		end repeat
 80 	end tell
 81 end move_files
 82 
 83 on close_bluetooth_file_exchange()
 84 	tell application "System Events"
 85 		tell process "OBEXAgent" -- that's really Bluetooth File Exchange
 86 			tell window "Incoming File Transfer"
 87 				tell button 1
 88 					click
 89 				end tell
 90 			end tell
 91 		end tell
 92 	end tell
 93 end close_bluetooth_file_exchange
 94 
 95 (* ################################################# *)
 96 on log_arguments(this_folder, these_items)
 97 	set args to {posix_path(this_folder)}
 98 	repeat with this_item in these_items
 99 		set args to args & posix_path(this_item)
100 	end repeat
101 	
102 	--do shell script "show_args" & " " & join_args(args)
103 	do shell script "/bin/false"
104 end log_arguments
105 
106 on posix_path(this_alias)
107 	tell application "Finder"
108 		set this_path to POSIX path of this_alias
109 	end tell
110 	return replace_chars(this_path, " ", "\\ ")
111 end posix_path
112 
113 on join_args(this_list)
114 	set old_delimiter to AppleScript's text item delimiters
115 	set AppleScript's text item delimiters to " "
116 	set this_text to this_list as string
117 	set AppleScript's text item delimiters to old_delimiter
118 	return this_text
119 end join_args
120 
121 on replace_chars(this_text, search_string, replacement_string)
122 	set old_delimiter to AppleScript's text item delimiters
123 	set AppleScript's text item delimiters to the search_string
124 	set the item_list to every text item of this_text
125 	set AppleScript's text item delimiters to the replacement_string
126 	set this_text to the item_list as string
127 	set AppleScript's text item delimiters to old_delimiter
128 	return this_text
129 end replace_chars


Return to digitalmedia.oreilly.com