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.
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 |
![]() Marking the files to send |
![]() Sending the files via Bluetooth |
![]() Sending the files via Bluetooth |
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
brian d foy is a Perl trainer for Stonehenge Consulting Services and is the publisher of The Perl Review.
Return to digitalmedia.oreilly.com
Showing messages 1 through 7 of 7.
on adding folder items to T630 after receiving images
tell application "Finder"
activate
select item 1 of images
end tell
tell application "System Events"
keystroke "c" using command down
tell application "iPhoto"
activate
select album "T630"
end tell
keystroke "v" using command down
end tell
end adding folder items to
Yep. FA can detect a batch transfer performed by the Finder, for example, when the user selects a group of files in the Finder and drag-n-drops them into the FA-enabled folder as a single operation. In your case though it's just seeing a sequence of separate file copy operations, so it sends a separate 'adding folder items' event for each. It has no way of knowing otherwise; it just does what it's told.
"""So, my configured Folder Action can fire up seemingly at random for as long as I am logged in, even though the files have already been imported and moved. If anyone has any idea what is going on here, please let me know."""
It's not random. Put simply, your problem is that batch-processing and event-driven programming tend to mix like oil and water. :p
FA is simply working its way through each of the 'adding folder items' events that have been added to the event queue while your script was still working on the first one. If your script was more polite, it would operate only on the file(s) specified in the event's 'after receiving' parameter. Instead, you're using FAs merely to trigger a batch processing script the first time an 'adding folder items' event is received, telling it to wait an arbitrary length of time before going off to process an entire folderful of files that have [hopefully] appeared during that wait. By the time FA finishes processing the first event and starts on the second one, you've already finished the entire job. How rude!
A couple options:
1. What you really want is to trigger the batch processing script just once, immediately after BFE has finished doing its job. That's usually easy enough to do when you're controlling an application yourself via Apple events, since well-mannered apps don't return until they've finished the operation that the event triggered. In situations where your script isn't in charge of an application, you're in a bit of a pickle though. It's extremely rare for an application to offer some kind of notification service [1] that would allow you to register your script to be triggered by, say, a 'did finish synchronisation' event. I don't imagine for a moment that BFE falls into that category.
2. Next nearest thing, as you've already found, is to attach FAs to BFE's download folder. The clean solution would be to rewrite your script so it only operates upon the file(s) passed to it via the 'after receiving' parameter. The hacky solution would be to check the download folder actually contains some files before going any further (I think this is what you meant to do, but you had it wrong):
This will ensure that the second and subsquent events are quickly skipped over if there's nothing left to do. Just say a little prayer and hope that your delays are set long enough not to screw things up.
3. Write yourself a daemon that regularly polls the download folder. If it notices new files appearing in the folder, have it keep polling until it's confident they've stopped appearing, then trigger your batch-processing script. If you don't want the daemon running permanently, use a folder action to launch it and have it shut itself down when done. You could use a stay-open AppleScript applet for this if you want; just put your polling code in its idle handler and set the delay between idle events to a reasonable amount.
"""I stumbled upon a curious fix. I wanted to look at the arguments that the Finder gave to each invocation of the script, which turns out to be a lot of work in AppleScript. I settled on a Perl script that I called with do shell script. The script reports the complete list of files I transferred, which is what I wanted to inspect. I wanted to see what Folder Actions thought I was adding to the folder during these random invocations.
As long as that little debugging code was in there, the random invocation problem disappears."""
Sounds like a Heisenbug. It suggests there's a bug in your debug code that causes it to error under certain conditions, those conditions occurring when your files have already been processed and moved by the time your script is notified of their arrival. Because your logging code appears before your importing code, if an error occurs in the former the latter never gets a chance to execute. System Events has no way of knowing how to deal with any errors that occur in your attached script so just silently squelches them, leaving you none the wiser as to why your import code didn't run. This is why during development and testing it's always wise to wrap a 'try | on error e | display error e | end try' block around the code in each event handler so you'll immediately know if something goes wrong.
HTH
has [http://freespace.virgin.net/hamish.sanderson/appscript.html]
[1] System Events and Mail are a couple of examples with their Folder Actions and mail filter rules respectively, and even they are incredibly limited in what's on offer. Certainly nothing to write home about. It's such a sorry situation that there's now a third-party application available that enables you to attach scripts to applications' GUI objects to intercept events sent to them; an incredible kludge, but that's how desperate folk are. [/rant]