dom/src/notification/NotificationDB.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = [];
     9 const DEBUG = false;
    10 function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
    12 const Cu = Components.utils;
    13 const Cc = Components.classes;
    14 const Ci = Components.interfaces;
    16 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    17 Cu.import("resource://gre/modules/osfile.jsm");
    19 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
    20                                    "@mozilla.org/parentprocessmessagemanager;1",
    21                                    "nsIMessageListenerManager");
    23 XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
    24   return new TextEncoder();
    25 });
    27 XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
    28   return new TextDecoder();
    29 });
    32 const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
    33 const NOTIFICATION_STORE_PATH =
    34         OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
    36 let NotificationDB = {
    37   init: function() {
    38     this.notifications = {};
    39     this.byTag = {};
    40     this.loaded = false;
    42     this.tasks = []; // read/write operation queue
    43     this.runningTask = false;
    45     ppmm.addMessageListener("Notification:Save", this);
    46     ppmm.addMessageListener("Notification:Delete", this);
    47     ppmm.addMessageListener("Notification:GetAll", this);
    48   },
    50   // Attempt to read notification file, if it's not there we will create it.
    51   load: function(callback) {
    52     var promise = OS.File.read(NOTIFICATION_STORE_PATH);
    53     promise.then(
    54       function onSuccess(data) {
    55         try {
    56           this.notifications = JSON.parse(gDecoder.decode(data));
    57         } catch (e) {
    58           if (DEBUG) { debug("Unable to parse file data " + e); }
    59         }
    60         // populate the list of notifications by tag
    61         if (this.notifications) {
    62           for (var origin in this.notifications) {
    63             this.byTag[origin] = {};
    64             for (var id in this.notifications[origin]) {
    65               var curNotification = this.notifications[origin][id];
    66               if (curNotification.tag) {
    67                 this.byTag[origin][curNotification.tag] = curNotification;
    68               }
    69             }
    70           }
    71         }
    72         this.loaded = true;
    73         callback && callback();
    74       }.bind(this),
    76       // If read failed, we assume we have no notifications to load.
    77       function onFailure(reason) {
    78         this.loaded = true;
    79         this.createStore(callback);
    80       }.bind(this)
    81     );
    82   },
    84   // Creates the notification directory.
    85   createStore: function(callback) {
    86     var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
    87       ignoreExisting: true
    88     });
    89     promise.then(
    90       function onSuccess() {
    91         this.createFile(callback);
    92       }.bind(this),
    94       function onFailure(reason) {
    95         if (DEBUG) { debug("Directory creation failed:" + reason); }
    96         callback && callback();
    97       }
    98     );
    99   },
   101   // Creates the notification file once the directory is created.
   102   createFile: function(callback) {
   103     var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
   104     promise.then(
   105       function onSuccess(handle) {
   106         handle.close();
   107         callback && callback();
   108       },
   109       function onFailure(reason) {
   110         if (DEBUG) { debug("File creation failed:" + reason); }
   111         callback && callback();
   112       }
   113     );
   114   },
   116   // Save current notifications to the file.
   117   save: function(callback) {
   118     var data = gEncoder.encode(JSON.stringify(this.notifications));
   119     var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
   120     promise.then(
   121       function onSuccess() {
   122         callback && callback();
   123       },
   124       function onFailure(reason) {
   125         if (DEBUG) { debug("Save failed:" + reason); }
   126         callback && callback();
   127       }
   128     );
   129   },
   131   // Helper function: callback will be called once file exists and/or is loaded.
   132   ensureLoaded: function(callback) {
   133     if (!this.loaded) {
   134       this.load(callback);
   135     } else {
   136       callback();
   137     }
   138   },
   140   receiveMessage: function(message) {
   141     if (DEBUG) { debug("Received message:" + message.name); }
   143     // sendAsyncMessage can fail if the child process exits during a
   144     // notification storage operation, so always wrap it in a try/catch.
   145     function returnMessage(name, data) {
   146       try {
   147         message.target.sendAsyncMessage(name, data);
   148       } catch (e) {
   149         if (DEBUG) { debug("Return message failed, " + name); }
   150       }
   151     }
   153     switch (message.name) {
   154       case "Notification:GetAll":
   155         this.queueTask("getall", message.data, function(notifications) {
   156           returnMessage("Notification:GetAll:Return:OK", {
   157             requestID: message.data.requestID,
   158             origin: message.data.origin,
   159             notifications: notifications
   160           });
   161         });
   162         break;
   164       case "Notification:Save":
   165         this.queueTask("save", message.data, function() {
   166           returnMessage("Notification:Save:Return:OK", {
   167             requestID: message.data.requestID
   168           });
   169         });
   170         break;
   172       case "Notification:Delete":
   173         this.queueTask("delete", message.data, function() {
   174           returnMessage("Notification:Delete:Return:OK", {
   175             requestID: message.data.requestID
   176           });
   177         });
   178         break;
   180       default:
   181         if (DEBUG) { debug("Invalid message name" + message.name); }
   182     }
   183   },
   185   // We need to make sure any read/write operations are atomic,
   186   // so use a queue to run each operation sequentially.
   187   queueTask: function(operation, data, callback) {
   188     if (DEBUG) { debug("Queueing task: " + operation); }
   189     this.tasks.push({
   190       operation: operation,
   191       data: data,
   192       callback: callback
   193     });
   195     // Only run immediately if we aren't currently running another task.
   196     if (!this.runningTask) {
   197       if (DEBUG) { dump("Task queue was not running, starting now..."); }
   198       this.runNextTask();
   199     }
   200   },
   202   runNextTask: function() {
   203     if (this.tasks.length === 0) {
   204       if (DEBUG) { dump("No more tasks to run, queue depleted"); }
   205       this.runningTask = false;
   206       return;
   207     }
   208     this.runningTask = true;
   210     // Always make sure we are loaded before performing any read/write tasks.
   211     this.ensureLoaded(function() {
   212       var task = this.tasks.shift();
   214       // Wrap the task callback to make sure we immediately
   215       // run the next task after running the original callback.
   216       var wrappedCallback = function() {
   217         if (DEBUG) { debug("Finishing task: " + task.operation); }
   218         task.callback.apply(this, arguments);
   219         this.runNextTask();
   220       }.bind(this);
   222       switch (task.operation) {
   223         case "getall":
   224           this.taskGetAll(task.data, wrappedCallback);
   225           break;
   227         case "save":
   228           this.taskSave(task.data, wrappedCallback);
   229           break;
   231         case "delete":
   232           this.taskDelete(task.data, wrappedCallback);
   233           break;
   234       }
   235     }.bind(this));
   236   },
   238   taskGetAll: function(data, callback) {
   239     if (DEBUG) { debug("Task, getting all"); }
   240     var origin = data.origin;
   241     var notifications = [];
   242     // Grab only the notifications for specified origin.
   243     for (var i in this.notifications[origin]) {
   244       notifications.push(this.notifications[origin][i]);
   245     }
   246     callback(notifications);
   247   },
   249   taskSave: function(data, callback) {
   250     if (DEBUG) { debug("Task, saving"); }
   251     var origin = data.origin;
   252     var notification = data.notification;
   253     if (!this.notifications[origin]) {
   254       this.notifications[origin] = {};
   255       this.byTag[origin] = {};
   256     }
   258     // We might have existing notification with this tag,
   259     // if so we need to remove it before saving the new one.
   260     if (notification.tag && this.byTag[origin][notification.tag]) {
   261       var oldNotification = this.byTag[origin][notification.tag];
   262       delete this.notifications[origin][oldNotification.id];
   263       this.byTag[origin][notification.tag] = notification;
   264     }
   266     this.notifications[origin][notification.id] = notification;
   267     this.save(callback);
   268   },
   270   taskDelete: function(data, callback) {
   271     if (DEBUG) { debug("Task, deleting"); }
   272     var origin = data.origin;
   273     var id = data.id;
   274     if (!this.notifications[origin]) {
   275       if (DEBUG) { debug("No notifications found for origin: " + origin); }
   276       return;
   277     }
   279     // Make sure we can find the notification to delete.
   280     var oldNotification = this.notifications[origin][id];
   281     if (!oldNotification) {
   282       if (DEBUG) { debug("No notification found with id: " + id); }
   283       return;
   284     }
   286     if (oldNotification.tag) {
   287       delete this.byTag[origin][oldNotification.tag];
   288     }
   289     delete this.notifications[origin][id];
   290     this.save(callback);
   291   }
   292 };
   294 NotificationDB.init();

mercurial