Browse code

[tbird] BM-14428 Fix: tb 68 support

Nicolas Lascombes authored on 20/05/2019 12:58:38
Showing 10 changed files
... ...
@@ -41,7 +41,7 @@ BMFileHostingAPI.prototype.share = function(aOnLoad, aOnError, aThis, path, down
41 41
 };
42 42
 
43 43
 BMFileHostingAPI.prototype.unShare = function(aOnLoad, aOnError, aThis, uurl) {
44
-    let url = this._baseUrl + "/attachment/" + this._domainUid + "/" + encodeURIComponent(uurl) + "/unShare";
44
+    let url = this._baseUrl + "/filehosting/" + this._domainUid + "/" + encodeURIComponent(uurl) + "/unshare";
45 45
     return this._execute(aOnLoad, aOnError, aThis, url, "DELETE", null, null);
46 46
 };
47 47
 
... ...
@@ -167,6 +167,14 @@ var gBMRemoteChooser = {
167 167
             let opts = Components.utils.cloneInto(options, win, {cloneFunctions: true});
168 168
             win.application.setOptions(opts);
169 169
         });
170
-        rBrowser.loadURI(this._baseUri + "/chooser/#");
170
+        if (!Components.interfaces.nsIMsgCloudFileProvider) {
171
+          // TB 68 loadURI extra param
172
+          let params = {
173
+            triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()
174
+          };
175
+          rBrowser.loadURI(this._baseUri + "/chooser/#", params);
176
+        } else {
177
+          rBrowser.loadURI(this._baseUri + "/chooser/#");
178
+        }
171 179
     },
172 180
 }
173 181
\ No newline at end of file
... ...
@@ -19,15 +19,23 @@
19 19
 */
20 20
 
21 21
 Components.utils.import("resource://gre/modules/Services.jsm");
22
+try {
23
+	Components.utils.import("resource:///modules/cloudFileAccounts.js");
24
+} catch(e) {
25
+	//TB 68
26
+	Components.utils.import("resource:///modules/cloudFileAccounts.jsm");
27
+}
22 28
 Components.utils.import("resource://bm/bmUtils.jsm");
23 29
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
24 30
 Components.utils.import("resource://bm/core2/BMAuthService.jsm");
25 31
 Components.utils.import("resource://gre/modules/osfile.jsm");
26 32
 
27 33
 function canAttachFilesFromHosting() {
28
-    let fileProvider = Components.classes["@mozilla.org/mail/bmFileProvider;1"]
29
-                                .getService(Components.interfaces.nsIMsgCloudFileProvider);
30
-    return fileProvider.wrappedJSObject.canUseFilehosting();
34
+    let accs = cloudFileAccounts.getAccountsForType("BlueMind");
35
+    if (accs.length > 0) {
36
+        return accs[0].wrappedJSObject.canUseFilehosting();
37
+    }
38
+    return false;
31 39
 }
32 40
 
33 41
 function attachFilesFromHosting() {
... ...
@@ -61,27 +69,52 @@ function onRemoteFileChoosed(aFiles) {
61 69
         attachment.name = choosed.name;
62 70
         attachment.size = choosed.size;
63 71
         attachment.sendViaCloud = true;
64
-        attachment.cloudProviderKey = provider.accountKey;
65
-        
66
-        files.push(file);
67
-        attachments.push(attachment);
72
+        if (Components.interfaces.nsIMsgCloudFileProvider) {
73
+            attachment.cloudProviderKey = provider.accountKey;
74
+            files.push(file);
75
+            attachments.push(attachment);
76
+        } else {
77
+            // TB 68
78
+            attachment.cloudFileAccountKey = provider.accountKey;
79
+            let listener = {
80
+                onStartRequest: function() {},
81
+                onStopRequest: function(p, ctx, cr) {
82
+                    if (!Components.isSuccessCode(cr)) {
83
+                        return;
84
+                    }
85
+                    attachment.contentLocation = provider.urlForFile(file);
86
+                    AddAttachments([attachment], function(item) {
87
+                        item.account = provider;
88
+                        item.setAttribute("name", file.leafName);
89
+                        item.image = provider.iconURL;
90
+                        item.cloudFileUpload = {};
91
+                        item.dispatchEvent(
92
+                            new CustomEvent("attachment-uploaded", { bubbles: true, cancelable: true })
93
+                        );
94
+                    });
95
+                }
96
+            }
97
+            provider.shareFile(file, listener);
98
+        }
68 99
     }
69 100
     
70
-    let i = 0;
71
-    AddAttachments(attachments, function(aItem) {
72
-        let listener = new uploadListener(attachments[i], files[i], provider);
73
-        try {
74
-            provider.shareFile(files[i], listener);
75
-        }
76
-        catch (ex) {
77
-            Components.utils.reportError(ex);
78
-            listener.onStopRequest(null, null, ex.result);
79
-        }
80
-        i++;
81
-    });
101
+    if (Components.interfaces.nsIMsgCloudFileProvider) {
102
+        let i = 0;
103
+        AddAttachments(attachments, function(aItem) {
104
+            let listener = new uploadListener(attachments[i], files[i], provider);
105
+            try {
106
+                provider.shareFile(files[i], listener);
107
+            }
108
+            catch (ex) {
109
+                Components.utils.reportError(ex);
110
+                listener.onStopRequest(null, null, ex.result);
111
+            }
112
+            i++;
113
+        });
82 114
 
83
-    dispatchAttachmentBucketEvent("attachments-uploading", attachments);
84
-    SetLastAttachDirectory(files[files.length-1]);
115
+        dispatchAttachmentBucketEvent("attachments-uploading", attachments);
116
+        SetLastAttachDirectory(files[files.length-1]);
117
+    }
85 118
 }
86 119
 
87 120
 if (!Services.io.offline && bmUtils.getSettings({}, {}, {}, false)) {
... ...
@@ -360,7 +393,15 @@ var gBMCompose = {
360 393
             let box = document.getElementById("bmSignature");
361 394
             box.setAttribute("collapsed", "false");
362 395
             let browser = document.getElementById("bm-browser-signature");
363
-            browser.loadURI(uri.spec);
396
+            if (!Components.interfaces.nsIMsgCloudFileProvider) {
397
+                // TB 68 loadURI extra param
398
+                let params = {
399
+                  triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()
400
+                };
401
+                browser.loadURI(uri.spec, params);
402
+              } else {
403
+                browser.loadURI(uri.spec);
404
+              }
364 405
         });
365 406
     },
