[california/wip/725764-ics] Parse command-line options and parse .ics file



commit e2e942d2ab75e10ee9627255a7e3b313434b0ace
Author: Jim Nelson <jim yorba org>
Date:   Fri Apr 11 19:31:34 2014 -0700

    Parse command-line options and parse .ics file

 src/Makefile.am                             |    2 +
 src/application/california-application.vala |   50 ++++++++++-
 src/application/california-commandline.vala |   67 ++++++++++++++
 src/component/component-event.vala          |    2 +-
 src/component/component-icalendar.vala      |  132 +++++++++++++++++++++++++++
 src/component/component-instance.vala       |    6 +-
 vapi/libical.vapi                           |   14 ++--
 7 files changed, 261 insertions(+), 12 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 074b53d..ae21505 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,6 +22,7 @@ california_VALASOURCES = \
        activator/webcal/activator-webcal-pane.vala \
        \
        application/california-application.vala \
+       application/california-commandline.vala \
        application/california-resource.vala \
        application/main.vala \
        \
@@ -73,6 +74,7 @@ california_VALASOURCES = \
        component/component-date-time.vala \
        component/component-error.vala \
        component/component-event.vala \
+       component/component-icalendar.vala \
        component/component-instance.vala \
        component/component-uid.vala \
        component/component-vtype.vala \
