Syncronising Calendars Noly Working In One Direction

297 views
Skip to first unread message

rshin...@googlemail.com

unread,
Jan 12, 2015, 6:53:01 PM1/12/15
to provider-for-g...@googlegroups.com
Hi,

When I make a change to a calendar in lightning and sync the calendars the changes don't appear on google calendar. If I make a change on google calendar and sync then the change appears in lightning.
I'm running the latest release versions of all the relevant software.
The only things I can see in the debug log are as follows:

[JavaScript Error: "[calGoogleCalendar] MyEventName failed:2147746065: A request Error Occurred. Status Code: 400 Bad Request Body: {
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "required",
    "message": "Missing time zone definition for start time."
   }
  ],
  "code": 400,
  "message": "Missing time zone definition for start time."
 }
}
"]

Regards,
Roland

Markus Lippert

unread,
Jan 15, 2015, 12:38:56 AM1/15/15
to provider-for-g...@googlegroups.com
Hi,

I also see lots of sync issues. 
I can not say that I am really happy. I never know for sure if an entry was synced or not between my latest thunderbird and android 4.4.2. 
I uses the calender very much. 
I had this also in the past. Some versions work and some have issues
Any ideas?
Regards Markus

Bruce Lang

unread,
Mar 16, 2015, 8:42:06 AM3/16/15
to provider-for-g...@googlegroups.com
I am still experiencing issues as described, despite using Provider 1.0.4 - can update on Google Calendar and it appears in Lightning 3.3.3, but add an event in Lightning and I get a 'Modification Failed' error message. (Have not noticed any difference/improvement in this function, which first appeared under 1.0.3) - the problem is now getting up towards being 6 months old.

BéèM

unread,
Apr 8, 2015, 4:52:33 PM4/8/15
to provider-for-g...@googlegroups.com
Two days ago I discovered Lightning and use it on SeaMonkey .
Me too an entry made in Google Calendar is reflected in Lightning, but entries in Lightning aren't reflected in Google Calendar.
However when syncing, I have no error entry in the log as shown by rshin.

Hope that a solution for this issue will be found.

Op dinsdag 13 januari 2015 00:53:01 UTC+1 schreef rshin...@googlemail.com:

Cecilia Torres

unread,
Apr 14, 2015, 9:48:45 AM4/14/15
to provider-for-g...@googlegroups.com
Has anyone found a solution to this problem? 

I also have only been able to successfully sync in one direction, i.e. Google calendar events appear on my Lightning calendar but not the other way around. 

Suggestions anyone?

Bruce Lang

unread,
Apr 15, 2015, 2:49:28 AM4/15/15
to provider-for-g...@googlegroups.com
Don't have a solution, but tried to create an event called "Test" and this is what the Error Console returns as the source file (not sure if it helps at all):

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calProviderUtils.jsm");

const REGISTRY_BRANCH = "calendar.registry.";
const DB_SCHEMA_VERSION = 10;
const MAX_INT = Math.pow(2, 31) - 1;
const MIN_INT = -MAX_INT;

function calCalendarManager() {
    this.wrappedJSObject = this;
    this.mObservers = new calListenerBag(Components.interfaces.calICalendarManagerObserver);
    this.mCalendarObservers = new calListenerBag(Components.interfaces.calIObserver);
}

