root/cybozu2ical/trunk/cybozu2ical

Revision 521, 10.5 kB (checked in by ogawa, 1 month ago)

Add is_private and is_shared methods to Calendar::Entry class.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1 #!/usr/bin/perl
2 # cybozu2ical: Convert Cybozu Office calendar into iCalendar format
3 #
4 # $Id$
5
6 use strict;
7 use lib 'lib';
8
9 use Encode qw( decode_utf8 encode );
10 use Data::ICal;
11 use Data::ICal::Entry::Event;
12 use Data::ICal::Entry::TimeZone;
13 use Data::ICal::Entry::TimeZone::Standard;
14 use DateTime;
15 use WWW::CybozuOffice6::Calendar;
16 use URI;
17 use Pod::Usage;
18 use Getopt::Long;
19
20 our $VERSION = '0.32';
21
22 ###
23 ### TRICK (stop escaping for 'exdate' property)
24 ###
25 *Data::ICal::Property::_value_as_string = sub {
26     my $self  = shift;
27     my $key   = shift;
28     my $value = defined( $self->value() ) ? $self->value() : '';
29
30     unless ( $self->vcal10 ) {
31         my $lc_key = lc($key);
32         $value =~ s/\\/\\/gs;
33         $value =~ s/\Q;/\\;/gs
34           unless ( $lc_key eq 'rrule' || $lc_key eq 'exdate' );
35         $value =~ s/,/\\,/gs
36           unless ( $lc_key eq 'rrule' || $lc_key eq 'exdate' );
37         $value =~ s/\n/\\n/gs;
38         $value =~ s/\\N/\\N/gs;
39     }
40
41     return $value;
42
43 };
44
45 ###
46 ### Utility subroutines
47 ###
48 sub to_icaldate {
49     my ( $dt, $is_full_day ) = @_;
50     $is_full_day
51       ? $dt->ymd('')
52       : $dt->ymd('') . 'T'
53       . $dt->hms('')
54       . ( $dt->time_zone->is_utc ? 'Z' : '' );
55 }
56
57 sub encode_string {
58     my ( $enc, $text ) = @_;
59     if ( $enc eq 'ncr' ) {
60         $text =~ s/(\P{ASCII})/sprintf("&#%d;", ord($1))/eg;
61     }
62     else {
63         $text = encode( $enc, $text );
64     }
65     $text;
66 }
67
68 sub read_yaml {
69     my $file = shift;
70     my $yaml;
71     if ( eval('require YAML::Tiny') ) {
72         ($yaml) = YAML::Tiny::LoadFile($file);
73     }
74     elsif ( eval('require YAML') ) {
75         ($yaml) = YAML::LoadFile($file);
76     }
77     if ($@) {
78         die "Faild to read yaml file: $@";
79     }
80     $yaml;
81 }
82
83 ###
84 ### Main part
85 ###
86
87 # Handle command-line options
88 my %opt = (
89     conf                     => 'config.yaml',
90     'compat-google-calendar' => 0,
91     'uid'                    => 1,
92     'url'                    => 0,
93 );
94 GetOptions( \%opt, 'output=s', 'conf=s', 'compat-google-calendar', 'debug',
95     'input-csv=s', 'output-csv=s', 'help', 'uid!', 'url!' )
96   or pod2usage(2);
97 pod2usage(1) if $opt{help};
98
99 # Read configuration file
100 my $cfg = read_yaml( $opt{conf} );
101 my $time_zone = $cfg->{time_zone} || 'Asia/Tokyo';
102
103 # Generate a root 'calendar' object
104 my $vcalendar = Data::ICal->new();
105 $vcalendar->add_properties(
106     prodid   => "-//as-is.net/Cybozu2ICal $VERSION//EN",
107     calscale => 'GREGORIAN',
108     method   => 'PUBLISH',
109     $cfg->{calname} ? ( 'X-WR-CALNAME' => $cfg->{calname} ) : (),
110     'X-WR-TIMEZONE' => $time_zone
111 );
112
113 # Obtain Cybozu Office 6/7 Calendar items
114 my $cal = WWW::CybozuOffice6::Calendar->new(%$cfg);
115
116 if ( $opt{'input-csv'} ) {
117     $cal->read_from_csv_file( $opt{'input-csv'} )
118       or die "Failed to read CSV file: $opt{'input-csv'}";
119 }
120 else {
121     $cal->request()
122       or die "Failed to get Cybozu Office 6 Calendar";
123 }
124
125 # Output the calendar CSV for debugging
126 if ( $opt{'output-csv'} ) {
127     local *FH;
128     open FH, ">$opt{'output-csv'}" or die "Failed to write $opt{'output-csv'}";
129     print FH "$_\n" for $cal->response;
130     close FH;
131 }
132
133 # For each items, generate an 'event entry' and append it to the calendar
134 for my $item ( $cal->get_items() ) {
135     my $vevent = Data::ICal::Entry::Event->new();
136     my %args   = (
137         summary     => decode_utf8( $item->summary ),
138         description => decode_utf8( $item->description ),
139         created     => to_icaldate( $item->created ),
140         dtstamp     => to_icaldate( $item->modified ),
141     );
142
143     if ( $item->is_full_day ) {
144         $args{dtstart} =
145           [ to_icaldate( $item->start, 1 ), { VALUE => 'DATE' } ];
146         $args{dtend} = [ to_icaldate( $item->end, 1 ), { VALUE => 'DATE' } ];
147     }
148     else {
149         $args{dtstart} =
150           [ to_icaldate( $item->start, 0 ), { TZID => $time_zone } ];
151         $args{dtend} = [ to_icaldate( $item->end, 0 ), { TZID => $time_zone } ];
152     }
153
154     # handle frequency
155     if ( $item->can('rrule') ) {
156
157         # rrule
158         my %rrule = %{ $item->rrule };
159         $rrule{UNTIL} = to_icaldate( $rrule{UNTIL}, $item->is_full_day )
160           if $rrule{UNTIL};
161         $rrule{WKST} = 'SU'
162           if $opt{'compat-google-calendar'};
163
164         my @rrule_list;
165         for (qw(FREQ COUNT INTERVAL BYMONTH BYMONTHDAY WKST BYDAY UNTIL)) {
166             push @rrule_list, $_ . '=' . $rrule{$_}
167               if exists $rrule{$_};
168         }
169         $args{rrule} = join ';', @rrule_list;
170
171         # exdate
172         if ( $item->exdates ) {
173             if ( $item->is_full_day ) {
174                 my $exdate = join ',',
175                   map { to_icaldate( $_, 1 ) } $item->exdates;
176                 $args{exdate} = [ $exdate, { VALUE => 'DATE' } ];
177             }
178             else {
179                 my $exdate = join ',',
180                   map { to_icaldate( $_, 0 ) } $item->exdates;
181                 $args{exdate} = [ $exdate, { TZID => $time_zone } ];
182             }
183         }
184
185     }
186
187     # set uid (recommended to be the identical syntax to RFC822)
188     $args{uid} =
189       $item->id . '@' . ( URI->new( $cal->url )->host || 'localhost' )
190       if $opt{uid};
191
192     $args{url} = $cal->url . '?page=ScheduleView&EID=' . $item->id
193       if $opt{url};
194
195     # $args{class}  = $item->is_private ? 'PRIVATE'     : 'PUBLIC';
196     # $args{transp} = $item->is_private ? 'TRANSPARENT' : 'OPAQUE';
197
198     $args{comment} = decode_utf8( $item->comment )
199       if $opt{debug} && $item->comment;
200
201     $vevent->add_properties(%args);
202     $vcalendar->add_entry($vevent);
203 }
204
205 # Generate a 'timezone entry' and append it to the calendar
206 my $vtimezone = Data::ICal::Entry::TimeZone->new();
207 $vtimezone->add_properties( tzid => $time_zone );
208
209 # probably we need to support the Daylight Saving Time, but not yet supported.
210 my $standard = Data::ICal::Entry::TimeZone::Standard->new();
211 my $std      = DateTime->new(
212     year      => 1970,
213     month     => 1,
214     day       => 1,
215     hour      => 0,
216     minute    => 0,
217     second    => 0,
218     time_zone => $time_zone
219 );
220 my $offset = DateTime::TimeZone::offset_as_string( $std->offset ) || '+0900';
221 my $tzname = $cfg->{tzname} || 'JST';
222
223 $standard->add_properties(
224     tzoffsetfrom => $offset,
225     tzoffsetto   => $offset,
226     tzname       => $tzname,
227     dtstart      => to_icaldate($std)
228 );
229 $vtimezone->add_entry($standard);
230
231 $vcalendar->add_entry($vtimezone);
232
233 # Outputs the calendar as a string
234 my $text =
235   encode_string( $cfg->{output_encoding} || 'utf8', $vcalendar->as_string );
236 if ( $opt{output} ) {
237     local *FH;
238     open FH, ">$opt{output}" or die "Failed to write $opt{output}";
239     print FH $text;
240     close FH;
241 }
242 else {
243     print $text;
244 }
245
246 1;
247 __END__
248
249 =head1 NAME
250
251 cybozu2ical - Convert Cybozu Office calendar into iCalendar format
252
253 =head1 SYNOPSIS
254
255   % cybozu2ical
256   % cybozu2ical --conf /path/to/config.yaml
257
258 =head1 DESCRIPTION
259
260 C<cybozu2ical> is a command line application that fetches calendar
261 items from Cybozu Office 6 or later, and converts them into an
262 iCalendar file.  It allows you to easily integrate the Cybozu Calendar
263 into iCalendar-enabled Calendar applications, such as Microsoft
264 Outlook, Apple iCal, and of course, Google Calendar.
265
266 You can run this via crontab, for example, every 1 hour.
267
268 =head1 REQUIREMENT
269
270 This application requires perl 5.8.0 with following Perl modules
271 installed on your box.
272
273 =over 4
274
275 =item WWW::CybozuOffice6::Calendar
276
277 =item Text::CSV_XS or Text::CSV
278
279 =item DateTime
280
281 =item LWP::UserAgent
282
283 =item Class::Accessor::Fast
284
285 =item Data::ICal
286
287 =item YAML or YAML::Tiny
288
289 =back
290
291 =head1 OPTIONS
292
293 =over 4
294
295 =item --output /path/to/output.ics
296
297 Specify the output file.  By default, this application outputs to
298 STDOUT.
299
300 =item --conf /path/to/config.yaml
301
302 Specify the configuration file.  By default, C<config.yaml> in the
303 current directory will be used.
304
305 =item --compat-google-calendar
306
307 Output an iCalendar file compatible with Google Calendar.
308
309 =item --debug
310
311 Output CSV data in a COMMENT field of each events.  It's just for
312 debugging.
313
314 =item --input-csv /path/to/input.csv
315
316 Instead of requesting Cybozu Office 6 server, read from a local CSV
317 file.
318
319 =item --output-csv /path/to/output.csv
320
321 Specify the output CSV file for debugging.
322
323 =item --uid, --no-uid
324
325 Enable/Disable UID fields of the iCalendar file. (Default: Enable)
326
327 =item --url, --no-url
328
329 Enable/Disable URL fields of the iCalendar file. (Default: Disable)
330
331 =item --help
332
333 Print out this message.
334
335 =back
336
337 =head1 CONFIGURATION
338
339 The distributions includes a sample configuration file
340 C<config.yaml.sample>. You can rename it to C<config.yaml> and
341 configure C<cybozu2ical>.
342
343 =over 4
344
345 =item cybozu_url
346
347 Set the URL of your Cybozu Office 6 or later.
348
349 =item calname
350
351 Set the calendar name string. iCalendar applications which properly
352 handle X-WR-CALNAME header, is expected to use this string as a
353 calendar name.
354
355 =item username, userid
356
357 Set your username or userid for Cybozu Office.
358
359 =item password
360
361 Set your password for Cybozu Office.
362
363 =item time_zone
364
365 Set the timezone of your Cybozu Office (e.g., Asia/Tokyo).
366
367 =item tzname
368
369 Set the short timezone name of your Cybozu Office (e.g., JST).
370
371 =item input_encoding
372
373 Set the charset of Cybozu Office. By default, C<input_encoding> is
374 "shiftjis".
375
376 =item output_encoding
377
378 Set the charset of the iCalendar file.  By default, C<output_encoding>
379 is "utf8".  If you need to output multibyte strings as Numeric
380 Character References for some reason, set C<output_encoding> to "ncr".
381
382 =item calendar_driver
383
384 Set the calendar driver that C<cybozu2ical> employs.  By default,
385 C<ApiCalendar> is used as C<calendar_driver>.
386
387 Currently, C<ApiCalendar> and C<SyncCalendar> drivers are shipped with
388 C<cybozu2ical>.  If you are using Cybozu Office 6, C<SyncCalendar> is
389 strongly recommended.  Otherwise, you have to use C<ApiCalendar>.
390
391 =item date_range (experimental)
392
393 Set the date range of calendar, which means C<cybozu2ical> handles
394 calendar items between N days before and after.  Default C<date_range>
395 is 30.
396
397 =back
398
399 =head1 DEVELOPMENT
400
401 The development version is always available from the following
402 subversion repository:
403
404   http://code.as-is.net/svn/public/cybozu2ical/trunk/
405
406 You can browse the files via Trac from the following:
407
408   http://code.as-is.net/public/browser/cybozu2ical/trunk/
409
410 Any comments, suggestions, or patches are welcome.
411
412 =head1 LICENSE
413
414 Copyright (c) 2008 Hirotaka Ogawa E<lt>hirotaka.ogawa at gmail.comE<gt>.
415 All rights reserved.
416
417 This library is free software; you can redistribute it and/or modify
418 it under the terms of either:
419        
420    a) the GNU General Public License as published by the Free Software
421       Foundation; either version 1, or (at your option) any later
422       version, or
423                          
424    b) the "Artistic License" which comes with Perl.
425
426 =cut
Note: See TracBrowser for help on using the browser.