ljr/ljcom/htdocs/code/mylj/ljsync-0.1.pl.txt

288 lines
7.3 KiB
Perl
Executable File

#!/usr/bin/perl
# -*-perl-*-
#
# LiveJournal Sync Client
# (see http://www.livejournal.com/)
# (protocol information at http://www.livejournal.com/developer/)
#
# Brad Fitzpatrick
# bradfitz@bradfitz.com
#
# For this to work, make a ~/.livejournal.rc file like:
#
# user: username
# password: password
# syncdir: /path/to/syncdir/
#
# use_proxy: 1 (optional)
# proxy_host: my.proxy.com (optional)
# proxy_port: 81 (optional)
#
# And, if not using livejournal.com's servers:
#
# server_host: ...
# server_port: ...
# server_uri: ...
use strict;
my $VERSION = "0.1";
my $SERVER_HOST = "www.livejournal.com";
my $SERVER_PORT = 80;
my $SERVER_URI = "/cgi-bin/log.cgi";
##########################################################
use URI::Escape;
use LWP::UserAgent;
# load the ~/.livejournal.rc file
my %rc = ();
load_rc_file(\%rc);
$rc{'server_host'} ||= $SERVER_HOST;
$rc{'server_port'} ||= $SERVER_PORT;
$rc{'server_uri'} ||= $SERVER_URI;
unless ($rc{'user'}) {
die "Error: No username (user) specified in ~/.livejournal.rc\n";
}
unless ($rc{'password'}) {
die "Error: No password specified in ~/.livejournal.rc\n";
}
unless ($rc{'syncdir'}) {
die "Error: No sync directory specified in ~/.livejournal.rc\n";
}
unless (-d $rc{'syncdir'}) {
die "Sync dir does not exist ($rc{'syncdir'})\n";
}
unless (-w $rc{'syncdir'}) {
die "Sync dir is not writable ($rc{'syncdir'})\n";
}
print "Starting sync.\n";
my %last;
if (open (LAST, "$rc{'syncdir'}/lastsyncs.dat")) {
print "lastsync file opened.\n";
while (<LAST>) {
chomp;
if (/^(\w+):\s*(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)/) {
$last{$1} = $2;
}
}
close LAST;
} else {
print "no lastsync file found (first use?)\n";
}
my $ua = make_lj_agent();
my $get_sync_items = 1;
while ($get_sync_items)
{
print "Getting some sync items...\n";
print " last{L} = $last{'L'}\n";
my %ljres = lj_request($ua, {
"mode" => "syncitems",
"user" => $rc{'user'},
"password" => $rc{'password'},
"lastsync" => $last{'L'},
});
if ($ljres{'success'} eq "OK")
{
print "Got $ljres{'sync_count'} sync items of $ljres{'sync_total'} total.\n";
if ($ljres{'sync_count'} == $ljres{'sync_total'}) {
print "Done getting sync items!\n";
$get_sync_items = 0;
}
my %syncitem;
for (my $i=1; $i<=$ljres{'sync_count'}; $i++) {
next unless ($ljres{"sync_${i}_item"} =~ /^(.+?)-(\d+)/);
my $type = $1;
my $id = $2;
my $time = $ljres{"sync_${i}_time"};
$syncitem{$type}->{$id} = $time;
}
# currently there's only type "L" (journal entries), but
# this later could be "C"omment or "T"odo, etc..
foreach my $type (keys %syncitem)
{
print "Syncing type: $type\n";
# journal entries
if ($type eq "L") {
# rebuild journal entry here ($entry{$itemid}->{$key} = $val)
# before writing it to disk.
my %entry;
# keep track of the most recent journal entry saved,
# but only use in the case when there are no left to
# find the minimum from. (for sending "lastsync")
my $maxtime = "0000-00-00 00:00:00";
# we want to keep fetching more items until no more of
# this type exist (we'll delete the key after we write
# it to disk)
while (keys %{$syncitem{$type}})
{
print "Getting a batch of log entries...\n";
print " keys = ", scalar(keys %{$syncitem{$type}}), "\n";
print " lastsync = $last{'L'}\n";
my %sres = lj_request($ua, {
"mode" => "getevents",
"selecttype" => "syncitems",
"user" => $rc{'user'},
"password" => $rc{'password'},
"lastsync" => $last{'L'},
});
if ($sres{"success"} ne "OK") {
die "getevents failed: $sres{'errmsg'}\n";
}
# these next two loops reconstruct the journal entry
# from the response
for (my $i=1; $i<=$sres{'events_count'}; $i++) {
my $itemid = $sres{"events_${i}_itemid"};
$entry{$itemid} = {
'itemid' => $itemid,
'eventtime' => $sres{"events_${i}_eventtime"},
'event' => $sres{"events_${i}_event"},
'security' => $sres{"events_${i}_security"},
'allowmask' => $sres{"events_${i}_allowmask"},
};
}
for (my $i=1; $i<=$sres{'prop_count'}; $i++) {
my $itemid = $sres{"prop_${i}_itemid"};
my $prop = $sres{"prop_${i}_name"};
my $value = $sres{"prop_${i}_value"};
$entry{$itemid}->{"prop_$prop"} = $value;
}
# now, write each journal entry to disk, then erase
# its from the $syncitem{'L'} hash
print "Writing journal entries to disk...\n";
print "Wrote: ";
foreach my $itemid (sort { $a <=> $b } keys %entry)
{
if (open(E, ">$rc{'syncdir'}/$itemid.entry")) {
foreach (sort keys %{$entry{$itemid}}) {
print E "$_: $entry{$itemid}->{$_}\n";
}
close E;
print "$itemid, ";
# increment maxtime if this sync item was newer.
if ($syncitem{'L'}->{$itemid} gt $maxtime) {
$maxtime = $syncitem{'L'}->{$itemid};
}
delete $entry{$itemid};
delete $syncitem{'L'}->{$itemid};
} else {
die "Couldn't open $itemid.entry for write!\n";
}
}
print "\n";
# now that's stuff written to disk, we need to update
# the $last{'L'} time
print "Find new LastL...\n";
if (keys %{$syncitem{'L'}}) {
# find the earliest that isn't yet synced
my @times = sort values %{$syncitem{'L'}};
$last{'L'} = $times[0];
print "New LastL (keys) = $last{'L'}\n";
} else {
$last{'L'} = $maxtime; # FIXME: subtract a second
# in case two entries were on
# same second.
print "New LastL (maxtime) = $last{'L'}\n";
}
if (open (LAST, ">>$rc{'syncdir'}/lastsyncs.dat")) {
print LAST "L: $last{'L'}\n";
close LAST;
} else {
die "Couldn't append lastsyncs.dat file.\n";
}
}
}
}
}
else
{
die "Error getting sync items: $ljres{'errmsg'}\n";
}
}
print "DONE!\n";
sub load_rc_file
{
my $rcref = shift;
my $file = "$ENV{'HOME'}/.livejournal.rc";
return unless (-e $file);
open (RC, $file);
while (<RC>)
{
s/^\s+//;
s/\s+$//;
next unless /\S/;
my ($var, $val) = split(/\s*:\s*/, $_);
$rcref->{$var} = $val;
}
close RC;
}
sub make_lj_agent
{
my $ua = new LWP::UserAgent;
$ua->agent("PerlLiveJournalClient/$VERSION");
$ua->timeout(10);
return $ua;
}
sub lj_request
{
my $ua = shift;
my $vars = shift;
my %ljres = ();
# Create a request
my $req = new HTTP::Request POST => "http://$SERVER_HOST:$SERVER_PORT/$SERVER_URI";
$req->content_type('application/x-www-form-urlencoded');
$req->content(request_string($vars));
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success) {
%ljres = split(/\n/, $res->content);
} else {
$ljres{'success'} = "FAIL";
$ljres{'errmsg'} = "Client error: Error contacing server.";
}
return %ljres;
}
sub request_string
{
my ($vars) = shift;
my $req = "";
foreach (sort keys %{$vars})
{
my $val = uri_escape($vars->{$_},"\+\=\&");
$val =~ s/ /+/g;
$req .= "&" if $req;
$req .= "$_=$val";
}
return $req;
}