const calCalendarManagerClassID = Components.ID("{f42585e7-e736-4600-985d-9624c1c51992}");
const calCalendarManagerInterfaces = [
    Components.interfaces.calICalendarManager,
    Components.interfaces.calIStartupService,
    Components.interfaces.nsIObserver,
];
calCalendarManager.prototype = {
    classID: calCalendarManagerClassID,
    QueryInterface: XPCOMUtils.generateQI(calCalendarManagerInterfaces),
    classInfo: XPCOMUtils.generateCI({
        classID: calCalendarManagerClassID,
        contractID: "@mozilla.org/calendar/manager;1",
        classDescription: "Calendar Manager",
        interfaces: calCalendarManagerInterfaces,
        flags: Components.interfaces.nsIClassInfo.SINGLETON
    }),

    get networkCalendarCount() this.mNetworkCalendarCount,
    get readOnlyCalendarCount() this.mReadonlyCalendarCount,
    get calendarCount() this.mCalendarCount,

    // calIStartupService:
    startup: function ccm_startup(aCompleteListener) {
        AddonManager.addAddonListener(gCalendarManagerAddonListener);
        this.checkAndMigrateDB();
        this.mCache = null;
        this.mCalObservers = null;
        this.mRefreshTimer = {};
        this.setupOfflineObservers();
        this.mNetworkCalendarCount = 0;
        this.mReadonlyCalendarCount = 0;
        this.mCalendarCount = 0;

        Services.obs.addObserver(this, "http-on-modify-request", false);

        // We only add the observer if the pref is set and only check for the
        // pref on startup to avoid checking for every http request
        if (Preferences.get("calendar.network.multirealm", false)) {
            Services.obs.addObserver(this, "http-on-examine-response", false);
        }

        aCompleteListener.onResult(null, Components.results.NS_OK);
    },

    shutdown: function ccm_shutdown(aCompleteListener) {
        for each (var calendar in this.mCache) {
            calendar.removeObserver(this.mCalObservers[calendar.id]);
        }

        this.cleanupOfflineObservers();

        Services.obs.removeObserver(this, "http-on-modify-request");

        AddonManager.removeAddonListener(gCalendarManagerAddonListener);

        // Remove the observer if the pref is set. This might fail when the
        // user flips the pref, but we assume he is going to restart anyway
        // afterwards.
        if (Preferences.get("calendar.network.multirealm", false)) {
            Services.obs.removeObserver(this, "http-on-examine-response");
        }

        aCompleteListener.onResult(null, Components.results.NS_OK);
    },


    setupOfflineObservers: function ccm_setupOfflineObservers() {
        Services.obs.addObserver(this, "network:offline-status-changed", false);
    },

    cleanupOfflineObservers: function ccm_cleanupOfflineObservers() {
        Services.obs.removeObserver(this, "network:offline-status-changed");
    },

    observe: function ccm_observe(aSubject, aTopic, aData) {
        switch (aTopic) {
            case "timer-callback":
                // Refresh all the calendars that can be refreshed.
                var cals = this.getCalendars({});
                for each (var calendar in cals) {
                    if (!calendar.getProperty("disabled") && calendar.canRefresh) {
                        calendar.refresh();
                    }
                }
                break;
            case "network:offline-status-changed":
                for each (var calendar in this.mCache) {
                    if (calendar instanceof calCachedCalendar) {
                        calendar.onOfflineStatusChanged(aData == "offline");
                    }
                }
                break;
            case "http-on-examine-response":
                try {
                    let channel = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
                    if (channel.notificationCallbacks) {
                        // We use the notification callbacks to get the calendar interface,
                        // which likely works for our requests since getInterface is called
                        // from the calendar provider context.
                        let authHeader = channel.getResponseHeader("WWW-Authenticate");
                        let calendar = channel.notificationCallbacks
                                              .getInterface(Components.interfaces.calICalendar);
                        if (calendar && !calendar.getProperty("capabilities.realmrewrite.disabled")) {
                            // The provider may choose to explicitly disable the
                            // rewriting, for example if all calendars on a
                            // domain have the same credentials
                            let escapedName = calendar.name.replace('\\', '\\\\', 'g')
                                                           .replace('"','\\"', 'g');
                            authHeader = appendToRealm(authHeader, "(" + escapedName + ")");
                            channel.setResponseHeader("WWW-Authenticate", authHeader, false);
                        }
                    }
                } catch (e if e.result == Components.results.NS_NOINTERFACE ||
                              e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
                    // Possible reasons we got here:
                    // - Its not a http channel (wtf? Oh well)
                    // - The owner is not a calICalendar (looks like its not our deal)
                    // - The WWW-Authenticate header is missing (thats ok)
                }
                break;
            case "http-on-modify-request":
                // Unfortunately, the ability to do this with a general pref has
                // been removed. Calendar servers might still want to know what
                // client is used for access, so add our UA String to each
                // request.
                let httpChannel = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
                try {
                    // NOTE: For some reason, this observer call doesn't have
                    // the "cal" namespace defined
                    let ua = httpChannel.getRequestHeader("User-Agent");
                    let calUAString = Preferences.get("calendar.useragent.extra");
                    if (calUAString && ua.indexOf(calUAString) < 0) {
                        // User-Agent is not a mergeable header. We need to
                        // merge the user agent ourselves.
                        httpChannel.setRequestHeader("User-Agent",
                                                     ua + " " + calUAString,
                                                     false);
                    }
                } catch (e if e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
                    // We swallow this error since it means the User Agent
                    // header is not set. We don't want to force it to be set.
                }
                break;
        }
    },

    //
    // DB migration code begins here
    //

    upgradeDB: function(oldVersion, db) {
        // some common helpers
        function addColumn(db_, tableName, colName, colType) {
            db_.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
        }

        if (oldVersion < 6) {
            dump ("**** Upgrading calCalendarManager schema to 6\n");

            // Schema changes in v6:
            //
            // - Change all STRING columns to TEXT to avoid SQLite's
            //   "feature" where it will automatically convert strings to
            //   numbers (ex: 10e4 -> 10000). See bug 333688.

            // Create the new tables.

            try {
                db.executeSimpleSQL("DROP TABLE cal_calendars_v6; DROP TABLE cal_calendars_prefs_v6;");
            } catch (e) {
                // We should get exceptions for trying to drop tables
                // that don't (shouldn't) exist.
            }

            db.executeSimpleSQL("CREATE TABLE cal_calendars_v6 " +
                                "(id   INTEGER PRIMARY KEY," +
                                " type TEXT," +
                                " uri  TEXT);");

            db.executeSimpleSQL("CREATE TABLE cal_calendars_prefs_v6 " +
                                "(id       INTEGER PRIMARY KEY," +
                                " calendar INTEGER," +
                                " name     TEXT," +
                                " value    TEXT);");

            // Copy in the data.
            var calendarCols = ["id", "type", "uri"];
            var calendarPrefsCols = ["id", "calendar", "name", "value"];

            db.executeSimpleSQL("INSERT INTO cal_calendars_v6(" + calendarCols.join(",") + ") " +
                                "     SELECT " + calendarCols.join(",") +
                                "       FROM cal_calendars");

            db.executeSimpleSQL("INSERT INTO cal_calendars_prefs_v6(" + calendarPrefsCols.join(",") + ") " +
                                "     SELECT " + calendarPrefsCols.join(",") +
                                "       FROM cal_calendars_prefs");

            // Delete each old table and rename the new ones to use the
            // old tables' names.
            var tableNames = ["cal_calendars", "cal_calendars_prefs"];

            for (var i in tableNames) {
                db.executeSimpleSQL("DROP TABLE " + tableNames[i] + ";" +
                                    "ALTER TABLE " + tableNames[i] + "_v6 " +
                                    "  RENAME TO " + tableNames[i] + ";");
            }

            oldVersion = 8;
        }

        if (oldVersion < DB_SCHEMA_VERSION) {
            dump ("**** Upgrading calCalendarManager schema to 9/10\n");

            if (db.tableExists("cal_calmgr_schema_version")) {
                // Set only once the last time to v10, so the version check works in calendar 0.8.
                // In calendar 0.9 and following, the provider itself will check its version
                // on initialization and notify the calendar whether it's usable or not.
                db.executeSimpleSQL("UPDATE cal_calmgr_schema_version SET version = " + DB_SCHEMA_VERSION + ";");
            } else {
                // Schema changes in v9:
                //
                // - Decouple schema version from storage calendar
                // Create the new tables.
                db.executeSimpleSQL("CREATE TABLE cal_calmgr_schema_version (version INTEGER);");
                db.executeSimpleSQL("INSERT INTO cal_calmgr_schema_version VALUES(" + DB_SCHEMA_VERSION + ")");
            }
        }
    },

    migrateDB: function calmgr_migrateDB(db) {
        let selectCalendars = db.createStatement("SELECT * FROM cal_calendars");
        let selectPrefs = db.createStatement("SELECT name, value FROM cal_calendars_prefs WHERE calendar = :calendar");
        try {
            let sortOrder = {};

            while (selectCalendars.executeStep()) {
                let id = cal.getUUID(); // use fresh uuids
                Preferences.set(getPrefBranchFor(id) + "type", selectCalendars.row.type);
                Preferences.set(getPrefBranchFor(id) + "uri", selectCalendars.row.uri);
                // the former id served as sort position:
                sortOrder[selectCalendars.row.id] = id;
                // move over prefs:
                selectPrefs.params.calendar = selectCalendars.row.id;
                while (selectPrefs.executeStep()) {
                    let name = selectPrefs.row.name.toLowerCase(); // may come lower case, so force it to be
                    let value = selectPrefs.row.value;
                    switch (name) {
                        case "readonly":
                            Preferences.set(getPrefBranchFor(id) + "readOnly", value == "true");
                            break;
                        case "relaxedmode":
                            Preferences.set(getPrefBranchFor(id) + "relaxedMode", value == "true");
                            break;
                        case "suppressalarms":
                            Preferences.set(getPrefBranchFor(id) + "suppressAlarms", value == "true");
                            break;
                        case "disabled":
                        case "cache.supported":
                        case "auto-enabled":
                        case "cache.enabled":
                        case "lightning-main-in-composite":
                        case "calendar-main-in-composite":
                        case "lightning-main-default":
                        case "calendar-main-default":
                            Preferences.set(getPrefBranchFor(id) + name, value == "true");
                            break;
                        case "backup-time":
                        case "uniquenum":
                            // These preference names were migrated due to bug 979262.
                            Preferences.set(getPrefBranchFor(id) + name + "2", "bignum:" + value);
                            break;
                        default: // keep as string
                            Preferences.set(getPrefBranchFor(id) + name, value);
                            break;
                    }
                }
                selectPrefs.reset();
            }

            let sortOrderAr = [];
            for each (let s in sortOrder) {
                sortOrderAr.push(s);
            }
            Preferences.set("calendar.list.sortOrder", sortOrderAr.join(" "));
            flushPrefs();

        } finally {
            selectPrefs.reset();
            selectCalendars.reset();
        }
    },

    checkAndMigrateDB: function calmgr_checkAndMigrateDB() {
        let storageSdb = Services.dirsvc.get("ProfD", Components.interfaces.nsILocalFile);
        storageSdb.append("storage.sdb");
        let db = Services.storage.openDatabase(storageSdb);

        db.beginTransactionAs(Components.interfaces.mozIStorageConnection.TRANSACTION_EXCLUSIVE);
        try {
            if (db.tableExists("cal_calendars_prefs")) {
                // Check if we need to upgrade:
                let version = this.getSchemaVersion(db);
                //cal.LOG("*** Calendar schema version is: " + version);
                if (version < DB_SCHEMA_VERSION) {
                    this.upgradeDB(version, db);
                }

                this.migrateDB(db);

                db.executeSimpleSQL("DROP TABLE cal_calendars; " +
                                    "DROP TABLE cal_calendars_prefs; " +
                                    "DROP TABLE cal_calmgr_schema_version;");
            }

            if (!db.tableExists("cal_calendars")) {
                // create dummy cal_calendars, so previous versions (pre 1.0pre) run into the schema check:
                db.createTable("cal_calendars", "id INTEGER");
                // let schema checks always fail, we cannot take the shared cal_calendar_schema_version:
                db.createTable("cal_calmgr_schema_version", "version INTEGER");
                db.executeSimpleSQL("INSERT INTO cal_calmgr_schema_version VALUES(" + (DB_SCHEMA_VERSION + 1) + ")");
                db.commitTransaction();
            } else {
                db.rollbackTransaction();
            }
        } catch (exc) {
            db.rollbackTransaction();
            throw exc;
        } finally {
            db.close();
        }
    },

    /**
     * @return      db schema version
     * @exception   various, depending on error
     */
    getSchemaVersion: function calMgrGetSchemaVersion(db) {
        var stmt;
        var version = null;

        var table;
        if (db.tableExists("cal_calmgr_schema_version")) {
            table = "cal_calmgr_schema_version";
        } else {
            // Fall back to the old schema table
            table = "cal_calendar_schema_version";
        }

        try {
            stmt = db.createStatement("SELECT version FROM " + table + " LIMIT 1");
            if (stmt.executeStep()) {
                version = stmt.row.version;
            }
            stmt.reset();

            if (version !== null) {
                // This is the only place to leave this function gracefully.
                return version;
            }
        } catch (e) {
            if (stmt) {
                stmt.reset();
            }
            cal.ERROR("++++++++++++ calMgrGetSchemaVersion() error: " + db.lastErrorString);
            Components.utils.reportError("Error getting calendar schema version! DB Error: " + db.lastErrorString);
            throw e;
        }

        throw table + " SELECT returned no results";
    },

    //
    // / DB migration code ends here
    //

    alertAndQuit: function cmgr_alertAndQuit() {
        // We want to include the extension name in the error message rather
        // than blaming Thunderbird.
        var hostAppName = calGetString("brand", "brandShortName", null, "branding");
        var calAppName = calGetString("lightning", "brandShortName", null, "lightning");
        var errorBoxTitle = calGetString("calendar", "tooNewSchemaErrorBoxTitle", [calAppName]);
        var errorBoxText = calGetString("calendar", "tooNewSchemaErrorBoxTextLightning", [calAppName, hostAppName]);
        var errorBoxButtonLabel = calGetString("calendar", "tooNewSchemaButtonRestart", [hostAppName]);

        var promptSvc = Services.prompt;

        var errorBoxButtonFlags = (promptSvc.BUTTON_POS_0 *
                                   promptSvc.BUTTON_TITLE_IS_STRING +
                                   promptSvc.BUTTON_POS_0_DEFAULT);

        var choice = promptSvc.confirmEx(null,
                                         errorBoxTitle,
                                         errorBoxText,
                                         errorBoxButtonFlags,
                                         errorBoxButtonLabel,
                                         null, // No second button text
                                         null, // No third button text
                                         null, // No checkbox
                                         { value: false }); // Unnecessary checkbox state

        // Disable Lightning
        AddonManager.getAddonByID("{e2fda1a4-762b-4020-b5ad-a41df1933103}", function getLightningExt(aAddon) {
            aAddon.userDisabled = true;
            Services.startup.quit(Components.interfaces.nsIAppStartup.eRestart |
                Components.interfaces.nsIAppStartup.eForceQuit);
        });
    },

    /**
     * calICalendarManager interface
     */
    createCalendar: function cmgr_createCalendar(type, uri) {
        try {
            if (!Components.classes["@mozilla.org/calendar/calendar;1?type=" + type]) {
                // Don't notify the user with an extra dialog if the provider
                // interface is missing.
                return null;
            }
            let calendar = Components.classes["@mozilla.org/calendar/calendar;1?type=" + type]
                                     .createInstance(Components.interfaces.calICalendar);
            calendar.uri = uri;
            return calendar;
        } catch (ex) {
            let rc = ex;
            let uiMessage = ex;
            if (ex instanceof Components.interfaces.nsIException) {
                rc = ex.result;
                uiMessage = ex.message;
            }
            switch (rc) {
                case Components.interfaces.calIErrors.STORAGE_UNKNOWN_SCHEMA_ERROR:
                    // For now we alert and quit on schema errors like we've done before:
                    this.alertAndQuit();
                    return;
                case Components.interfaces.calIErrors.STORAGE_UNKNOWN_TIMEZONES_ERROR:
                    uiMessage = calGetString("calendar", "unknownTimezonesError", [uri.spec]);
                    break;
                default:
                    uiMessage = calGetString("calendar", "unableToCreateProvider", [uri.spec]);
                    break;
            }
            // Log the original exception via error console to provide more debug info
            cal.ERROR(ex);

            // Log the possibly translated message via the UI.
            let paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
                                       .createInstance(Components.interfaces.nsIDialogParamBlock);
            paramBlock.SetNumberStrings(3);
            paramBlock.SetString(0, uiMessage);
            paramBlock.SetString(1, "0x" + rc.toString(0x10));
            paramBlock.SetString(2, ex);
            Services.ww.openWindow(null,
                                   "chrome://calendar/content/calendar-error-prompt.xul",
                                   "_blank",
                                   "chrome,dialog=yes,alwaysRaised=yes",
                                   paramBlock);
            return null;
        }
    },

    registerCalendar: function(calendar) {
        this.assureCache();

        // If the calendar is already registered, bail out
        cal.ASSERT(!calendar.id || !(calendar.id in this.mCache),
                   "[calCalendarManager::registerCalendar] calendar already registered!",
                   true);

        if (!calendar.id) {
            calendar.id = cal.getUUID();
        }

        Preferences.set(getPrefBranchFor(calendar.id) + "type", calendar.type);
        Preferences.set(getPrefBranchFor(calendar.id) + "uri", calendar.uri.spec);

        if ((calendar.getProperty("cache.supported") !== false) &&
            (calendar.getProperty("cache.enabled") ||
             calendar.getProperty("cache.always"))) {
            calendar = new calCachedCalendar(calendar);
        }

        this.setupCalendar(calendar);
        flushPrefs();

        if (!calendar.getProperty("disabled") && calendar.canRefresh) {
            calendar.refresh();
        }

        this.notifyObservers("onCalendarRegistered", [calendar]);
    },

    setupCalendar: function cmgr_setupCalendar(calendar) {
        this.mCache[calendar.id] = calendar;

        // Add an observer to track readonly-mode triggers
        var newObserver = new calMgrCalendarObserver(calendar, this);
        calendar.addObserver(newObserver);
        this.mCalObservers[calendar.id] = newObserver;

        // Set up statistics
        if (calendar.getProperty("requiresNetwork") !== false) {
            this.mNetworkCalendarCount++;
        }
        if (calendar.readOnly) {
            this.mReadonlyCalendarCount++;
        }
        this.mCalendarCount++;

        // Set up the refresh timer
        this.setupRefreshTimer(calendar);
    },

    setupRefreshTimer: function setupRefreshTimer(aCalendar) {
        // Add the refresh timer for this calendar
        let refreshInterval = aCalendar.getProperty("refreshInterval");
        if (refreshInterval === null) {
            // Default to 30 minutes, in case the value is missing
            refreshInterval = 30;
        }

        this.clearRefreshTimer(aCalendar);

        if (refreshInterval > 0) {
            this.mRefreshTimer[aCalendar.id] =
                Components.classes["@mozilla.org/timer;1"]
                          .createInstance(Components.interfaces.nsITimer);

            this.mRefreshTimer[aCalendar.id]
                .initWithCallback(new timerCallback(aCalendar),
                                  refreshInterval * 60000,
                                  Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
        }
    },

    clearRefreshTimer: function clearRefreshTimer(aCalendar) {
        if (aCalendar.id in this.mRefreshTimer &&
            this.mRefreshTimer[aCalendar.id]) {
            this.mRefreshTimer[aCalendar.id].cancel();
            delete this.mRefreshTimer[aCalendar.id]
        }
    },

    unregisterCalendar: function(calendar) {
        this.notifyObservers("onCalendarUnregistering", [calendar]);

        // calendar may be a calICalendar wrapper:
        if (calendar.wrappedJSObject instanceof calCachedCalendar) {
            calendar.wrappedJSObject.onCalendarUnregistering();
        }

        calendar.removeObserver(this.mCalObservers[calendar.id]);
        Services.prefs.deleteBranch(getPrefBranchFor(calendar.id));
        flushPrefs();

        if (this.mCache) {
            delete this.mCache[calendar.id];
        }

        if (calendar.readOnly) {
            this.mReadonlyCalendarCount--;
        }

        if (calendar.getProperty("requiresNetwork") !== false) {
            this.mNetworkCalendarCount--;
        }
        this.mCalendarCount--;

        this.clearRefreshTimer(calendar);
    },

    deleteCalendar: function(calendar) {
        /* check to see if calendar is unregistered first... */
        /* delete the calendar for good */
        if (this.mCache && (calendar.id in this.mCache)) {
            throw "Can't delete a registered calendar";
        }
        this.notifyObservers("onCalendarDeleting", [calendar]);

        // XXX This is a workaround for bug 351499. We should remove it once
        // we sort out the whole "delete" vs. "unsubscribe" UI thing.
        //
        // We only want to delete the contents of calendars from local
        // providers (storage and memory). Otherwise we may nuke someone's
        // calendar stored on a server when all they really wanted to do was
        // unsubscribe.
        let wrappedCalendar = cal.wrapInstance(calendar, Components.interfaces.calICalendarProvider);
        if (wrappedCalendar &&
            (wrappedCalendar.type == "storage" || wrappedCalendar.type == "memory")) {
            try {
                wrappedCalendar.deleteCalendar(calendar, null);
            } catch (e) {
                Components.utils.reportError("error purging calendar: " + e);
            }
        }
    },

    getCalendarById: function cmgr_getCalendarById(aId) {
        if (aId in this.mCache) {
            return this.mCache[aId];
        } else {
            return null;
        }
    },

    getCalendars: function cmgr_getCalendars(count) {
        this.assureCache();
        var calendars = [];
        for each (var calendar in this.mCache) {
            calendars.push(calendar);
        }
        count.value = calendars.length;
        return calendars;
    },

    assureCache: function cmgr_assureCache() {
        if (!this.mCache) {
            this.mCache = {};
            this.mCalObservers = {};

            let allCals = {};
            for each (let key in Services.prefs.getChildList(REGISTRY_BRANCH)) { // merge down all keys
                allCals[key.substring(0, key.indexOf(".", REGISTRY_BRANCH.length))] = true;
            }

            for (let calBranch in allCals) {
                let id = calBranch.substring(REGISTRY_BRANCH.length);
                let ctype = Preferences.get(calBranch + ".type", null);
                let curi = Preferences.get(calBranch + ".uri", null);

                try {
                    if (!ctype || !curi) { // sanity check
                        Services.prefs.deleteBranch(calBranch + ".");
                        continue;
                    }

                    let uri = cal.makeURL(curi);
                    let calendar = this.createCalendar(ctype, uri);
                    if (calendar) {
                        calendar.id = id;
                        if (calendar.getProperty("auto-enabled")) {
                            calendar.deleteProperty("disabled");
                            calendar.deleteProperty("auto-enabled");
                        }

                        if ((calendar.getProperty("cache.supported") !== false) &&
                            (calendar.getProperty("cache.enabled") ||
                             calendar.getProperty("cache.always"))) {
                            calendar = new calCachedCalendar(calendar);
                        }
                    } else { // create dummy calendar that stays disabled for this run:
                        calendar = new calDummyCalendar(ctype);
                        calendar.id = id;
                        calendar.uri = uri;
                        // try to enable on next startup if calendar has been enabled:
                        if (!calendar.getProperty("disabled")) {
                            calendar.setProperty("auto-enabled", true);
                        }
                        calendar.setProperty("disabled", true);
                    }

                    this.setupCalendar(calendar);
                } catch (exc) {
                    cal.ERROR("Can't create calendar for " + id + " (" + ctype + ", " + curi + "): " + exc);
                }
            }

            // do refreshing in a second step, when *all* calendars are already available
            // via getCalendars():
            for each (let calendar in this.mCache) {
                if (!calendar.getProperty("disabled") && calendar.canRefresh) {
                    calendar.refresh();
                }
            }
        }
    },

    getCalendarPref_: function(calendar, name) {
        cal.ASSERT(calendar, "Invalid Calendar!");
        cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
        cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");

        let branch = (getPrefBranchFor(calendar.id) + name);
        let value = Preferences.get(branch, null);

        if (typeof value == "string" && value.startsWith("bignum:")) {
            let converted = Number(value.substr(7));
            if (!isNaN(converted)) {
                value = converted;
            }
        }
        return value;
    },

    setCalendarPref_: function(calendar, name, value) {
        cal.ASSERT(calendar, "Invalid Calendar!");
        cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
        cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");

        let branch = (getPrefBranchFor(calendar.id) + name);

        if (typeof value == "number" && (value > MAX_INT || value < MIN_INT || !Number.isInteger(value))) {
            // This is something the preferences service can't store directly.
            // Convert to string and tag it so we know how to handle it.
            value = "bignum:" + value;
        }

        // Delete before to allow pref-type changes, then set the pref.
        Services.prefs.deleteBranch(branch);
        if (value !== null && value !== undefined) {
            Preferences.set(branch, value);
        }
    },

    deleteCalendarPref_: function(calendar, name) {
        cal.ASSERT(calendar, "Invalid Calendar!");
        cal.ASSERT(calendar.id !== null, "Calendar id needs to be set!");
        cal.ASSERT(name && name.length > 0, "Pref Name must be non-empty!");
        Services.prefs.deleteBranch(getPrefBranchFor(calendar.id) + name);
    },

    mObservers: null,
    addObserver: function(aObserver) this.mObservers.add(aObserver),
    removeObserver: function(aObserver) this.mObservers.remove(aObserver),
    notifyObservers: function(functionName, args) this.mObservers.notify(functionName, args),

    mCalendarObservers: null,
    addCalendarObserver: function(aObserver) this.mCalendarObservers.add(aObserver),
    removeCalendarObserver: function(aObserver) this.mCalendarObservers.remove(aObserver),
    notifyCalendarObservers: function(functionName, args) this.mCalendarObservers.notify(functionName, args)
};

function equalMessage(msg1, msg2) {
    if (msg1.GetString(0) == msg2.GetString(0) &&
        msg1.GetString(1) == msg2.GetString(1) &&
        msg1.GetString(2) == msg2.GetString(2)) {
        return true;
    }
    return false;
}

function calMgrCalendarObserver(calendar, calMgr) {
    this.calendar = calendar;
    // We compare this to determine if the state actually changed.
    this.storedReadOnly = calendar.readOnly;
    this.announcedMessages = [];
    this.calMgr = calMgr;
}

calMgrCalendarObserver.prototype = {
    calendar: null,
    storedReadOnly: null,
    calMgr: null,

    QueryInterface: XPCOMUtils.generateQI([
        Components.interfaces.nsIWindowMediatorListener,
        Components.interfaces.calIObserver
    ]),

    // calIObserver:
    onStartBatch: function() this.calMgr.notifyCalendarObservers("onStartBatch", arguments),
    onEndBatch: function() this.calMgr.notifyCalendarObservers("onEndBatch", arguments),
    onLoad: function(calendar) this.calMgr.notifyCalendarObservers("onLoad", arguments),
    onAddItem: function(aItem) this.calMgr.notifyCalendarObservers("onAddItem", arguments),
    onModifyItem: function(aNewItem, aOldItem) this.calMgr.notifyCalendarObservers("onModifyItem", arguments),
    onDeleteItem: function(aDeletedItem) this.calMgr.notifyCalendarObservers("onDeleteItem", arguments),
    onError: function(aCalendar, aErrNo, aMessage) {
        this.calMgr.notifyCalendarObservers("onError", arguments);
        this.announceError(aCalendar, aErrNo, aMessage);
    },

    onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
        this.calMgr.notifyCalendarObservers("onPropertyChanged", arguments);
        switch (aName) {
            case "requiresNetwork":
                this.calMgr.mNetworkCalendarCount += (aValue ? 1 : -1);
                break;
            case "readOnly":
                this.calMgr.mReadonlyCalendarCount += (aValue ? 1 : -1);
                break;
            case "refreshInterval":
                this.calMgr.setupRefreshTimer(aCalendar);
                break;
            case "cache.enabled":
                this.changeCalendarCache.apply(this, arguments);
                break;
            case "disabled":
                if (!aValue && aCalendar.canRefresh) {
                    aCalendar.refresh();
                }
                break;
        }
    },

    changeCalendarCache: function(aCalendar, aName, aValue, aOldValue) {
        aOldValue = aOldValue || false;
        aValue = aValue || false;

        if (aOldValue != aValue) {
            // Try to find the current sort order
            let sortOrderPref = Preferences.get("calendar.list.sortOrder", "").split(" ");
            let initialSortOrderPos = null;
            for (let i = 0; i < sortOrderPref.length; ++i) {
                if (sortOrderPref[i] == aCalendar.id) {
                    initialSortOrderPos = i;
                }
            }
            // Enabling or disabling cache on a calendar re-creates
            // it so the registerCalendar call can wrap/unwrap the
            // calCachedCalendar facade saving the user the need to
            // restart Thunderbird and making sure a new Id is used.
            this.calMgr.unregisterCalendar(aCalendar);
            this.calMgr.deleteCalendar(aCalendar);
            var newCal = this.calMgr.createCalendar(aCalendar.type,aCalendar.uri);
            newCal.name = aCalendar.name;

            // TODO: if properties get added this list will need to be adjusted,
            // ideally we should add a "getProperties" method to calICalendar.idl
            // to retrieve all non-transient properties for a calendar.
            let propsToCopy = [ "color",
                                "disabled",
                                "auto-enabled",
                                "cache.enabled",
                                "refreshInterval",
                                "suppressAlarms",
                                "calendar-main-in-composite",
                                "calendar-main-default",
                                "readOnly",
                                "imip.identity.key"];
            for each (let prop in propsToCopy ) {
              newCal.setProperty(prop,
                                 aCalendar.getProperty(prop));
            }

            if (initialSortOrderPos != null) {
                newCal.setProperty("initialSortOrderPos",
                                   initialSortOrderPos);
            }
            this.calMgr.registerCalendar(newCal);
        } else {
            if (aCalendar.wrappedJSObject instanceof calCachedCalendar) {
                // any attempt to switch this flag will reset the cached calendar;
                // could be useful for users in case the cache may be corrupted.
                aCalendar.wrappedJSObject.setupCachedCalendar();
            }
        }
    },

    onPropertyDeleting: function(aCalendar, aName) {
        this.onPropertyChanged(aCalendar, aName, false, true);
    },

    // Error announcer specific functions
    announceError: function(aCalendar, aErrNo, aMessage) {

        var paramBlock = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
                                   .createInstance(Components.interfaces.nsIDialogParamBlock);
        var props = Services.strings.createBundle("chrome://calendar/locale/calendar.properties");
        var errMsg;
        paramBlock.SetNumberStrings(3);
        if (!this.storedReadOnly && this.calendar.readOnly) {
            // Major errors change the calendar to readOnly
            errMsg = props.formatStringFromName("readOnlyMode", [this.calendar.name], 1);
        } else if (!this.storedReadOnly && !this.calendar.readOnly) {
            // Minor errors don't, but still tell the user something went wrong
            errMsg = props.formatStringFromName("minorError", [this.calendar.name], 1);
        } else {
            // The calendar was already in readOnly mode, but still tell the user
            errMsg = props.formatStringFromName("stillReadOnlyError", [this.calendar.name], 1);
        }

        // When possible, change the error number into its name, to
        // make it slightly more readable.
        var errCode = "0x"+aErrNo.toString(16);
        const calIErrors = Components.interfaces.calIErrors;
        // Check if it is worth enumerating all the error codes.
        if (aErrNo & calIErrors.ERROR_BASE) {
            for (var err in calIErrors) {
                if (calIErrors[err] == aErrNo) {
                    errCode = err;
                }
            }
        }

        var message;
        switch (aErrNo) {
            case calIErrors.CAL_UTF8_DECODING_FAILED:
                message = props.GetStringFromName("utf8DecodeError");
                break;
            case calIErrors.ICS_MALFORMEDDATA:
                message = props.GetStringFromName("icsMalformedError");
                break;
            case calIErrors.MODIFICATION_FAILED:
                errMsg = calGetString("calendar", "errorWriting", [aCalendar.name]);
             default:
                message = aMessage;
         }


        paramBlock.SetString(0, errMsg);
        paramBlock.SetString(1, errCode);
        paramBlock.SetString(2, message);

        this.storedReadOnly = this.calendar.readOnly;
        var errorCode = calGetString("calendar","errorCode", [errCode]);
        var errorDescription = calGetString("calendar","errorDescription", [message]);
        var summary = errMsg + " " + errorCode + ". " + errorDescription;

        // Log warnings in error console.
        // Report serious errors in both error console and in prompt window.
        var isSerious = (aErrNo == calIErrors.MODIFICATION_FAILED);
        if (!isSerious) {
            WARN(summary);
        } else {
            // Write error to console.
            Components.utils.reportError(summary);

            // silently don't do anything if this message already has
            // been announced without being acknowledged.
            if (this.announcedMessages.some(
                function(element, index, array) {
                    return equalMessage(paramBlock, element);
                })) {
                return;
            }

            // this message hasn't been announced recently, remember the
            // details of the message for future reference.
            this.announcedMessages.push(paramBlock);

            // Display in prompt window.
            var promptWindow =
                Services.ww.openWindow
                    (null, "chrome://calendar/content/calendar-error-prompt.xul",
                     "_blank", "chrome,dialog=yes,alwaysRaised=yes",
                     paramBlock);
            // Will remove paramBlock from announced messages when
            // promptWindow is closed.  (Closing fires unloaded event, but
            // promptWindow is also unloaded [to clean it?] before loading,
            // so wait for detected load event before detecting unload event
            // that signifies user closed this prompt window.)
            var observer = this;
            function awaitLoad(event) {
                // #2 loaded, remove load listener
                promptWindow.removeEventListener("load", awaitLoad, false);
                function awaitUnload(event) {
                    // #4 unloaded (user closed prompt window),
                    // remove paramBlock and unload listener.
                    try {
                        // remove the message that has been shown from
                        // the list of all announced messages.
                        observer.announcedMessages =
                            observer.announcedMessages.filter(function(msg) {
                                return !equalMessage(msg, paramBlock);
                            });
                        promptWindow.removeEventListener("unload", awaitUnload,
                                                         false);
                    } catch (e) {
                        Components.utils.reportError(e);
                    }
                }
                // #3 add unload listener (wait for user to close promptWindow)
                promptWindow.addEventListener("unload", awaitUnload, false);
            }
            // #1 add load listener
            promptWindow.addEventListener("load", awaitLoad, false);
        }
    }
};