366 407
     _hideSignature: function() {
... ...
@@ -21,11 +21,20 @@
21 21
 /* Main window overlay */
22 22
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
23 23
 Components.utils.import("resource://gre/modules/Http.jsm");
24
-Components.utils.import("resource:///modules/cloudFileAccounts.js");
24
+try {
25
+	Components.utils.import("resource:///modules/cloudFileAccounts.js");
26
+} catch(e) {
27
+	//TB 68
28
+	Components.utils.import("resource:///modules/cloudFileAccounts.jsm");
29
+}
25 30
 Components.utils.import("resource://bm/bmUtils.jsm");
26 31
 Components.utils.import("resource://bm/bmService.jsm");
27 32
 Components.utils.import("resource://bm/core2/BMAuthService.jsm");
28 33
 
34
+if (!Components.interfaces.nsIMsgCloudFileProvider) {
35
+	Components.utils.import("resource://bm/bmFileProvider.jsm");
36
+}
37
+
29 38
 var gBMOverlay = {
30 39
     syncObserver : new BMSyncObserver(),
31 40
     _logger: Components.classes["@blue-mind.net/logger;1"].getService().wrappedJSObject.getLogger("gBMOverlay: "),
... ...
@@ -92,41 +101,35 @@ var gBMOverlay = {
92 101
         }
93 102
     },
94 103
 	initFileProvider: function() {
95
-		let createAccountObserver = {
96
-			onStartRequest: function(aRequest, aContext) {},
97
-			onStopRequest: function(aRequest, aContext, aStatusCode) {
98
-				if (aStatusCode == Cr.NS_OK
99
-					&& aContext instanceof Ci.nsIMsgCloudFileProvider) {
100
-					//ok
101
-				} else {
102
-					if (aContext instanceof Ci.nsIMsgCloudFileProvider) {
103
-						cloudFileAccounts.removeAccount(aContext.accountKey);
104
-					} else {
105
-						// Something went seriously wrong here...
106
-						Components.utils.reportError("Cloud account creation failed, and " +
107
-							"provider instance missing!");
108
-					}
109
-				}
110
-			},
111
-		}
112 104
 		if (bmUtils.getSettings({}, {}, {}, true, window)) {
113
-			let fileProvider = Components.classes["@mozilla.org/mail/bmFileProvider;1"]
114
-									.getService(Components.interfaces.nsIMsgCloudFileProvider);
115
-			fileProvider.refreshUserInfo(true, {
116
-				onStartRequest: function(){},
117
-				onStopRequest: function(){
118
-					let accs = cloudFileAccounts.getAccountsForType("BlueMind");
119
-					if (fileProvider.wrappedJSObject.canRemoteAttach()) {
120
-						if (accs.length == 0) {
121
-							cloudFileAccounts.createAccount("BlueMind", createAccountObserver);
122
-						}
123
-					} else {
124
-						if (accs.length == 1) {
125
-							cloudFileAccounts.removeAccount(accs[0].accountKey);
105
+			let accs = cloudFileAccounts.getAccountsForType("BlueMind");
106
+			let fileProvider;
107
+			if (accs.length == 0) {
108
+				fileProvider = cloudFileAccounts.createAccount("BlueMind", {
109
+					onStartRequest: function(){},
110
+					onStopRequest: function(){}
111
+				});
112
+			} else {
113
+				fileProvider = accs[0];
114
+			}
115
+			if (Components.interfaces.nsIMsgCloudFileProvider
116
+				 && !cloudFileAccounts._providers.has(fileProvider.type)) {
117
+				// workaround TB < 57 provider not registred correctly by XPCOM category
118
+				cloudFileAccounts._providers.set(fileProvider.type, fileProvider);
119
+			}
120
+			try {
121
+				fileProvider.refreshUserInfo(true, {
122
+					onStartRequest: function(){},
123
+					onStopRequest: function(){
124
+						if (!fileProvider.wrappedJSObject.canRemoteAttach()) {
125
+							cloudFileAccounts.removeAccount(fileProvider.accountKey);
126 126
 						}
127 127
 					}
128
-				}
129
-			});
128
+				});
129
+			} catch(e) {
130
+				this._logger.error("Fail to init File provider: " + e);
131
+				cloudFileAccounts.removeAccount(fileProvider.accountKey);
132
+			}
130 133
 		}
131 134
 	},
132 135
     _auth: function(aServer, aApps, aLogin, aPassword) {
... ...
@@ -7,58 +7,57 @@
7 7
 <?xml-stylesheet href="chrome://bm/skin/style.css" type="text/css"?>
8 8
 <!DOCTYPE window SYSTEM "chrome://bm/locale/bm.dtd">
9 9
 
10
-<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
10
+<!-- TB >= 67 copy of pur XUL bmPreferences.xul -->
11
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
11 12
             xmlns:html="http://www.w3.org/1999/xhtml"
12 13
             id="bmprefs"
13 14
             title="&bm.preferences.tab.label;"
14 15
             onload="gBMPreferences.init();">
15
-    <prefpane id="paneBM">
16
-        <preferences>
17
-            <preference id="pref_logdebug" name="extensions.bm.log.debug" type="bool"/>
18
-        </preferences>
19
-            <groupbox>
20
-                <caption label="&bm.preferences.server.label;"/>
21
-                <hbox align="center">
22
-                    <label value="&bm.preferences.server.address.label;"/>
23
-                    <textbox id="bmserver" flex="1" class="uri-element"/>
24
-                    <button label="&bm.preferences.server.button.label;" oncommand="gBMPreferences.promptUsernameAndPassword();"/>
25
-                </hbox>
26
-            </groupbox>
27
-            <groupbox>
28
-                <caption label="&bm.preferences.calendar.label;"/>
29
-                <hbox align="center">
30
-                    <label value="&bm.preferences.calendar.calendar.label;"/>
31
-                    <spacer flex="1"/>
32
-                    <button label="&bm.preferences.calendar.button.label;" oncommand="gBMPreferences.openCalendarPrefDialog();"/>
33
-                </hbox>
34
-            </groupbox>
35
-            <groupbox>
36
-                <caption label="&bm.preferences.im.label;"/>
37
-                <hbox align="center">
38
-                    <label value="&bm.preferences.im.add.label;"/>
39
-                    <spacer flex="1"/>
40
-                    <button label="&bm.preferences.im.button.add.label;" oncommand="gBMPreferences.addXMPPAccount();"/>
41
-                </hbox>
42
-            </groupbox>
43
-            <groupbox>
44
-                <caption label="&bm.preferences.mail.label;"/>
45
-                <hbox align="center">
46
-                    <label value="&bm.preferences.mail.vacation.label;"/>
47
-                    <spacer flex="1"/>
48
-                    <button label="&bm.preferences.mail.button.vacation.label;" oncommand="gBMPreferences.openVacationDialog();"/>
49
-                </hbox>
50
-            </groupbox>
51
-            <groupbox>
52
-                <caption label="&bm.preferences.log.label;"/>
53
-                <hbox align="center">
54
-                    <checkbox preference="pref_logdebug" label="&bm.preferences.log.pref.label;"/>
55
-                    <spacer flex="1"/>
56
-                    <button id="bm-button-showlog"
57
-                            label="&bm.preferences.showlog.button.label;"
58
-                            tooltiptext="&bm.preferences.showlog.button.tooltiptext;"
59
-                            oncommand="gBMPreferences.showLog();" align="right"/>
60
-                </hbox>
61
-            </groupbox>
16
+        <groupbox>
17
+            <caption label="&bm.preferences.server.label;"/>
18
+            <hbox align="center">
19
+                <label value="&bm.preferences.server.address.label;"/>
20
+                <textbox id="bmserver" flex="1" class="uri-element"/>
21
+                <button label="&bm.preferences.server.button.label;" oncommand="gBMPreferences.promptUsernameAndPassword();"/>
22
+            </hbox>
23
+        </groupbox>
24
+        <groupbox>
25
+            <caption label="&bm.preferences.calendar.label;"/>
26
+            <hbox align="center">
27
+                <label value="&bm.preferences.calendar.calendar.label;"/>
28
+                <spacer flex="1"/>
29
+                <button label="&bm.preferences.calendar.button.label;" oncommand="gBMPreferences.openCalendarPrefDialog();"/>
30
+            </hbox>
31
+        </groupbox>
32
+        <groupbox>
33
+            <caption label="&bm.preferences.im.label;"/>
34
+            <hbox align="center">
35
+                <label value="&bm.preferences.im.add.label;"/>
36
+                <spacer flex="1"/>
37
+                <button label="&bm.preferences.im.button.add.label;" oncommand="gBMPreferences.addXMPPAccount();"/>
38
+            </hbox>
39
+        </groupbox>
40
+        <groupbox>
41
+            <caption label="&bm.preferences.mail.label;"/>
42
+            <hbox align="center">
43
+                <label value="&bm.preferences.mail.vacation.label;"/>
44
+                <spacer flex="1"/>
45
+                <button label="&bm.preferences.mail.button.vacation.label;" oncommand="gBMPreferences.openVacationDialog();"/>
46
+            </hbox>
47
+        </groupbox>
48
+        <groupbox>
49
+            <caption label="&bm.preferences.log.label;"/>
50
+            <hbox align="center">
51
+                <checkbox id="bm-checkbox-debug"
52
+                     label="&bm.preferences.log.pref.label;"
53
+                     onclick="gBMPreferences.toggleDebug();"/>
54
+                <spacer flex="1"/>
55
+                <button id="bm-button-showlog"
56
+                        label="&bm.preferences.showlog.button.label;"
57
+                        tooltiptext="&bm.preferences.showlog.button.tooltiptext;"
58
+                        oncommand="gBMPreferences.showLog();" align="right"/>
59
+            </hbox>
60
+        </groupbox>
62 61
 	    <hbox align="left">
63 62
 	      <button id="bm-button-reset"
64 63
 		      label="&bm.preferences.reset.button.label;"
... ...
@@ -69,7 +68,6 @@
69 68
 		      tooltiptext="&bm.preferences.import.button.tooltiptext;"
70 69
 		      oncommand="gBMPreferences.importCollected();"/>
71 70
 	    </hbox>
72
-    </prefpane>
73 71
     <script type="application/javascript" src="chrome://messenger/content/sanitize.js"/>
74 72
     <script type="application/javascript" src="chrome://bm/content/preferences/bmPreferences.js"/>
75
-</prefwindow>
76 73
\ No newline at end of file
74
+</dialog>
77 75
\ No newline at end of file
... ...
@@ -130,6 +130,11 @@ var gBMPreferences = {
130 130
     loadBmPrefs: function() {
131 131
         let bmserver = document.getElementById("bmserver");
132 132
         bmserver.value = bmUtils.getCharPref("extensions.bm.server", "https://xxxx");
133
+        let checkbox = document.getElementById("bm-checkbox-debug");
134
+        if (checkbox) {
135
+            let debug = bmUtils.getBoolPref("extensions.bm.log.debug", false);
136
+            checkbox.setAttribute("checked", "" + debug);
137
+        }
133 138
     },
134 139
     saveBmPrefs: function() {
135 140
         let oldServer = bmUtils.getCharPref("extensions.bm.server", "https://xxxx");
... ...
@@ -144,7 +149,7 @@ var gBMPreferences = {
144 149
     },
145 150
     openCalendarPrefDialog: function() {
146 151
         gBMPreferences.saveBmPrefs();
147
-        document.documentElement.openSubDialog("chrome://bm/content/preferences/bmCalendarPreferences.xul", "", null);
152
+        window.openDialog("chrome://bm/content/preferences/bmCalendarPreferences.xul", "", "modal,centerscreen,resizable=no" , null);
148 153
     },
149 154
     importCollected: function() {
150 155
         if (!bmUtils.promptService.confirm(null, bmUtils.getLocalizedString("dialogs.title"),
... ...
@@ -271,7 +276,12 @@ var gBMPreferences = {
271 276
     },
272 277
     openVacationDialog: function() {
273 278
         gBMPreferences.saveBmPrefs();
274
-        document.documentElement.openSubDialog("chrome://bm/content/preferences/bmVacation.xul", "", null);
279
+        window.openDialog("chrome://bm/content/preferences/bmVacation.xul", "", "modal,centerscreen,resizable=no", null);
280
+    },
281
+    // replace of deprecated XUL <preference> for TB >= 67 
282
+    toggleDebug: function() {
283
+        let debug = bmUtils.getBoolPref("extensions.bm.log.debug", false);
284
+        bmUtils.setBoolPref("extensions.bm.log.debug", !debug);
275 285
     }
276 286
 };
277 287
 
... ...
@@ -2,6 +2,7 @@
2 2
 
3 3
 <?xml-stylesheet href="chrome://global/skin/"?>
4 4
 <?xml-stylesheet href="chrome://messenger/content/bindings.css" type="text/css"?>
5
+<?xml-stylesheet href="chrome://messenger/skin/preferences/preferences.css" type="text/css" ?>
5 6
 <?xml-stylesheet href="chrome://bm/skin/style.css" type="text/css"?>
6 7
 <!DOCTYPE dialog SYSTEM "chrome://bm/locale/bm.dtd">
7 8
 <dialog id="bmVacation"
8 9
new file mode 100644
... ...
@@ -0,0 +1,617 @@
1
+/**
2
+ * BEGIN LICENSE
3
+ * Copyright © Blue Mind SAS, 2012-2019
4
+ *
5
+ * This file is part of BlueMind. BlueMind is a messaging and collaborative
6
+ * solution.
7
+ *
8
+ * This program is free software, you can redistribute it and/or modify
9
+ * it under the terms of either the GNU Affero General Public License as
10
+ * published by the Free Software Foundation (version 3 of the License).
11
+ *
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY, without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
+ *
17
+ * See LICENSE.txt
18
+ * END LICENSE
19
+*/
20
+
21
+const Cc = Components.classes;
22
+const Ci = Components.interfaces;
23
+const Cu = Components.utils;
24
+const Cr = Components.results;
25
+
26
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
27
+Cu.import("resource://gre/modules/Services.jsm");
28
+try {
29
+	Cu.import("resource:///modules/cloudFileAccounts.js");
30
+} catch(e) {
31
+	//TB 68
32
+	Cu.import("resource:///modules/cloudFileAccounts.jsm");
33
+}
34
+Cu.import("resource://bm/bmUtils.jsm");
35
+Cu.import("resource://bm/core2/BMAuthService.jsm");
36
+Cu.import("resource://gre/modules/FileUtils.jsm");
37
+
38
+Cu.importGlobalProperties(["File"]);
39
+
40
+let cloudFileProvInterface;
41
+if (Ci.nsIMsgCloudFileProvider) {
42
+    cloudFileProvInterface = Ci.nsIMsgCloudFileProvider;
43
+} else {
44
+    // TB >= 67
45
+    cloudFileProvInterface = cloudFileAccounts.constants;
46
+}
47
+
48
+this.EXPORTED_SYMBOLS = ["bmFileProvider"];
49
+
50
+function bmFileProvider() {
51
+    let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
52
+    loader.loadSubScript("chrome://bm/content/core2/FileHosting.js");
53
+    this.wrappedJSObject = this;
54
+}
55
+
56
+bmFileProvider.prototype = {
57
+    /* nsISupports */
58
+    QueryInterface: ChromeUtils.generateQI ?
59
+        ChromeUtils.generateQI([Ci.nsIMsgCloudFileProvider])
60
+        : XPCOMUtils.generateQI([Ci.nsIMsgCloudFileProvider]),
61
+    classID: Components.ID("{90286309-f57f-4aa7-813b-32b9508d8392}"),
62
+    // The type is a unique string identifier used by various interface elements
63
+    // for styling. As such, the type should be an alphanumpheric string with
64
+    // no spaces.
65
+    get type() { return "BlueMind"; },
66
+    // Unlike the type, the displayName is purely for rendering the name of
67
+    // a storage service provider.
68
+    get displayName() { return "BlueMind"; },
69
+    // A link to the homepage of the service, if applicable.
70
+    get serviceURL() { return ""; },
71
+    /// Some providers might want to provide an icon for the menu
72
+    get iconClass() { return "chrome://bm/skin/BM_Icone01_16.png"; },
73
+    get accountKey() { return this._accountKey; },
74
+    get settingsURL() { return "chrome://bm/content/fileProvider/settings.xhtml"; },
75
+    get managementURL() { return "chrome://bm/content/fileProvider/management.xhtml"; },
76
+
77
+    _logger: Cc["@blue-mind.net/logger;1"].getService().wrappedJSObject.getLogger("bmFileProvider: "),
78
+    _accountKey: null,
79
+    _prefBranch: null,
80
+    _userInfo: null,
81
+    _authKey: null,
82
+    _uploadingFile: false,
83
+    _uploader: null,
84
+    _uploads: [],
85
+    _uploadInfo: {},
86
+    _remoteInfo: {},
87
+    _urlsForFiles : {},
88
+    _expireForUrls : {},
89
+    _maxFileSize: -1, //in bytes
90
+    
91
+    init: function(aAccountKey) {
92
+        this._accountKey = aAccountKey;
93
+        this._prefBranch = Services.prefs.getBranch("mail.cloud_files.accounts." + aAccountKey + ".");
94
+        this._prefBranch.setCharPref("displayName", "BlueMind");
95
+    },
96
+  
97
+    // for TB >= 67
98
+    get configured() {
99
+        return true;
100
+    },
101
+    
102
+    /**
103
+     * upload the file to the cloud provider. The callback's OnStopRequest
104
+     * method will be called when finished, with success or an error code.
105
+     *
106
+     * @param aFile file to upload
107
+     * @param aCallback callback when finished.
108
+     * @param aSkipUpload BM specific: skip upload and share
109
+     *
110
+     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
111
+     */
112
+    uploadFile: function(aFile, aCallback) {
113
+        let remoteInfo = this._remoteInfo[aFile.path];
114
+        if (remoteInfo) {
115
+            this._logger.info("do not upload remote file:" + aFile.path);
116
+            if (aCallback) {
117
+                return aCallback.onStopRequest(null, null, Cr.NS_ERROR_FAILURE);
118
+            } else {
119
+                throw Cr.NS_ERROR_FAILURE;
120
+            }
121
+        }
122
+        if (!aCallback) {
123
+            let wrapper = function(aFile) {
124
+                return new Promise(function(resolve, reject) {
125
+                    this._uploadFile(aFile, {
126
+                        onStartRequest: function() {},
127
+                        onStopRequest: function(p, ctx, cr) {
128
+                            if (!Components.isSuccessCode(cr)) {
129
+                                throw cr;
130
+                            }
131
+                            resolve({});
132
+                        }
133
+                    });
134
+                }.bind(this));
135
+            }.bind(this);
136
+            async function asyncUpload(aFile) {
137
+                return await wrapper(aFile);
138
+            }
139
+            return asyncUpload(aFile);
140
+        } else {
141
+            this._uploadFile(aFile, aCallback);
142
+        }
143
+    },
144
+    
145
+    /**
146
+     * BM specific share file on the cloud provider. The callback's OnStopRequest
147
+     * method will be called when finished, with success or an error code.
148
+     *
149
+     * @param aFile file already uploaded
150
+     * @param aCallback callback when finished.
151
+     * @param aSkipUpload BM specific: skip upload and share
152
+     *
153
+     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
154
+     */
155
+    shareFile: function(aFile, aCallback) {
156
+        this._uploadFile(aFile, aCallback, true);
157
+    },
158
+    
159
+    _uploadFile: function(aFile, aCallback, aSkipUpload) {
160
+        if (Services.io.offline)
161
+            throw cloudFileProvInterface.offlineErr;
162
+        this._logger.info("Upload file: " + aFile.leafName + " skip upload: " + aSkipUpload);
163
+        if (this._uploadingFile && this._uploadingFile != aFile) {
164
+            this._logger.info("Adding file to queue");
165
+            let uploader = new bmFileUploader(this, aFile, this._uploaderCallback.bind(this), aCallback, aSkipUpload);
166
+            this._uploads.push(uploader);
167
+            return;
168
+        }
169
+        this._uploadingFile = aFile;
170
+        
171
+        let onSuccess = function() {
172
+            this._finishUpload(aFile, aCallback, aSkipUpload);
173
+        }.bind(this);
174
+        let onFailure = function() {
175
+            aCallback.onStopRequest(null, null, cloudFileProvInterface.authErr);
176
+        }.bind(this);
177
+    
178
+        if (!this._authKey)
179
+            return this._login(onSuccess, onFailure, true);
180
+        if (!this._userInfo)
181
+            return this._getUserInfo(onSuccess, onFailure);
182
+        onSuccess();
183
+    },
184
+    
185
+    urlForFile: function(aFile) {
186
+        return this._urlsForFiles[aFile.path];
187
+    },
188
+    
189
+    expireForLink: function(aUrl) {
190
+        let expire = this._expireForUrls[aUrl];
191
+        if (expire) {
192
+            return expire;
193
+        }
194
+        return null;
195
+    },
196
+    
197
+    /**
198
+     * Cancels the upload of the passed in file, if it hasn't finished yet.
199
+     * If it hasn't started yet, it will be removed from the upload queue.
200
+     *
201
+     * @param aFile file whose upload we should cancel.
202
+     */
203
+    cancelFileUpload: function(aFile) {
204
+        if (this._uploadingFile.equals(aFile)) {
205
+            this._uploader.cancel();
206
+        } else {
207
+            for (let i = 0; i < this._uploads.length; i++) {
208
+                if (this._uploads[i].file.equals(aFile)) {
209
+                    this._uploads[i].requestObserver.onStopRequest(null, null, cloudFileProvInterface.uploadCanceled);
210
+                    this._uploads.splice(i, 1);
211
+                    return;
212
+                }
213
+            }
214
+        }
215
+    },
216
+  
217
+    /**
218
+     * Refresh the user info for this account. This will fill in the quota info,
219
+     * if supported by the provider.
220
+     *
221
+     * @param aWithUI if true, we may prompt for a password, or bring up an auth
222
+     *                page. If false, we won't, and will fail if auth is required.
223
+     * @param aCallback callback when finished.
224
+     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
225
+     */
226
+    refreshUserInfo: function(aWithUI, aCallback) {
227
+        if (Services.io.offline)
228
+            throw cloudFileProvInterface.offlineErr;
229
+        aCallback.onStartRequest(null, null);
230
+        
231
+        let onSuccess = function() {
232
+            aCallback.onStopRequest(null, null, Cr.NS_OK);
233
+        }.bind(this);
234
+        let onFailure = function() {
235
+            aCallback.onStopRequest(null, null, cloudFileProvInterface.authErr);
236
+        }.bind(this);
237
+        
238
+        if (!this._authkey)
239
+            return this._login(onSuccess, onFailure, aWithUI);
240
+        if (!this._userInfo)
241
+            return this._getUserInfo(onSuccess, onFailure);
242
+        return this._userInfo;
243
+    },
244
+  
245
+    /**
246
+     * Delete a file that we've uploaded in this session and discarded. This
247
+     * operation is asynchronous.
248
+     *
249
+     * @param aFile File we previously uploaded in this session.
250
+     * @param aCallback callback when finished
251
+     *
252
+     * @throws NS_ERROR_FAILURE if we don't know about the file.
253
+     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
254
+     */
255
+    deleteFile: function(aFile, aCallback) {
256
+        if (Services.io.offline)
257
+            throw cloudFileProvInterface.offlineErr;
258
+        let uploadInfo = this._uploadInfo[aFile.path];
259
+        let remoteInfo = this._remoteInfo[aFile.path];
260
+        if (!uploadInfo && !remoteInfo)
261
+            throw Cr.NS_ERROR_FAILURE;
262
+        
263
+        if (remoteInfo) {
264
+            this._logger.info("do not remove remote file:" + aFile.path);
265
+            if (aCallback) {
266
+                return aCallback.onStopRequest(null, null, Cr.NS_OK);
267
+            } else {
268
+                return Cr.NS_OK
269
+            }
270
+        }
271
+        
272
+        if (!aCallback) {
273
+            let wrapper = function(aFile, aPublicUrl) {
274
+                return new Promise(function(resolve, reject) {
275
+                    this._deleteFile(aFile, aPublicUrl, {
276
+                        onStartRequest: function() {},
277
+                        onStopRequest: function(p, ctx, cr) {
278
+                            if (!Components.isSuccessCode(cr)) {
279
+                                throw cr;
280
+                            }
281
+                            resolve();
282
+                        }
283
+                    });
284
+                }.bind(this));
285
+            }.bind(this);
286
+            async function asyncDelete(aFile, aPublicUrl) {
287
+                return await wrapper(aFile, aPublicUrl);
288
+            }
289
+            return asyncDelete(aFile, uploadInfo.publicUrl);
290
+        } else {
291
+            this._deleteFile(aFile, uploadInfo.publicUrl, aCallback);
292
+        }
293
+    },
294
+
295
+    _deleteFile: function(aFile, aPublicUrl, aCallback) {
296
+        let onSuccess = function(aResponseText, aRequest) {
297
+            this._logger.info("success deleting file: " + aResponseText);
298
+            
299
+            if (this._urlsForFiles[aFile.path])
300
+                delete this._urlsForFiles[aFile.path];
301
+            if (this._expireForUrls[aPublicUrl]) {
302
+                delete this._expireForUrls[aPublicUrl];
303
+            }
304
+            delete this._uploadInfo[aFile.path];
305
+            aCallback.onStopRequest(null, null, Cr.NS_OK);
306
+        }.bind(this);
307
+        
308
+        let onFailure = function(aException, aResponseText, aRequest) {
309
+            this._logger.error("fail deleting file: " + aResponseText);
310
+            aCallback.onStopRequest(null, null, cloudFileProvInterface.uploadErr);
311
+        }.bind(this);
312
+        
313
+        this._fileHostingAPI.unShare(
314
+            onSuccess,
315
+            onFailure,
316
+            this,
317
+            aPublicUrl
318
+        );
319
+    },
320
+  
321
+    /**
322
+     * If the provider has an API for creating an account, this will start the
323
+     * process of creating one. There will probably have to be some sort of
324
+     * validation on the part of the user before the account is created.
325
+     * If not, this will throw NS_ERROR_NOT_IMPLEMENTED.
326
+     *
327
+     * If the REST call succeeds, aCallback.onStopRequest will get called back
328
+     * with an http status. Generally, status between 200 and 300 is OK,
329
+     * otherwise, an error occurred which is * probably specific to the provider.
330
+     * If the request fails completely, onStopRequest will get called with
331
+     * Components.results.NS_ERROR_FAILURE
332
+     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
333
+     */
334
+    createNewAccount: function(aEmailAddress, aPassword, aFirstName, aLastName, aCallback) {
335
+        return Cr.NS_ERROR_NOT_IMPLEMENTED;
336
+    },
337
+  
338
+    createExistingAccount: function(aCallback) {
339
+        if (Services.io.offline)
340
+            throw cloudFileProvInterface.offlineErr;
341
+        aCallback.onStartRequest(null, null);
342
+        
343
+        let onSuccess = function() {
344
+            aCallback.onStopRequest(null, this, Cr.NS_OK);
345
+        }.bind(this);
346
+        let onFailure = function() {
347
+            aCallback.onStopRequest(null, null, cloudFileProvInterface.authErr);
348
+        }.bind(this);
349
+        
350
+        this._login(onSuccess, onFailure, true);
351
+    },
352
+  
353
+    /**
354
+     * If the provider doesn't have an API for creating an account, perhaps
355
+     * there's a url we can load in a content tab that will allow the user
356
+     * to create an account.
357
+     */
358
+    get createNewAccountUrl() { return ""; },
359
+  
360
+    /**
361
+     * For some errors, the provider may have an explanatory page, or have an
362
+     * option to upgrade an account to handle the error. This method returns a url
363
+     * to a page hosted by the provider which can help with the error.
364
+     *
365
+     * @param aError e.g. uploadWouldExceedQuota or uploadExceedsFileLimit
366
+     * @returns provider url, if any, for the error, empty string otherwise.
367
+     */
368
+    providerUrlForError: function(aError) {
369
+        return Cr.NS_ERROR_NOT_IMPLEMENTED;
370
+    },
371
+  
372
+    /**
373
+     * If we don't know the limit, this will return -1.
374
+     */
375
+    get fileUploadSizeLimit() { return this._maxFileSize; },
376
+  
377
+    /// -1 if we don't have this info
378
+    get remainingFileSpace() { return -1; },
379
+  
380
+    /// -1 if we don't have this info
381
+    get fileSpaceUsed() { return -1; },
382
+  
383
+    /// This is used by our test harness to override the urls the provider uses.
384
+    //overrideUrls: function(aNumUrls, aUrls),
385
+  
386
+    // Error handling
387
+    // If the cloud provider gets textual errors back from the server,
388
+    // they can be retrieved here.
389
+    get lastError() { return ""; },
390
+    
391
+    
392
+    canRemoteAttach: function() {
393
+        let can = this._canRemoteAttach;
394
+        this._logger.info("canRemoteAttach: " + can);
395
+        return can;
396
+    },
397
+    
398
+    canUseFilehosting: function() {
399
+        let can = this._canUseFilehosting;
400
+        this._logger.info("canUseFilehosting: " + can);
401
+        return can;
402
+    },
403
+    
404
+    /** Private methods **/
405
+    _login: function(onSuccess, onFailure, aWithUI) {
406
+        let user = {};
407
+        let pwd = {};
408
+        let srv = {};
409
+        if (bmUtils.getSettings(user, pwd, srv, aWithUI)) {
410
+            let self = this;
411
+            let result = BMAuthService.login(srv.value, user.value, pwd.value);
412
+            result.then(function(logged) {
413
+                self._authKey = logged.authKey;
414
+                self._logger.debug("LOGGED with authKey:" + logged.authKey);
415
+                self._loggedUser = logged.authUser;
416
+                self._fileHostingAPI = new BMFileHostingAPI(srv.value, self._authKey, self._loggedUser.domainUid);
417
+                self._canRemoteAttach = self._loggedUser.roles.indexOf("canRemoteAttach") != -1;
418
+                self._canUseFilehosting = self._loggedUser.roles.indexOf("canUseFilehosting") != -1;
419
+                if (self._canRemoteAttach || self._canRemoteAttach) {
420
+                    self._getUserInfo(onSuccess, onFailure);
421
+                } else {
422
+                    onSuccess();
423
+                }
424
+            },
425
+            function(aRejectReason) {
426
+                self._logger.error(aRejectReason);
427
+                onFailure();
428
+            }).catch(function(err) {
429
+                self._logger.error(err);
430
+                onFailure();
431
+            });
432
+        } else {
433
+            onFailure();
434
+        }
435
+    },
436
+    _getUserInfo: function(onSuccess, onFailure) {
437
+        let onOk = function(aResponseText, aRequest) {
438
+            this._logger.info("success get config: " + aResponseText);
439
+            let config = JSON.parse(aResponseText);
440
+            if (config) {
441
+                if (config.autoDetachmentLimit != 0) {
442
+                    // big_attachments.threshold_kb pref is in KiB
443
+                    Services.prefs.getBranch("mail.compose.")
444
+                            .setIntPref("big_attachments.threshold_kb", config.autoDetachmentLimit / 1024);
445
+                }
446
+                this._maxFileSize = (config.maxFilesize == 0 ? -1 : config.maxFilesize);
447
+                this._retentionTime = config.retentionTime;
448
+            }
449
+            onSuccess();
450
+        }.bind(this);
451
+        
452
+        let onError = function(aException, aResponseText, aRequest) {
453
+            this._logger.error("fail to get config: " + aResponseText);
454
+            onFailure();
455
+        }.bind(this);
456
+        
457
+        this._fileHostingAPI.getConfig(onOk, onError, this);
458
+    },
459
+    _finishUpload: function(aFile, aCallback, aSkipUpload) {
460
+        let exceedsFileLimit = cloudFileProvInterface.uploadExceedsFileLimit;
461
+        let exceedsQuota = cloudFileProvInterface.uploadWouldExceedQuota;
462
+        if (this._maxFileSize != -1 && aFile.fileSize > this._maxFileSize)
463
+          return aCallback.onStopRequest(null, null, exceedsFileLimit);
464
+        //if (aFile.fileSize > this.remainingFileSpace)
465
+        //  return aCallback.onStopRequest(null, null, exceedsQuota);
466
+    
467
+        delete this._userInfo;
468
+        if (!this._uploader) {
469
+          this._uploader = new bmFileUploader(this, aFile, this._uploaderCallback.bind(this), aCallback, aSkipUpload);
470
+          this._uploads.unshift(this._uploader);
471
+        }
472
+        this._uploadingFile = aFile;
473
+        this._uploader.uploadFile();
474
+    },
475
+    _uploaderCallback: function(aRequestObserver, aStatus) {
476
+        aRequestObserver.onStopRequest(null, null, aStatus);
477
+        this._uploadingFile = null;
478
+        this._uploads.shift();
479
+        if (this._uploads.length > 0) {
480
+            let nextUpload = this._uploads[0];
481
+            this._logger.info("add new upload file: " + nextUpload.file.leafName);
482
+            this._uploadingFile = nextUpload.file;
483
+            this._uploader = nextUpload;
484
+            try {
485
+                this.uploadFile(nextUpload.file, nextUpload.callback);
486
+            } catch (ex) {
487
+                nextUpload.callback(nextUpload.requestObserver, Cr.NS_ERROR_FAILURE);
488
+            }
489
+        } else {
490
+            this._uploader = null;
491
+        }
492
+    },
493
+};
494
+
495
+function bmFileUploader(provider, aFile, aCallback, aRequestObserver, aSkipUpload) {
496
+    this._provider = provider;
497
+    this._logger = provider._logger;
498
+    this._skipUpload = aSkipUpload;
499
+    this.file = aFile;
500
+    this.callback = aCallback;
501
+    this.requestObserver = aRequestObserver;
502
+}
503
+
504
+bmFileUploader.prototype = {
505
+    _provider : null,
506
+    file : null,
507
+    callback : null,
508
+    request : null,
509
+    uploadFile: function() {
510
+        this._logger.info("ready to upload file [" + this.file.leafName + "]");
511
+        
512
+        let onSuccess = function(aResponseText, aRequest) {
513
+            this.request = null;
514
+            let detachInfo = JSON.parse(aResponseText);
515
+            this._logger.info("success detach file: " + aResponseText);
516
+            this._provider._uploadInfo[this.file.path] = detachInfo;
517
+            this._provider._urlsForFiles[this.file.path] = detachInfo.publicUrl;
518
+            this._provider._expireForUrls[detachInfo.publicUrl] = detachInfo.expirationDate;
519
+            this.callback(this.requestObserver, Cr.NS_OK);
520
+        }.bind(this);
521
+        
522
+        let onFailure = function(aException, aResponseText, aRequest) {
523
+            this._logger.error("fail detach file: " + aResponseText);
524
+            this.request = null;
525
+            if (this.callback)
526
+                this.callback(this.requestObserver, cloudFileProvInterface.uploadErr);
527
+        }.bind(this);
528
+        
529
+        if (!this._skipUpload) {
530
+            let upload = function(aFile) {
531
+                this.request = this._provider._fileHostingAPI.detach(
532
+                    onSuccess,
533
+                    onFailure,
534
+                    this,
535
+                    null,
536
+                    this.file.leafName,
537
+                    aFile
538
+                );
539
+            }.bind(this);
540
+            File.createFromNsIFile(this.file).then(file => {
541
+                upload(file);
542
+            });
543
+        } else {
544
+           this._logger.info("skip upload file is already on server");
545
+           this._getShareUrl();
546
+        }
547
+        
548
+    },
549
+    cancel: function() {
550
+        this.callback(this.requestObserver, cloudFileProvInterface.uploadCanceled);
551
+        if (this.request) {
552
+            let req = this.request;
553
+            if (req.channel) {
554
+                this._logger.info("cancel channel upload");
555
+                delete this.callback;
556
+                req.channel.cancel(Cr.NS_BINDING_ABORTED);
557
+            }
558
+            this.request = null;
559
+        }
560
+    },
561
+    _getShareUrl: function() {
562
+        let onSuccess = function(aResponseText, aRequest) {
563
+            let shareInfo = JSON.parse(aResponseText);
564
+            this._logger.info("success get share url: " + shareInfo.url);
565
+            this._provider._remoteInfo[this.file.path] = shareInfo;
566
+            this._provider._urlsForFiles[this.file.path] = shareInfo.url;
567
+            this._provider._expireForUrls[shareInfo.url] = shareInfo.expirationDate;
568
+            this.callback(this.requestObserver, Cr.NS_OK);
569
+        }.bind(this);
570
+        
571
+        let onFailure = function(aException, aResponseText, aRequest) {
572
+            this._logger.error("fail to get share url");
573
+            this.callback(this.requestObserver, Cr.NS_ERROR_FAILURE);
574
+        }.bind(this);
575
+        
576
+        //Remove Tmp path from path
577
+        let tmpPath = FileUtils.getDir("TmpD", []).path;
578
+        let filePath = this.file.path.replace(tmpPath, "");
579
+        filePath = filePath.replace(/\\/g, "/");
580
+        
581
+        let expire = "";
582
+        if (this._retentionTime && this._retentionTime > 0) {
583
+            let now = new Date();
584
+            now.setDate(now.getDate() + this._retentionTime);
585
+            expire = "" + now.getTime();
586
+        }
587
+        this._provider._fileHostingAPI.share(
588
+            onSuccess,
589
+            onFailure,
590
+            this,
591
+            filePath,
592
+            0,
593
+            expire
594
+        );
595
+    },
596
+};
597
+
598
+// Only one BM account (used only with TB >= 67)
599
+let instanceCount = 0;
600
+
601
+if (!Ci.nsIMsgCloudFileProvider) {
602
+    // TB >= 67
603
+    cloudFileAccounts.registerProvider("BlueMind", {
604
+        type: "BlueMind",
605
+        displayName: "BlueMind",
606
+        iconURL: "chrome://bm/skin/BM_Icone01_16.png",
607
+        initAccount: function(accountKey) {
608
+            if (instanceCount > 0) {
609
+                return null;
610
+            }
611
+            let account = new bmFileProvider();
612
+            account.init(accountKey);
613
+            instanceCount++;
614
+            return account;
615
+        },
616
+    });
617
+}
... ...
@@ -18,509 +18,9 @@
18 18
  * END LICENSE
19 19
 */
20 20
 
21
-const Cc = Components.classes;
22
-const Ci = Components.interfaces;
23
-const Cu = Components.utils;
24
-const Cr = Components.results;
21
+// this XPCOM component is not loaded by TB >= 67
25 22
 
26
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
27
-Cu.import("resource://gre/modules/Services.jsm");
28
-Cu.import("resource:///modules/cloudFileAccounts.js");
29
-Cu.import("resource://bm/bmUtils.jsm");
30
-Cu.import("resource://bm/core2/BMAuthService.jsm");
31
-Cu.import("resource://gre/modules/FileUtils.jsm");
23
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
24
+Components.utils.import("resource://bm/bmFileProvider.jsm");
32 25
 
33
-Cu.importGlobalProperties(["File"]);
34
-
35
-function bmFileProvider() {
36
-    let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
37
-    loader.loadSubScript("chrome://bm/content/core2/FileHosting.js");
38
-    this.wrappedJSObject = this;
39
-}
40
-
41
-bmFileProvider.prototype = {
42
-    /* nsISupports */
43
-    QueryInterface: ChromeUtils.generateQI ?
44
-        ChromeUtils.generateQI([Ci.nsIMsgCloudFileProvider])
45
-        : XPCOMUtils.generateQI([Ci.nsIMsgCloudFileProvider]),
46
-    classID: Components.ID("{90286309-f57f-4aa7-813b-32b9508d8392}"),
47
-    // The type is a unique string identifier used by various interface elements
48
-    // for styling. As such, the type should be an alphanumpheric string with
49
-    // no spaces.
50
-    get type() { return "BlueMind"; },
51
-    // Unlike the type, the displayName is purely for rendering the name of
52
-    // a storage service provider.
53
-    get displayName() { return "BlueMind"; },
54
-    // A link to the homepage of the service, if applicable.
55
-    get serviceURL() { return ""; },
56
-    /// Some providers might want to provide an icon for the menu
57
-    get iconClass() { return "chrome://bm/skin/BM_Icone01_16.png"; },
58
-    get accountKey() { return this._accountKey; },
59
-    get settingsURL() { return "chrome://bm/content/fileProvider/settings.xhtml"; },
60
-    get managementURL() { return "chrome://bm/content/fileProvider/management.xhtml"; },
61
-  
62
-    _logger: Cc["@blue-mind.net/logger;1"].getService().wrappedJSObject.getLogger("bmFileProvider: "),
63
-    _accountKey: null,
64
-    _prefBranch: null,
65
-    _userInfo: null,
66
-    _authKey: null,
67
-    _uploadingFile: false,
68
-    _uploader: null,
69
-    _uploads: [],
70
-    _uploadInfo: {},
71
-    _remoteInfo: {},
72
-    _urlsForFiles : {},
73
-    _expireForUrls : {},
74
-    _maxFileSize: -1, //in bytes
75
-    
76
-    init: function(aAccountKey) {
77
-        this._accountKey = aAccountKey;
78
-        this._prefBranch = Services.prefs.getBranch("mail.cloud_files.accounts." + aAccountKey + ".");
79
-        this._prefBranch.setCharPref("displayName", "BlueMind");
80
-    },
81
-  
82
-    /**
83
-     * upload the file to the cloud provider. The callback's OnStopRequest
84
-     * method will be called when finished, with success or an error code.
85
-     *
86
-     * @param aFile file to upload
87
-     * @param aCallback callback when finished.
88
-     * @param aSkipUpload BM specific: skip upload and share
89
-     *
90
-     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
91
-     */
92
-    uploadFile: function(aFile, aCallback) {
93
-        let remoteInfo = this._remoteInfo[aFile.path];
94
-        if (remoteInfo) {
95
-            this._logger.info("do not upload remote file:" + aFile.path);
96
-            return aCallback.onStopRequest(null, null, Cr.NS_ERROR_FAILURE);
97
-        }
98
-        this._uploadFile(aFile, aCallback);
99
-    },
100
-    
101
-    /**
102
-     * BM specific share file on the cloud provider. The callback's OnStopRequest
103
-     * method will be called when finished, with success or an error code.
104
-     *
105
-     * @param aFile file already uploaded
106
-     * @param aCallback callback when finished.
107
-     * @param aSkipUpload BM specific: skip upload and share
108
-     *
109
-     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
110
-     */
111
-    shareFile: function(aFile, aCallback) {
112
-        this._uploadFile(aFile, aCallback, true);
113
-    },
114
-    
115
-    _uploadFile: function(aFile, aCallback, aSkipUpload) {
116
-        if (Services.io.offline)
117
-            throw Ci.nsIMsgCloudFileProvider.offlineErr;
118
-        this._logger.info("Upload file: " + aFile.leafName + " skip upload: " + aSkipUpload);
119
-        if (this._uploadingFile && this._uploadingFile != aFile) {
120
-            this._logger.info("Adding file to queue");
121
-            let uploader = new bmFileUploader(this, aFile, this._uploaderCallback.bind(this), aCallback, aSkipUpload);
122
-            this._uploads.push(uploader);
123
-            return;
124
-        }
125
-        this._uploadingFile = aFile;
126
-        
127
-        let onSuccess = function() {
128
-            this._finishUpload(aFile, aCallback, aSkipUpload);
129
-        }.bind(this);
130
-        let onFailure = function() {
131
-            aCallback.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.authErr);
132
-        }.bind(this);
133
-    
134
-        if (!this._authKey)
135
-            return this._login(onSuccess, onFailure, true);
136
-        if (!this._userInfo)
137
-            return this._getUserInfo(onSuccess, onFailure);
138
-        onSuccess();
139
-    },
140
-    
141
-    urlForFile: function(aFile) {
142
-        return this._urlsForFiles[aFile.path];
143
-    },
144
-    
145
-    expireForLink: function(aUrl) {
146
-        let expire = this._expireForUrls[aUrl];
147
-        if (expire) {
148
-            return expire;
149
-        }
150
-        return null;
151
-    },
152
-    
153
-    /**
154
-     * Cancels the upload of the passed in file, if it hasn't finished yet.
155
-     * If it hasn't started yet, it will be removed from the upload queue.
156
-     *
157
-     * @param aFile file whose upload we should cancel.
158
-     */
159
-    cancelFileUpload: function(aFile) {
160
-        if (this._uploadingFile.equals(aFile)) {
161
-            this._uploader.cancel();
162
-        } else {
163
-            for (let i = 0; i < this._uploads.length; i++) {
164
-                if (this._uploads[i].file.equals(aFile)) {
165
-                    this._uploads[i].requestObserver.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.uploadCanceled);
166
-                    this._uploads.splice(i, 1);
167
-                    return;
168
-                }
169
-            }
170
-        }
171
-    },
172
-  
173
-    /**
174
-     * Refresh the user info for this account. This will fill in the quota info,
175
-     * if supported by the provider.
176
-     *
177
-     * @param aWithUI if true, we may prompt for a password, or bring up an auth
178
-     *                page. If false, we won't, and will fail if auth is required.
179
-     * @param aCallback callback when finished.
180
-     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
181
-     */
182
-    refreshUserInfo: function(aWithUI, aCallback) {
183
-        if (Services.io.offline)
184
-            throw Ci.nsIMsgCloudFileProvider.offlineErr;
185
-        aCallback.onStartRequest(null, null);
186
-        
187
-        let onSuccess = function() {
188
-            aCallback.onStopRequest(null, null, Cr.NS_OK);
189
-        }.bind(this);
190
-        let onFailure = function() {
191
-            aCallback.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.authErr);
192
-        }.bind(this);
193
-        
194
-        if (!this._authkey)
195
-            return this._login(onSuccess, onFailure, aWithUI);
196
-        if (!this._userInfo)
197
-            return this._getUserInfo(onSuccess, onFailure);
198
-        return this._userInfo;
199
-    },
200
-  
201
-    /**
202
-     * Delete a file that we've uploaded in this session and discarded. This
203
-     * operation is asynchronous.
204
-     *
205
-     * @param aFile File we previously uploaded in this session.
206
-     * @param aCallback callback when finished
207
-     *
208
-     * @throws NS_ERROR_FAILURE if we don't know about the file.
209
-     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
210
-     */
211
-    deleteFile: function(aFile, aCallback) {
212
-        if (Services.io.offline)
213
-            throw Ci.nsIMsgCloudFileProvider.offlineErr;
214
-        let uploadInfo = this._uploadInfo[aFile.path];
215
-        let remoteInfo = this._remoteInfo[aFile.path];
216
-        if (!uploadInfo && !remoteInfo)
217
-            throw Cr.NS_ERROR_FAILURE;
218
-        
219
-        if (remoteInfo) {
220
-            this._logger.info("do not remove remote file:" + aFile.path);
221
-            return aCallback.onStopRequest(null, null, Cr.NS_OK);
222
-        }
223
-        
224
-        let onSuccess = function(aResponseText, aRequest) {
225
-            this._logger.info("success deleting file: " + aResponseText);
226
-            
227
-            if (this._urlsForFiles[aFile.path])
228
-                delete this._urlsForFiles[aFile.path];
229
-            if (this._expireForUrls[uploadInfo.publicUrl]) {
230
-                delete this._expireForUrls[uploadInfo.publicUrl];
231
-            }
232
-            delete this._uploadInfo[aFile.path];
233
-            aCallback.onStopRequest(null, null, Cr.NS_OK);
234
-        }.bind(this);
235
-        
236
-        let onFailure = function(aException, aResponseText, aRequest) {
237
-            this._logger.error("fail deleting file: " + aResponseText);
238
-            aCallback.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.uploadErr);
239
-        }.bind(this);
240
-        
241
-        this._fileHostingAPI.unShare(
242
-            onSuccess,
243
-            onFailure,
244
-            this,
245
-            uploadInfo.publicUrl
246
-        );
247
-    },
248
-  
249
-    /**
250
-     * If the provider has an API for creating an account, this will start the
251
-     * process of creating one. There will probably have to be some sort of
252
-     * validation on the part of the user before the account is created.
253
-     * If not, this will throw NS_ERROR_NOT_IMPLEMENTED.
254
-     *
255
-     * If the REST call succeeds, aCallback.onStopRequest will get called back
256
-     * with an http status. Generally, status between 200 and 300 is OK,
257
-     * otherwise, an error occurred which is * probably specific to the provider.
258
-     * If the request fails completely, onStopRequest will get called with
259
-     * Components.results.NS_ERROR_FAILURE
260
-     * @throws nsIMsgCloudFileProvider.offlineErr if we are offline.
261
-     */
262
-    createNewAccount: function(aEmailAddress, aPassword, aFirstName, aLastName, aCallback) {
263
-        return Cr.NS_ERROR_NOT_IMPLEMENTED;
264
-    },
265
-  
266
-    createExistingAccount: function(aCallback) {
267
-        if (Services.io.offline)
268
-            throw Ci.nsIMsgCloudFileProvider.offlineErr;
269
-        aCallback.onStartRequest(null, null);
270
-        
271
-        let onSuccess = function() {
272
-            aCallback.onStopRequest(null, this, Cr.NS_OK);
273
-        }.bind(this);
274
-        let onFailure = function() {
275
-            aCallback.onStopRequest(null, null, Ci.nsIMsgCloudFileProvider.authErr);
276
-        }.bind(this);
277
-        
278
-        this._login(onSuccess, onFailure, true);
279
-    },
280
-  
281
-    /**
282
-     * If the provider doesn't have an API for creating an account, perhaps
283
-     * there's a url we can load in a content tab that will allow the user
284
-     * to create an account.
285
-     */
286
-    get createNewAccountUrl() { return ""; },
287
-  
288
-    /**
289
-     * For some errors, the provider may have an explanatory page, or have an
290
-     * option to upgrade an account to handle the error. This method returns a url
291
-     * to a page hosted by the provider which can help with the error.
292
-     *
293
-     * @param aError e.g. uploadWouldExceedQuota or uploadExceedsFileLimit
294
-     * @returns provider url, if any, for the error, empty string otherwise.
295
-     */
296
-    providerUrlForError: function(aError) {
297
-        return Cr.NS_ERROR_NOT_IMPLEMENTED;
298
-    },
299
-  
300
-    /**
301
-     * If we don't know the limit, this will return -1.
302
-     */
303
-    get fileUploadSizeLimit() { return this._maxFileSize; },
304
-  
305
-    /// -1 if we don't have this info
306
-    get remainingFileSpace() { return -1; },
307
-  
308
-    /// -1 if we don't have this info
309
-    get fileSpaceUsed() { return -1; },
310
-  
311
-    /// This is used by our test harness to override the urls the provider uses.
312
-    //overrideUrls: function(aNumUrls, aUrls),
313
-  
314
-    // Error handling
315
-    // If the cloud provider gets textual errors back from the server,
316
-    // they can be retrieved here.
317
-    get lastError() { return ""; },
318
-    
319
-    
320
-    canRemoteAttach: function() {
321
-        let can = this._canRemoteAttach;
322
-        this._logger.info("canRemoteAttach: " + can);
323
-        return can;
324
-    },
325
-    
326
-    canUseFilehosting: function() {
327
-        let can = this._canUseFilehosting;
328
-        this._logger.info("canUseFilehosting: " + can);
329
-        return can;
330
-    },
331
-    
332
-    /** Private methods **/
333
-    _login: function(onSuccess, onFailure, aWithUI) {
334
-        let user = {};
335
-        let pwd = {};
336
-        let srv = {};
337
-        if (bmUtils.getSettings(user, pwd, srv, aWithUI)) {
338
-            let self = this;
339
-            let result = BMAuthService.login(srv.value, user.value, pwd.value);
340
-            result.then(function(logged) {
341
-                self._authKey = logged.authKey;
342
-                self._logger.debug("LOGGED with authKey:" + logged.authKey);
343
-                self._loggedUser = logged.authUser;
344
-                self._fileHostingAPI = new BMFileHostingAPI(srv.value, self._authKey, self._loggedUser.domainUid);
345
-                self._canRemoteAttach = self._loggedUser.roles.indexOf("canRemoteAttach") != -1;
346
-                self._canUseFilehosting = self._loggedUser.roles.indexOf("canUseFilehosting") != -1;
347
-                if (self._canRemoteAttach || self._canRemoteAttach) {
348
-                    self._getUserInfo(onSuccess, onFailure);
349
-                } else {
350
-                    onSuccess();
351
-                }
352
-            },
353
-            function(aRejectReason) {
354
-                self._logger.error(aRejectReason);
355
-                onFailure();
356
-            }).catch(function(err) {
357
-                self._logger.error(err);
358
-                onFailure();
359
-            });
360
-        } else {
361
-            onFailure();
362
-        }
363
-    },
364
-    _getUserInfo: function(onSuccess, onFailure) {
365
-        let onOk = function(aResponseText, aRequest) {
366
-            this._logger.info("success get config: " + aResponseText);
367
-            let config = JSON.parse(aResponseText);
368
-            if (config) {
369
-                if (config.autoDetachmentLimit != 0) {
370
-                    // big_attachments.threshold_kb pref is in KiB
371
-                    Services.prefs.getBranch("mail.compose.")
372
-                            .setIntPref("big_attachments.threshold_kb", config.autoDetachmentLimit / 1024);
373
-                }
374
-                this._maxFileSize = (config.maxFilesize == 0 ? -1 : config.maxFilesize);
375
-                this._retentionTime = config.retentionTime;
376
-            }
377
-            onSuccess();
378
-        }.bind(this);
379
-        
380
-        let onError = function(aException, aResponseText, aRequest) {
381
-            this._logger.error("fail to get config: " + aResponseText);
382
-            onFailure();
383
-        }.bind(this);
384
-        
385
-        this._fileHostingAPI.getConfig(onOk, onError, this);
386
-    },
387
-    _finishUpload: function(aFile, aCallback, aSkipUpload) {
388
-        let exceedsFileLimit = Ci.nsIMsgCloudFileProvider.uploadExceedsFileLimit;
389
-        let exceedsQuota = Ci.nsIMsgCloudFileProvider.uploadWouldExceedQuota;
390
-        if (this._maxFileSize != -1 && aFile.fileSize > this._maxFileSize)
391
-          return aCallback.onStopRequest(null, null, exceedsFileLimit);
392
-        //if (aFile.fileSize > this.remainingFileSpace)
393