diff --git a/src/application/california-application.vala b/src/application/california-application.vala
index 85e8efc..bcf82d6 100644
--- a/src/application/california-application.vala
+++ b/src/application/california-application.vala
@@ -16,11 +16,12 @@ namespace California {
 
 public class Application : Gtk.Application {
     public const string TITLE = _("California");
-    public const string DESCRIPTION = _("Desktop Calendar");
+    public const string DESCRIPTION = _("GNOME 3 Calendar");
     public const string COPYRIGHT = _("Copyright 2014 Yorba Foundation");
     public const string VERSION = PACKAGE_VERSION;
     public const string WEBSITE_NAME = _("Visit California's home page");
     public const string WEBSITE_URL = "https://wiki.gnome.org/Apps/California";;
+    public const string BUGREPORT_URL = "https://bugzilla.gnome.org/enter_bug.cgi?product=california";;
     public const string ID = "org.yorba.california";
     public const string ICON_NAME = "x-office-calendar";
     
@@ -29,11 +30,16 @@ public class Application : Gtk.Application {
         null
     };
     
+    // public application menu actions; note their "app." prefix which does not
+    // match the actions in the action_entries table
     public const string ACTION_NEW_CALENDAR = "app.new-calendar";
     public const string ACTION_CALENDAR_MANAGER = "app.calendar-manager";
     public const string ACTION_ABOUT = "app.about";
     public const string ACTION_QUIT = "app.quit";
     
+    // internal actions; no "app." prefix
+    private const string ACTION_PROCESS_FILE = "process-file";
+    
     private static Application? _instance = null;
     public static Application instance {
         get {
@@ -42,10 +48,14 @@ public class Application : Gtk.Application {
     }
     
     private static const ActionEntry[] action_entries = {
+        // public actions
         { "new-calendar", on_new_calendar },
         { "calendar-manager", on_calendar_manager },
         { "about", on_about },
-        { "quit", on_quit }
+        { "quit", on_quit },
+        
+        // internal
+        { ACTION_PROCESS_FILE, on_process_file, "s" }
     };
     
     private Host.MainWindow? main_window = null;
@@ -59,6 +69,11 @@ public class Application : Gtk.Application {
     public override bool local_command_line(ref unowned string[] args, out int exit_status) {
         exec_file = File.new_for_path(Posix.realpath(Environment.find_program_in_path(args[0])));
         
+        // process arguments now, prior to register and activate; if true is returned before that,
+        // the application will exit with the exit code
+        if (!Commandline.parse(args, out exit_status))
+            return true;
+        
         try {
             register();
         } catch (Error err) {
@@ -67,6 +82,15 @@ public class Application : Gtk.Application {
         
         activate();
         
+        // tell the primary instance (which this instance may not be) about the command-line options
+        // it should act upon
+        if (Commandline.files != null) {
+            foreach (string file in Commandline.files) {
+                debug("send %s", file);
+                activate_action(ACTION_PROCESS_FILE, file);
+            }
+        }
+        
         exit_status = 0;
         
         return true;
@@ -139,6 +163,28 @@ public class Application : Gtk.Application {
         Manager.Window.display(main_window);
     }
     
+    private void on_process_file(SimpleAction action, Variant? variant) {
+        if (variant == null)
+            return;
+        
+        // TODO: Support URIs
+        File file = File.new_for_commandline_arg((string) variant);
+        if (!file.is_native() || file.get_path() == null)
+            return;
+        
+        Component.iCalendar ical;
+        try {
+            MappedFile mmap = new MappedFile(file.get_path(), false);
+            ical = Component.iCalendar.parse((string) mmap.get_contents());
+        } catch (Error err) {
+            message("Unable to add %s: %s", file.get_path(), err.message);
+            
+            return;
+        }
+        
+        debug("Loaded %s", ical.to_string());
+    }
+    
     private void on_about() {
         Gtk.show_about_dialog(main_window,
             "program-name", TITLE,
diff --git a/src/application/california-commandline.vala b/src/application/california-commandline.vala
new file mode 100644
index 0000000..324d5a4
--- /dev/null
+++ b/src/application/california-commandline.vala
@@ -0,0 +1,67 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Commandline {
+
+private const string PARAMS = _("[.ics FILE...]");
+
+public bool show_version = false;
+public Gee.List<string>? files = null;
+
+private const OptionEntry[] options = {
+    { "version", 'V', 0, OptionArg.NONE, ref show_version, N_("Display program version"), null },
+    { null }
+};
+
+/**
+ * Parse the command-line and process the obvious options, converting the remaining to options
+ * which are used by the remainder of the application.
+ *
+ * Returns false if the process should exit with the returned exit code.
+ */
+public bool parse(string[] args, out int exitcode) {
+    OptionContext context = new OptionContext(PARAMS);
+    context.set_help_enabled(true);
+    context.add_main_entries(options, null);
+    context.set_summary(Application.DESCRIPTION);
+    context.set_description("%s\n\n%s\n\t%s\n".printf(
+        Application.COPYRIGHT,
+        _("Please report problems and requests to:"),
+        Application.BUGREPORT_URL));
+    
+    try {
+        context.parse(ref args);
+    } catch (OptionError opterr) {
+        stdout.printf(_("Unknown options: %s\n").printf(opterr.message));
+        stdout.printf("\n%s".printf(context.get_help(true, null)));
+        
+        exitcode = 1;
+        
+        return false;
+    }
+    
+    // convert remaining arguments into files (although note that no sanity checking is
+    // performed)
+    for (int ctr = 1; ctr < args.length; ctr++) {
+        if (files == null)
+            files = new Gee.ArrayList<string>();
+        
+        files.add(args[ctr]);
+    }
+    
+    exitcode = 0;
+    
+    if (show_version) {
+        stdout.printf("%s %s\n", Application.TITLE, Application.VERSION);
+        
+        return false;
+    }
+    
+    return true;
+}
+
+}
+
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 83930dc..af2ce4f 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -59,7 +59,7 @@ public class Event : Instance, Gee.Comparable<Event> {
      *
      * Throws a BackingError if the E.CalComponent's VTYPE is not VEVENT.
      */
-    public Event(Backing.CalendarSource calendar_source, iCal.icalcomponent ical_component) throws Error {
+    public Event(Backing.CalendarSource? calendar_source, iCal.icalcomponent ical_component) throws Error {
         base (calendar_source, ical_component, iCal.icalcomponent_kind.VEVENT_COMPONENT);
         
         // remainder of state is initialized in update_from_component()
diff --git a/src/component/component-icalendar.vala b/src/component/component-icalendar.vala
new file mode 100644
index 0000000..e9e9b92
--- /dev/null
+++ b/src/component/component-icalendar.vala
@@ -0,0 +1,132 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Component {
+
+/**
+ * An immutable representation of an iCalendar VCALENDAR component.
+ *
+ * Note that a iCalendar is not considered an { link Instance}; it's a container which holds
+ * Instances.  Although iCalendar is immutable, there is no guarantee that the Instances it
+ * hold will be.
+ *
+ * Also note that iCalendar currently is not associated with a { link Backing.CalendarSource}.
+ * If the feature is ever added where a CalendarSource can cough up its entire VCALENDAR,
+ * then that might make sense.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.6]].
+ */
+
+public class iCalendar : BaseObject {
+    /**
+     * The VCALENDAR's PRODID.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.7.3]]
+     */
+    public string? prodid { get; private set; default = null; }
+    
+    /**
+     * The VCALENDAR's VERSION.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.7.4].  In particular,
+     * read the Purpose section.
+     */
+    public string? version { get; private set; default = null; }
+    
+    /**
+     * The VCALENDAR's METHOD.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.7.2]]
+     */
+    public iCal.icalproperty_method method { get; private set; default = iCal.icalproperty_method.NONE; }
+    
+    /**
+     * The VCALENDAR's CALSCALE.
+     *
+     * See [[https://tools.ietf.org/html/rfc5545#section-3.7.1]]
+     */
+    public string? calscale { get; private set; default = null; }
+    
+    /**
+     * VEVENTS within the VCALENDAR.
+     */
+    public Gee.List<Event> events { get; private set; default = new Gee.ArrayList<Event>(); }
+    
+    /**
+     * The iCal VCALENDAR this iCalendar represents.
+     */
+    private iCal.icalcomponent _ical_component;
+    public iCal.icalcomponent ical_component { get { return _ical_component; } }
+    
+    /**
+     * Create an { link iCalendar} representation of the iCal component.
+     *
+     * @throws ComponentError.INVALID if root is not a VCALENDAR.
+     */
+    private iCalendar(owned iCal.icalcomponent root) throws Error {
+        if (root.isa() != iCal.icalcomponent_kind.VCALENDAR_COMPONENT)
+            throw new ComponentError.INVALID("Not a VCALENDAR");
+        
+        //
+        // VCALENDAR properties
+        //
+        
+        unowned iCal.icalproperty? prop = root.get_first_property(iCal.icalproperty_kind.PRODID_PROPERTY);
+        if (prop != null)
+            prodid = prop.get_prodid();
+        
+        prop = root.get_first_property(iCal.icalproperty_kind.VERSION_PROPERTY);
+        if (prop != null)
+            version = prop.get_version();
+        
+        prop = root.get_first_property(iCal.icalproperty_kind.CALSCALE_PROPERTY);
+        if (prop != null)
+            calscale = prop.get_calscale();
+        
+        prop = root.get_first_property(iCal.icalproperty_kind.METHOD_PROPERTY);
+        if (prop != null)
+            method = prop.get_method();
+        
+        //
+        // Contained components
+        //
+        
+        // VEVENTS
+        unowned iCal.icalcomponent? child_component = root.get_first_component(
+            iCal.icalcomponent_kind.VEVENT_COMPONENT);
+        while (child_component != null) {
+            events.add(Instance.convert(null, child_component) as Event);
+            child_component = root.get_next_component(iCal.icalcomponent_kind.VEVENT_COMPONENT);
+        }
+        
+        // take ownership
+        _ical_component = (owned) root;
+    }
+    
+    /**
+     * Returns an appropriate { link Calendar} instance for the string of iCalendar data.
+     *
+     * @throws ComponentError if data is unrecognized
+     */
+    public static iCalendar parse(string? str) throws Error {
+        if (String.is_empty(str))
+            throw new ComponentError.INVALID("Empty VCALENDAR string");
+        
+        iCal.icalcomponent? ical_component = iCal.icalparser.parse_string(str);
+        if (ical_component == null)
+            throw new ComponentError.INVALID("Unable to parse VCALENDAR (%db)".printf(str.length));
+        
+        return new iCalendar((owned) ical_component);
+    }
+    
+    public override string to_string() {
+        return "iCalendar|%s|%s|%s|%s (%d events)".printf(prodid, version, calscale, method.to_string(),
+            events.size);
+    }
+}
+
+}
+
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index f9769d7..037f851 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -115,7 +115,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      * external invocation of full_update() (such as immutable data) should update that state after
      * the base constructor returns.
      */
-    protected Instance(Backing.CalendarSource calendar_source, iCal.icalcomponent ical_component,
+    protected Instance(Backing.CalendarSource? calendar_source, iCal.icalcomponent ical_component,
         iCal.icalcomponent_kind kind) throws Error {
         if (ical_component.isa() != kind) {
             throw new ComponentError.MISMATCH("Cannot create VTYPE %s from component of VTYPE %s",
@@ -260,9 +260,11 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     /**
      * Returns an appropriate { link Component} instance for the iCalendar component.
      *
+     * VCALENDARs should use { link Component.iCalendar}.
+     *
      * @returns null if the component is not represented in this namespace (yet).
      */
-    public static Component.Instance? convert(Backing.CalendarSource calendar_source,
+    public static Component.Instance? convert(Backing.CalendarSource? calendar_source,
         iCal.icalcomponent ical_component) throws Error {
         switch (ical_component.isa()) {
             case iCal.icalcomponent_kind.VEVENT_COMPONENT:
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index 7950841..14ea598 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -85,9 +85,9 @@ namespace iCal {
                [CCode (cname = "icalcomponent_get_comment")]
                public unowned string get_comment ();
                [CCode (cname = "icalcomponent_get_current_component")]
-               public unowned iCal.icalcomponent get_current_component ();
+               public unowned iCal.icalcomponent? get_current_component ();
                [CCode (cname = "icalcomponent_get_current_property")]
-               public unowned iCal.icalproperty get_current_property ();
+               public unowned iCal.icalproperty? get_current_property ();
                [CCode (cname = "icalcomponent_get_description")]
                public unowned string get_description ();
                [CCode (cname = "icalcomponent_get_dtend")]
@@ -101,7 +101,7 @@ namespace iCal {
                [CCode (cname = "icalcomponent_get_duration")]
                public unowned iCal.icaldurationtype get_duration ();
                [CCode (cname = "icalcomponent_get_first_component")]
-               public unowned iCal.icalcomponent get_first_component (iCal.icalcomponent_kind kind);
+               public unowned iCal.icalcomponent? get_first_component (iCal.icalcomponent_kind kind);
                [CCode (cname = "icalcomponent_get_first_property")]
                public unowned iCal.icalproperty? get_first_property (iCal.icalproperty_kind kind);
                [CCode (cname = "icalcomponent_get_first_real_component")]
@@ -113,9 +113,9 @@ namespace iCal {
                [CCode (cname = "icalcomponent_get_method")]
                public iCal.icalproperty_method get_method ();
                [CCode (cname = "icalcomponent_get_next_component")]
-               public unowned iCal.icalcomponent get_next_component (iCal.icalcomponent_kind kind);
+               public unowned iCal.icalcomponent? get_next_component (iCal.icalcomponent_kind kind);
                [CCode (cname = "icalcomponent_get_next_property")]
-               public unowned iCal.icalproperty get_next_property (iCal.icalproperty_kind kind);
+               public unowned iCal.icalproperty? get_next_property (iCal.icalproperty_kind kind);
                [CCode (cname = "icalcomponent_get_parent")]
                public unowned iCal.icalcomponent get_parent ();
                [CCode (cname = "icalcomponent_get_recurrenceid")]
@@ -481,9 +481,9 @@ namespace iCal {
                [CCode (cname = "icalparser_get_state")]
                public iCal.icalparser_state get_state ();
                [CCode (cname = "icalparser_parse")]
-               public unowned iCal.icalcomponent parse (GLib.Callback line_gen_func);
+               public iCal.icalcomponent? parse (GLib.Callback line_gen_func);
                [CCode (cname = "icalparser_parse_string")]
-               public static unowned iCal.icalcomponent parse_string (string str);
+               public static iCal.icalcomponent? parse_string (string str);
                [CCode (cname = "icalparser_parse_value")]
                public static unowned iCal.icalvalue parse_value (iCal.icalvalue_kind kind, string str, out 
unowned iCal.icalcomponent errors);
                [CCode (cname = "icalparser_set_gen_data")]


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]