function calDummyCalendar(type) {
    this.initProviderBase();
    this.type = type;
}
calDummyCalendar.prototype = {
    __proto__: cal.ProviderBase.prototype,

    getProperty: function calDummyCalendar_getProperty(aName) {
        switch (aName) {
            case "force-disabled":
                return true;
            default:
                return this.__proto__.__proto__.getProperty.apply(this, arguments);
        }
    }
};

function getPrefBranchFor(id) {
    return (REGISTRY_BRANCH + id + ".");
}

/**
 * Helper function to flush the preferences file. If the application crashes
 * after a calendar has been created using the prefs registry, then the calendar
 * won't show up. Writing the prefs helps counteract.
 */
function flushPrefs() {
    Services.prefs.savePrefFile(null);
}

/**
 * Callback object for the refresh timer. Should be called as an object, i.e
 * let foo = new timerCallback(calendar);
 *
 * @param aCalendar     The calendar to refresh on notification
 */
function timerCallback(aCalendar) {
    this.notify = function refreshNotify(aTimer) {
        if (!aCalendar.getProperty("disabled") && aCalendar.canRefresh) {
            aCalendar.refresh();
        }
    }
}

var gCalendarManagerAddonListener = {
    onDisabling: function(aAddon, aNeedsRestart) {
        if (!this.queryUninstallProvider(aAddon)) {
            // If the addon should not be disabled, then re-enable it.
            aAddon.userDisabled = false;
        }
    },

    onUninstalling: function(aAddon, aNeedsRestart) {
        if (!this.queryUninstallProvider(aAddon)) {
            // If the addon should not be uninstalled, then cancel the uninstall.
            aAddon.cancelUninstall();
        }
    },

    queryUninstallProvider: function(aAddon) {
        const uri = "chrome://calendar/content/calendar-providerUninstall-dialog.xul";
        const features = "chrome,titlebar,resizable,modal";
        let calMgr = cal.getCalendarManager();
        let affectedCalendars =
            [ calendar for each (calendar in calMgr.getCalendars({}))
              if (calendar.providerID == aAddon.id) ];
        if (!affectedCalendars.length) {
            // If no calendars are affected, then everything is fine.
            return true;
        }

        let args = { shouldUninstall: false, extension: aAddon };

        // Now find a window. The best choice would be the most recent
        // addons window, otherwise the most recent calendar window, or we
        // create a new toplevel window.
        let win = Services.wm.getMostRecentWindow("Extension:Manager") ||
                  cal.getCalendarWindow();
        if (win) {
            win.openDialog(uri, "CalendarProviderUninstallDialog", features, args);
        } else {
            // Use the window watcher to open a parentless window.
            Services.ww.openWindow(null, uri, "CalendarProviderUninstallWindow", features, args);
        }

        // Now that we are done, check if the dialog was accepted or canceled.
        return args.shouldUninstall;
    }
};

function appendToRealm(authHeader, appendStr) {
    let isEscaped = false;
    let idx = authHeader.search(/realm="(.*?)(\\*)"/);
    if (idx > -1) {
        let remain = authHeader.substr(idx + 7); idx += 7;
        while (remain.length && !isEscaped) {
            let m = remain.match(/(.*?)(\\*)"/);
            idx += m[0].length;

            isEscaped = ((m[2].length % 2) == 0);
            if (!isEscaped) {
                remain = remain.substr(m[0].length);
            }
        }
        return authHeader.substr(0, idx - 1) + " " +
                appendStr + authHeader.substr(idx - 1);
    } else {
        return authHeader;
    }
}

Cecilia Torres

unread,
Apr 16, 2015, 3:01:32 AM4/16/15
to provider-for-g...@googlegroups.com
Thanks for the try Bruce! Though I'm afraid that's all quite a bit above my technical skills level....


On Tuesday, January 13, 2015 at 12:53:01 AM UTC+1, rshin...@googlemail.com wrote:
Reply all
Reply to author
Forward
0 new messages