Computers: April 2003 Archives
Copying the MP3 in reality is painfully easy:
[pudge@bourque pudge]$ curl http://10.0.1.132:03689/databases/32/items/1104.mp3?session-id=20570 > file.mp3
% Total % Received % Xferd Average Speed Time Curr.
Dload Upload Total Current Left Speed
100 11.0M 100 11.0M 0 0 3071k 0 0:00:03 0:00:03 0:00:00 2818k
Now I have the MP3 as a file, streamed from iTunes via Playlist Sharing. Nothing on the server side prohibits this. I open it in iTunes and it is a regular ol' MP3. It has the exact same bytecount as the original file.
Note: I got the URL by peeking at the TCP/IP traffic with Interarchy:
010.000.001.104:49825->010.000.001.132:03689 (224 bytes)
TH_PUSH TH_ACK
GET /databases/32/items/1104.mp3?session-id=20570 HTTP/1.1
So I call the exact same thing with curl, except that I save to a file instead of opening the URL in iTunes. I can't tell a way to get the URL without doing that; this is not meant to describe a way to fetch arbitrary files from iTunes Playlist Sharing, just proving that copying files the same way you stream them is technically trivial.
And more fun: if I open that URL in iTunes instead of downloading it via curl, it shows up as a regular stream in my Library. And it DOES work with get current track AppleScript, although if I play the exact same stream via Playlist Sharing, it doesn't. Again, I maintain this behavior is either a bug or intentional breakage, not a neglected feature. And because I have proven that copying the file is so technically trivial, I believe DRM concepts are most likely involved.
Jobs says that users are not being treated like criminals, but iTunes 4 assumes that if I want to share my tracks with more than three computers, if I want to copy streamed track, if I want to burn more than 10 CDs with a "protected" track, that I am doing so illegitimately. I know they need to strike a balance with their partners, but that doesn't mean I am going to just ignore these facts.
I am, frankly, more offended by the clearly false assertion by Jobs that we are not being treated like criminals than I am by the fact that I cannot share the music or burn playlists without limit (except in the case of copying files from shared playlists).
BTW, this morning my Apple ID finally was able to work with iTunes Music Store. It wasn't site traffic, nor my credit card, since I could connect with a different Apple ID at the same time, using the same credit card. I am just happy Apple fixed whatever was wrong with my Apple ID.
Now Playing: I'm A Mother - The Pretenders (Last Of The Independents)
But iTunes keeps reporing that there is a problem and to try again later, as it has been saying with many other "pages" on the site. So I keep trying every 5-10 minutes.
My bank calls me a few minutes ago, tells me that Apple has tried to do something with the account 16 times, and wants to know if our card got stolen.
Yay, Apple.
[Update: it works now. Yay, Apple!]
Now Playing: Symphony No. 40 - Allegro assai - Wolfgang Amadeus Mozart (Symphony No. 35 & No. 40)
I took two of the scripts, one for iTunes and one for EyeTV, and combined them into one, looking first at EyeTV and then iTunes. This script is installed on my server box, which houses the EyeTV and iTunes players I normally use.
Then, I wrote a Perl script that first sees if iTunes is playing, and if not checks that script on the remote box, and if not, gets the current front application. It uses Mac::Glue for Mac OS X, which is not yet released, but will be someday
Note that iChatStatus does not use Apple events to update iChat, as iChat is not scriptable. It uses the private iChat frameworks, which are subject to change.
#!/usr/bin/perl
use strict;
use warnings;
no warnings 'utf8';
use Mac::Apps::Launch;
use Mac::Glue ':all';
use Socket;
my $itunes = new Mac::Glue 'iTunes';
if (IsRunning($itunes->{ID})) {
my $status = $itunes->get( $itunes->prop('player state') );
if ($status eq 'playing') {
my $track = $itunes->prop('current track');
my %info;
for my $prop (qw(name artist album)) {
$info{$prop} = $itunes->get( $itunes->prop($prop => $track) );
}
print "\x{266C} $info{name} - $info{artist}";
exit;
}
}
# only try this if i am at home
my $ip = inet_ntoa(inet_aton("bourque.pudge.net"));
if ($ip eq '10.0.1.177') {
my $result = `ssh sweeney osascript ~/Library/Scripts/myscript.scpt`;
if ($result =~/\w/) {
print $result;
exit;
}
}
my $system = new Mac::Glue 'System Events';
my $app = $system->prop(name => item => 1,
application_process => whose(frontmost => equals => 1)
);
print "Using: ", $system->get($app);
__END__
(Note: it may take time for the release to propagate to the various download mirrors.)
Changes:
* v1.04, Monday, April 21, 2003Posted using release by brian d foy.
Add support for saving/loading compiled AppleScripts as data in data fork
instead of resource in resource fork on Mac OS X, for compatability with
the Mac OS X Script Editor.
If I get center square as the first move, I win 50% of the time on the next move if the other user picks his first square at random: a side square means I win. Then, if he picks corner, I win 66% of the time if he picks his next square at random (it must be one of the two corners, as I select the opposite corner). So overall, for random picking of the first two moves, I win 83% of the time, and if he picks with some reason (meaning he would pick one of the two closest sides to his first corner on his second move), it is 75%.
Unfortunately, this bloke I played against picked a side square in the first game, then a corner and then a side in the second. Losing two games in a row of Tic Tac Toe must have flustered him, because in the next game he picked a corner and then another corner, but then he didn't pick to block my only winning move and I won the third game. He picked side again in the fourth, and then finally he picked two corners and blocked me in the fifth.
All this to say it must be really humiliating to lose four games in a row of Tic Tac Toe.
My first Perl program was a Tic Tac Toe CGI ...
perl -MMac::Glue -e '$i = new Mac::Glue "Interarchy"; while (<>) { chomp; $i->getwebsite(url => $_) }'Sure, you could use other command-line tools. But that's not the point.
Now Playing: I Will Return - dä (Kalhöun)
About 2/3 of my MP3s are 320 kbps @ stereo 44.1 kHz is about 1/4 the size of the original. So these still take up a good amount of space: 6929 songs (500 albums, 655 artists*) at 48GB (covering 18 days, 6 hours).
I think I am going to use the QCast Tuner to get MP3s into my main entertainment center, through the PS2 (all I really need is a good remote control ... suggestions welcome). As to the rest of the house, I'll use laptops running Mac OS X, which one way or another will copy/stream MP3s from the server (maybe with iCommune?). For the car, for now, I am going to use a laptop. I am thinking about getting a 1/8" input into the car stereo so I can plug in a laptop or MP3 player.
[* Compilations have more than one artist, and some albums cover more than one disc.]
Now Playing: Danny Boy (Version II) - Jackie Wilson (The Very Best Of Jackie Wilson)
Now Playing: Running With The Night - Lionel Richie (Grand Theft Auto: Vice City, Vol. 4: Flash FM)
So, we are moving to Arlington, Washington. We've got a new house ready and everything is moving ahead for a move in June.
(Note: it may take time for the release to propagate to the various download mirrors.)
Changes:
* v1.03, Monday, April 14, 2003Posted using release by brian d foy.
Updated tests and notes, and require more recent Mac::Carbon,
for Mac OS X support.
Added pack_pid, to target applications by PID instead of signature,
PPC location, or PSN (Mac OS X only).
Well, that's something.
Sigh.
Spam by Month
1997-04: 2 :
1997-05: 16 :
1997-06: 24 :
1997-07: 22 :
1997-08: 25 :
1997-09: 27 :
1997-10: 37 :
1997-11: 68 :
1997-12: 53 :
1998-01: 56 :
1998-02: 99 :
1998-03: 61 :
1998-04: 68 :
1998-05: 90 :
1998-06: 91 :
1998-07: 79 :
1998-08: 48 :
1998-09: 49 :
1998-10: 68 :
1998-11: 83 :
1998-12: 64 :
1999-01: 75 :
1999-02: 96 :
1999-03: 80 :
1999-04: 104 :
1999-05: 64 :
1999-06: 104 :
1999-07: 191 : #
1999-08: 182 : #
1999-09: 141 : #
1999-10: 127 : #
1999-11: 151 : #
1999-12: 151 : #
2000-01: 137 : #
2000-02: 156 : #
2000-03: 241 : ##
2000-04: 199 : #
2000-05: 217 : ##
2000-06: 284 : ##
2000-07: 237 : ##
2000-08: 238 : ##
2000-09: 333 : ###
2000-10: 359 : ###
2000-11: 357 : ###
2000-12: 294 : ##
2001-01: 332 : ###
2001-02: 325 : ###
2001-03: 346 : ###
2001-04: 480 : ####
2001-05: 528 : ####
2001-06: 497 : ####
2001-07: 628 : #####
2001-08: 886 : ########
2001-09: 741 : ######
2001-10: 781 : #######
2001-11: 817 : #######
2001-12 : 1077 : #########
2002-01 : 1219 : ###########
2002-02 : 1392 : ############
2002-03 : 2060 : ###################
2002-04 : 2598 : ########################
2002-05 : 4098 : #####################################
2002-06 : 3114 : ############################
2002-07 : 3390 : ###############################
2002-08 : 3414 : ###############################
2002-09 : 4069 : #####################################
2002-10 : 4366 : ########################################
2002-11 : 4065 : #####################################
2002-12 : 4127 : ######################################
2003-01 : 4707 : ###########################################
2003- 02 : 4205 : ######################################
2003-03 : 6049 : ################################################## ######
2003-04 : 2015 : ##################
Spam by Year
1998: 856 : ##
1999: 1466 : #####
2000: 3052 : ##########
2001: 7438 : #########################
2002 : 37912 : ################################################## ################################################## #############################
2003 : 16976 : ################################################## ########
Total Spam : 67978
killall sendmail and rm * ... argument list too long. perl -le 'opendir $x, "." or die $!; for (readdir $x) { unlink; print }'. There we go.
As noted in previous entries, I spent much of last week on Mac:: modules. I wanted to fix some outstanding bugs, make a few improvements, and prepare for Mac::Glue, which is really the whole point of my work on Mac::Carbon to begin with.
GUSI and file specifications
One of the big bugs in Mac::Carbon was in my reimplmentation of a portion of the GUSI API. GUSI (the POSIX library for MacPerl) deals with Mac/HFS file specifications (stuff like Macintosh HD:System Folder:Fonts:Geneva). Mac OS X, on the other hand, wants POSIX file specifications (/System/Library/Fonts/Geneva.dfont).
These routines from GUSI deal with something called an FSSpec. In Ye Olde Days, the Mac API took paths. But paths have various problems, including the fact that they are not unique on Mac OS (you can have multiple volumes with the same name). FSSpecs have a unique volume ID, a unique directory ID, and a filename. The volume and directory ID must refer to existing volumes and directories, to be able to get the right numbers. Because of this, FSSpecs can only point to existing files or directories, or nonexistent files or directories that are in existing directories.
FSSpecs are widely used by Mac::Carbon. Any time you see a function with FSp in it, that's an FSSpec. FSpOpenResFile opens a resource file by its FSSpec. You can make an FSSpec with FSMakeFSSpec (or a special text-encoded FSSpec with MacPerl::MakeFSSpec), but through the magic of XS and typemap, by telling the function we want an FSSpec in the XS, paths are automatically converted to FSSpecs for us. That's where routines like GUSIPath2FSp come in.
GUSIPath2FSp, in GUSI, accepts either a Mac/HFS path or the text-encoded FSSpec and returns an FSSpec. Neat, but how to get a POSIX path into an FSSpec? I decided the best way was via a new file specification type, FSRef.
The FSRef was created to work around limitations in FSSpecs (including, but not limited to, the filename length limits). So in Mac OS X you can see functions like FSOpenResFile, the same thing only different. A simple routine called FSPathMakeRef converts a POSIX path to an FSRef, and FSGetCatalogInfo can fill out an FSSpec for us.
Like an FSSpec, it should refer to an existing file, but unlike an FSSpec, it cannot refer to a nonexistent file in an existing directory. So how can I use FSRefs to create FSSpecs for functions like FSpCreateResFile? The file does not yet exist, so we can't get an FSRef.
After punting on a solution for months, it finally came to me. If creating the FSRef fails with a file not found error, chop off the basename and try to create an FSRef for the directory. If that works, get the FSSpec for that, and then use that, plus the basename, to create the required FSSpec (GUSIFSpDown(&spec, name) handles this nicely).
So that problem was thus solved. I then came to another problem: opening resource files.
Resource forks
In Mac OS X, resource forks are often not used, because of their many problems on non-Mac OS systems. FSpCreateResFile and FSpOpenResFile only work on resource forks. But FSCreateResourceFile and FSOpenResourceFile can open the specified named fork (data by default). So I added a few lines to the typemap for input and output of FSRefs, added the functions to Resources.xs, and now it works.
Adding those functions took some effort
I still don't know if the values for Unicode there are reasonable. This is only going to become more of a problem with other APIs. What about Japanese programmers? Ugh. Me hate-um Unicode.
In the long run, I might add a lot of FS equivalents to the FSp routines, just because they are less limited. But for now, I am just adding the functions that have the additional funcitonality needed, such as for opening resource files from data forks. Another question is whether to have these functions work on Mac OS X too (difficult without breaking compatibility with some older Mac OSes), or to have another function sitting on top of the other two, picking the right one for the platform (MPOpenResFile?).
Application paths
Another needed functionality was to get the path to an application, at least by creator ID. In Mac OS, FSpGetAPPL (from MoreFiles) did the trick. It did a lookup for the app in the Desktop database, and if that failed, searched the disk for the correct app. Well, that method is really slow on Mac OS X, with no Desktop database, and often returns incorrect results, as it knows only about files of type APPL, and knows nothing of bundles.
LaunchServices has a function LSFindApplicationForInfo which can return the path given either its creator ID ("R*ch"), bundle ID ("com.barebones.bbedit"), or name ("BBEdit"), or any combination of the three. So this is now added to Mac::Carbon too, in Mac::Processes, and the tied hash %Application (in Mac::MoreFiles) now uses it for Mac OS X (so $Application{"R*ch"} properly returns (for me)
And more on types: LSFindApplicationForInfo returns an FSRef, and XS handles it transparently, just as it handles FSRefs on input for FSCreateResourceFile.
Internet Config
One of the more useful modules in the Mac:: arsenal was always Mac::InternetConfig. $InternetConfig{kICEmail()} will get your email address (as specified in your System Preferences on Mac OS X). GetURL($url) will open the given URL in the appropriate program. $InternetConfigMap{".pdf"} will return an object with accessors describing the extension: it's Mac OS file type, creator type, app name, MIME type, etc.
There's a ton of information stored in Internet Config, and now it's available from Mac::Carbon. You can even set your IC prefs with it (something Mac OS X doesn't come with a GUI for).
Porting it posed a problem, one reminscent of the porting of Mac::AppleEvents. In both cases, a significant portion of the API was taken from a third-party library (AEGizmos in the case of AppleEvents), and was sucked into the official Carbon API, where Apple promptly changed the names.
In Mac::AppleEvents it mostly meant changing AEStream_Close to AEStreamClose. But also, they now took AEStreamRef instead of AEStream, so I have a bunch of things looking like this:
#define AEStream AEStreamRefNow it works on both Mac OS and Carbon.
#define AEStream_Close(stream, desc) \
AEStreamClose(*stream, desc)
#define AEStream_CloseDesc(stream) \
AEStreamCloseDesc(*stream)
Mac::InternetConfig was similar, except that the things that changed were fields in ICMapEntry, file_type to fileType, etc. But these are accessed by the Perl programmer, and I don't want to just change them, else it will break older code. So I can't just use #define file_type fileType
The way Matthias Neeracher (MacPerl's author, and author of most of the Mac:: modules) designed the XS code was that to access objects like ICMapEntry, a function would be called, which would return the proper field, based on an index number. So $entry->file_type calls ICMapEntry::file_type which calls XS_Mac__InternetConfig_ICMapEntry, with ix set to 1 (via dXSI32). The function returns the value corresponding to 1, which is the file type.
All of that is set up during xsubpp, before the #define is handled. So even with the define, I still get this:
cv = newXS("ICMapEntry::file_type", XS_Mac__InternetConfig_ICMapEntry, file);This would be a problem if I really DID want fileType in Perl-space. But I don't. So those few little defines do exactly what I want.
XSANY.any_i32 = 1;
Pascal
Pascal strings, as noted, are used in various places in Mac::Carbon, including FSSpecs. In two new places (Mac::InternetConfig, and in a bugfix for Mac::Files), there is a call for using the escape sequence "\p", which creates a Pascal string. A relatively recent addition to the Mac OS X gcc is -fpascal-strings. It is much appreciated.
What's Next
I'll fix bugs as they crop up in Mac::Carbon, and work to get the porting of Mac::Apps::Launch and Mac::AppleEvents::Simple finished up. I've still got more tests to do for Mac::Carbon (volunteers welcome). And Mac::Glue is coming along nicely (though I am having some issues with POSIX paths
- Ability to open resource files in data forks with FSOpenResourceFile()
- Ability to find paths to applications based on bundle ID, creator type, or app name with LSFindApplicationForInfo()
- Addition of Mac::InternetConfig, to look up and set IC information, Get URLs (e.g., GetURL($url) will open the URL in your app of choice), etc.
Some important bugfixes are included as well, including the ability of FSp* routines to accept pathnames for files that don't exist. A full test suite for Mac::Files was added as well (with more extensions to go).
I'll be posting some journal entries about this release of Mac::Carbon at http://use.perl.org/~pudge/journal this week, time permitting.
The ports of Mac::Apps::Launch, Mac::AppleEvents::Simple, and -- yes! -- Mac::Glue are basically done, with this release of Mac::Carbon, and are being prepared for release.
(Note: it may take time for the release to propagate to the various download mirrors.)
Changes:
* v0.50, 6 April 2003Posted using release by brian d foy.
Mac::InternetConfig added
GUSI fixes
- Fix bug with GUSIFSp* routines not accepting paths where files
do not exist
- Make GUSIFSpDown work
- Add new "GUSIFS2FullPath" and "GUSIPath2FS" (for lack of better
names) for dealing with FSRefs
- Add FSRef to typemap
Launching apps
- Add LSFindApplicationForInfo to Mac::Processes for finding
applications on Mac OS X (by creator, bundle ID, or name)
- Make %Mac::MoreFiles::Application find paths to apps using
LSFindApplicationForInfo for Mac OS X
Open resource files from data forks
- Add FSCreateResourceFile and FSOpenResourceFile to
Mac::Resources for creating/opening resource files from data
fork instead of resource fork
- Remove resource/data fork workaround from Sound.t
More constants
- Add AERegistry.h type* constants to Mac::AppleEvents
- Add file access permissions constants to Mac::Files
- Add Finder flag constants to Mac::Files
Add -fpascal-strings to ccflags, for compatibility with some of the
GUSI API (tested with gcc2 and gcc3 under Mac OS X 10.2).
Fix segfault for NULL descriptor in new AEDesc
Add lots of tests for Mac::Files
Fix the two-arg form of FSpGetCatInfo(FILE, INDEX)
Add option for MacPerl::{Ask,Answer,Pick} to be text-based on Mac OS X
Note, to those of you new to Mac:: modules, that GetURL() automatically opens the URL in whichever browser you have chosen as your default.
The Mac::Glue stuff is pretty straightforward.
#!/usr/bin/perlAlso worthy of note for those of you into Mac::Glue is that the normal form of object is CLASS => VALUE, such as message => $n to specify the nth message; but when we move the message to the trash, we no longer can go by the index, since it changes; so instead we use the message's unique ID, which is what the formUniqueID is all about (currently, Mac::Glue can't easily distinguish between an index and a unique ID that is an integer, so we have to help it out).
use strict;
use warnings;
use Mac::Glue ':all';
use Mac::InternetConfig 'GetURL';
my $eudora = new Mac::Glue 'Eudora';
my $box = $eudora->obj(mailbox => ' use perl');
my $count = $eudora->count($box, 'each' => 'messages');
my $trash = location(end => $eudora->obj(mailbox => "Trash"));
my %users;
# loop over messages in mailbox
for my $i (1.. $count) {
my $msg = $eudora->obj(message => $i, $box);
# check status
my $status = $eudora->get( $eudora->prop(status => $msg) );
next unless $status eq 'unread';
# check subject
my $subject = $eudora->get( $eudora->prop(subject => $msg) );
next unless $subject =~/^\[use Perl\] New Journal Entry by (.+?), "/;
my $user = $1;
# many Mac apps still use \015 as newline internally, as does AppleScript
(my $body = $eudora->get( $eudora->prop(body => $msg) )) =~ s/\015/\n/g;
# get URL for journal entry out of the body
my($url) = $body =~ m|^ (http://use.perl.org/~.+?/journal/\d+)$|m;
# save id and url for later
my $id = $eudora->get( $eudora->prop(id => $msg) );
push @{$users{$user}}, [ $id, $url ];
}
for my $user (sort { lc $a cmp lc $b } keys %users) {
# print journal entries to STDOUT
my @urls = sort map { $_->[1] } @{$users{$user}};
printf "%s:\n %s\n", $user, join "\n ", @urls;
# open each journal entry in the browser
for my $url (reverse @urls) {
GetURL($url);
}
# move messages to the trash
for my $id (map { $_->[0] } @{$users{$user}}) {
my $nid = $eudora->obj(message => obj_form(formUniqueID, typeInteger, $id), $box);
$eudora->move($nid, to => $trash);
}
<>; # wait for NL
}
__END__
- Mac::InternetConfig added
- GUSI-emulation API able to handle FSSpecs for files that don't exist
- Find apps with LaunchServices by creator, bundle ID, or name, instead of FSpDTGetAPPL
- Open resource files in data fork, not just resource fork
- Support for Mac::AppleEvents::Simple and Mac::Glue