c1bfd2463a5bb8f5e5e6ae9c869fdf3d49cca764
[xscreensaver] / OSX / updates.pl
1 #!/usr/bin/perl -w
2 # Copyright © 2013 Jamie Zawinski <jwz@jwz.org>
3 #
4 # Permission to use, copy, modify, distribute, and sell this software and its
5 # documentation for any purpose is hereby granted without fee, provided that
6 # the above copyright notice appear in all copies and that both that
7 # copyright notice and this permission notice appear in supporting
8 # documentation.  No representations are made about the suitability of this
9 # software for any purpose.  It is provided "as is" without express or 
10 # implied warranty.
11 #
12 # Generates updates.xml from README, archive/, and www/.
13 #
14 # Created: 27-Nov-2013.
15
16 require 5;
17 use diagnostics;
18 use strict;
19
20 use open ":encoding(utf8)";
21 use POSIX;
22
23 my $progname = $0; $progname =~ s@.*/@@g;
24 my $version = q{ $Revision: 1.1 $ }; $version =~ s/^[^\d]+([\d.]+).*/$1/;
25
26 my $verbose = 0;
27 my $debug_p = 0;
28
29 my $base_url = "http://www.jwz.org/";
30 my $priv_key_file = "$ENV{HOME}/.ssh/sparkle_dsa_priv.pem";
31 my $sign_update = "./sign_update.rb";
32
33
34 sub generate_xml($$$$) {
35   my ($app_name, $changelog, $archive_dir, $www_dir) = @_;
36
37   my $outfile = "updates.xml";
38
39   my $obody = '';
40   my %sigs;
41   my %dates;
42   if (open (my $in, '<', $outfile)) {
43     print STDERR "$progname: reading $outfile\n" if $verbose;
44     local $/ = undef;  # read entire file
45     $obody = <$in>;
46     close $in;
47     foreach my $item (split (/<item/i, $obody)) {
48       my ($v)    = ($item =~ m/version="(.*?)"/si);
49       my ($sig)  = ($item =~ m/dsaSignature="(.*?)"/si);
50       my ($date) = ($item =~ m/<pubDate>(.*?)</si);
51       next unless $v;
52       $sigs{$v}  = $sig  if $sig;
53       $dates{$v} = $date if $date;
54       print STDERR "$progname: $v: " . ($date || '?') . "\n"
55         if ($verbose > 1);
56     }
57   }
58
59   open (my $in, '<', $changelog) || error ("$changelog: $!");
60   print STDERR "$progname: reading $changelog\n" if $verbose;
61   local $/ = undef;  # read entire file
62   my $body = <$in>;
63   close $in;
64
65   my $rss = "";
66
67   $body =~ s/^(\d+\.\d+[ \t])/\001$1/gm;
68   my @log = split (/\001/, $body);
69   shift @log;
70   foreach my $log (@log) {
71     my ($v1, $entry) = ($log =~ m/^(\d+\.\d+)\s+(.*)$/s);
72
73     $entry =~ s/^\s*\d\d?[- ][A-Z][a-z][a-z][- ]\d{4}:?\s+//s;  # lose date
74
75     $entry =~ s/^\s+|\s+$//gs;
76     $entry =~ s/^\s+|\s+$//gm;
77     $entry =~ s/^[-*] /<BR>&bull; /gm;
78     $entry =~ s/^<BR>//si;
79     $entry =~ s/\s+/ /gs;
80
81     my $v2 = $v1; $v2 =~ s/\.//gs;
82     my $zip = undef;
83   DONE:
84     foreach my $ext ('zip', 'dmg', 'tar.gz', 'tar.Z') {
85       foreach my $v ($v1, $v2) {
86         foreach my $name ($app_name, "x" . lc($app_name)) {
87           my $f = "$name-$v.$ext";
88           if (-f "$archive_dir/$f") {
89             $zip = $f;
90             last DONE;
91           }
92         }
93       }
94     }
95
96     my $url = ("${base_url}$app_name/" .
97                ($zip && -f "$www_dir/$zip" ? $zip : ""));
98
99     $url =~ s@DaliClock/@xdaliclock/@gs if $url; # Kludge
100
101     my @st = stat("$archive_dir/$zip") if $zip;
102     my $size = $st[7];
103     my $date = $st[9];
104     $date = ($zip ?
105              strftime ("%a, %d %b %Y %T %z", localtime($date))
106              : "");
107
108     my $odate = $dates{$v1};
109     my $sig   = $sigs{$v1};
110     # Re-generate the sig if the file date changed.
111     $sig = undef if ($odate && $odate ne $date);
112
113     print STDERR "$progname: $v1: $date " . ($sig ? "Y" : "N") . "\n"
114       if ($verbose > 1);
115
116     if (!$sig && $zip) {
117       local %ENV = %ENV;
118       $ENV{PATH} = "/usr/bin:$ENV{PATH}";
119       $sig = `$sign_update "$archive_dir/$zip" "$priv_key_file"`;
120       $sig =~ s/\s+//gs;
121     }
122
123     my $enc = ($zip && -f "$www_dir/$zip"
124                ? ("<enclosure url=\"$url\"\n" .
125                   " sparkle:version=\"$v1\"\n" .
126                   " sparkle:dsaSignature=\"$sig\"\n" .
127                   " length=\"$size\"\n" .
128                   " type=\"application/octet-stream\" />\n")
129                : "<sparkle:version>$v1</sparkle:version>\n");
130
131     $enc =~ s/^/ /gm if $enc;
132     my $item = ("<item>\n" .
133                 " <title>Version $v1</title>\n" .
134                 " <link>$url</link>\n" .
135                 " <description><![CDATA[$entry]]></description>\n" .
136                 " <pubDate>$date</pubDate>\n" .
137                 $enc .
138                 "</item>\n");
139     $item =~ s/^/  /gm;
140
141     # I guess Sparkle doesn't like info-only items.
142     $item = '' unless ($zip && -f "$www_dir/$zip");
143
144     $rss .= $item;
145   }
146
147   $rss = ("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" .
148           "<rss version=\"2.0\"\n" .
149           "     xmlns:sparkle=\"http://www.andymatuschak.org/" .
150                "xml-namespaces/sparkle\"\n" .
151           "     xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" .
152           " <channel>\n" .
153           "  <title>$app_name updater</title>\n" .
154           "  <link>${base_url}$app_name/updates.xml</link>\n" .
155           "  <description>Updates to $app_name.</description>\n" .
156           "  <language>en</language>\n" .
157           $rss .
158           " </channel>\n" .
159           "</rss>\n");
160
161   if ($rss eq $obody) {
162     print STDERR "$progname: $outfile: unchanged\n";
163   } else {
164     my $tmp = "$outfile.tmp";
165     open (my $out, '>', $tmp) || error ("$tmp: $!");
166     print $out $rss;
167     close $out;
168     if ($debug_p) {
169       system ("diff", "-wNU2", "$outfile", "$tmp");
170       unlink $tmp;
171     } else {
172       if (!rename ("$tmp", "$outfile")) {
173         unlink "$tmp";
174         error ("mv $tmp $outfile: $!");
175       } else {
176         print STDERR "$progname: wrote $outfile\n";
177       }
178     }
179   }
180 }
181
182
183 sub error($) {
184   my ($err) = @_;
185   print STDERR "$progname: $err\n";
186   exit 1;
187 }
188
189 sub usage() {
190   print STDERR "usage: $progname [--verbose] app-name changelog archive www\n";
191   exit 1;
192 }
193
194 sub main() {
195   my ($app_name, $changelog, $archive_dir, $www_dir);
196   while ($#ARGV >= 0) {
197     $_ = shift @ARGV;
198     if (m/^--?verbose$/)  { $verbose++; }
199     elsif (m/^-v+$/)      { $verbose += length($_)-1; }
200     elsif (m/^--?debug$/) { $debug_p++; }
201     elsif (m/^-./)        { usage; }
202     elsif (!$app_name)    { $app_name = $_; }
203     elsif (!$changelog)   { $changelog = $_; }
204     elsif (!$archive_dir) { $archive_dir = $_; }
205     elsif (!$www_dir)     { $www_dir = $_; }
206     else { usage; }
207   }
208
209   usage unless $www_dir;
210   generate_xml ($app_name, $changelog, $archive_dir, $www_dir);
211
212 }
213
214 main();
215 exit 0;