summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-23 05:51:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-23 05:51:58 +0000
commitf163944c65ea2dffe2488515b16d80891dfb2cc8 (patch)
tree2cd073f51d0d8d9111e604e4422b1f6f09d46185
parentInitial commit. (diff)
downloadtbsync-f163944c65ea2dffe2488515b16d80891dfb2cc8.tar.xz
tbsync-f163944c65ea2dffe2488515b16d80891dfb2cc8.zip
Adding upstream version 2.19.upstream/2.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/issue_template.md20
-rw-r--r--CONTRIBUTORS.md16
-rw-r--r--LICENSE373
-rw-r--r--Makebeta37
-rw-r--r--Makefile.bat19
-rw-r--r--README.md99
-rw-r--r--UPDATE68.md24
-rw-r--r--_locales/Readme.txt8
-rw-r--r--_locales/bg/messages.json359
-rw-r--r--_locales/cs/messages.json359
-rw-r--r--_locales/de/messages.json359
-rw-r--r--_locales/en-US/messages.json359
-rw-r--r--_locales/fr/messages.json359
-rw-r--r--_locales/hu/messages.json359
-rw-r--r--_locales/it/messages.json359
-rw-r--r--_locales/ja/messages.json359
-rw-r--r--_locales/pl/messages.json359
-rw-r--r--_locales/pt_BR/messages.json359
-rw-r--r--_locales/ru/messages.json359
-rw-r--r--background.js14
-rw-r--r--beta-release-channel-update.json13
-rw-r--r--content/HttpRequest.jsm755
-rw-r--r--content/OverlayManager.jsm514
-rw-r--r--content/api/BootstrapLoader/README.md1
-rw-r--r--content/api/BootstrapLoader/implementation.js237
-rw-r--r--content/api/BootstrapLoader/schema.json50
-rw-r--r--content/manager/accountManager.js140
-rw-r--r--content/manager/accountManager.xhtml27
-rw-r--r--content/manager/accounts.js498
-rw-r--r--content/manager/accounts.xhtml119
-rw-r--r--content/manager/addonoptions.xhtml15
-rw-r--r--content/manager/catman.xhtml25
-rw-r--r--content/manager/editAccount.js397
-rw-r--r--content/manager/editAccount.xhtml88
-rw-r--r--content/manager/eventlog/eventlog.js159
-rw-r--r--content/manager/eventlog/eventlog.xhtml19
-rw-r--r--content/manager/help.xhtml58
-rw-r--r--content/manager/installProvider.xhtml35
-rw-r--r--content/manager/manageProvider.js40
-rw-r--r--content/manager/manager.css38
-rw-r--r--content/manager/missingProvider.xhtml21
-rw-r--r--content/manager/noaccounts.xhtml17
-rw-r--r--content/manager/support-wizard/support-wizard.xhtml44
-rw-r--r--content/manager/supporter.xhtml77
-rw-r--r--content/modules/abAutoComplete.js234
-rw-r--r--content/modules/addressbook.js1218
-rw-r--r--content/modules/cardbook.js20
-rw-r--r--content/modules/core.js332
-rw-r--r--content/modules/db.js465
-rw-r--r--content/modules/eventlog.js153
-rw-r--r--content/modules/io.js41
-rw-r--r--content/modules/lightning.js766
-rw-r--r--content/modules/manager.js387
-rw-r--r--content/modules/messenger.js108
-rw-r--r--content/modules/network.js114
-rw-r--r--content/modules/passwordManager.js78
-rw-r--r--content/modules/providers.js182
-rw-r--r--content/modules/public.js753
-rw-r--r--content/modules/tools.js80
-rw-r--r--content/overlays/abCSS.xhtml8
-rw-r--r--content/overlays/abNewCardWindowOverlay.js51
-rw-r--r--content/overlays/abNewCardWindowOverlay.xhtml9
-rw-r--r--content/overlays/addressbookiconsoverlay.js40
-rw-r--r--content/overlays/addressbookiconsoverlay.xhtml9
-rw-r--r--content/overlays/calendar-event-dialog-attendees.js35
-rw-r--r--content/overlays/calendar-event-dialog-attendees.xhtml9
-rw-r--r--content/overlays/messenger.js22
-rw-r--r--content/overlays/messenger.xhtml20
-rw-r--r--content/overlays/messengercompose.js58
-rw-r--r--content/overlays/messengercompose.xhtml9
-rw-r--r--content/passwordPrompt/passwordPrompt.js45
-rw-r--r--content/passwordPrompt/passwordPrompt.xhtml38
-rw-r--r--content/scripts/bootstrap.js89
-rw-r--r--content/scripts/locales.js3
-rw-r--r--content/skin/ab.css8
-rw-r--r--content/skin/acl_ro.pngbin0 -> 15607 bytes
-rw-r--r--content/skin/acl_ro2.pngbin0 -> 18264 bytes
-rw-r--r--content/skin/acl_rw.pngbin0 -> 15519 bytes
-rw-r--r--content/skin/acl_rw2.pngbin0 -> 17601 bytes
-rw-r--r--content/skin/add16.pngbin0 -> 17963 bytes
-rw-r--r--content/skin/browserOverlay.css10
-rw-r--r--content/skin/calendar16.pngbin0 -> 18192 bytes
-rw-r--r--content/skin/calendar16_shared.pngbin0 -> 18776 bytes
-rw-r--r--content/skin/catman32.pngbin0 -> 19134 bytes
-rw-r--r--content/skin/connect16.pngbin0 -> 1370 bytes
-rw-r--r--content/skin/contacts16.pngbin0 -> 18435 bytes
-rw-r--r--content/skin/contacts16_shared.pngbin0 -> 19055 bytes
-rw-r--r--content/skin/del16.pngbin0 -> 18172 bytes
-rw-r--r--content/skin/disabled16.pngbin0 -> 15506 bytes
-rw-r--r--content/skin/eas16.pngbin0 -> 664 bytes
-rw-r--r--content/skin/error16.pngbin0 -> 696 bytes
-rw-r--r--content/skin/fix_dropdown_1534697.css4
-rw-r--r--content/skin/group32.pngbin0 -> 433 bytes
-rw-r--r--content/skin/help32.pngbin0 -> 4667 bytes
-rw-r--r--content/skin/info16.pngbin0 -> 549 bytes
-rw-r--r--content/skin/lock24.pngbin0 -> 646 bytes
-rw-r--r--content/skin/provider16.pngbin0 -> 837 bytes
-rw-r--r--content/skin/provider32.pngbin0 -> 2166 bytes
-rw-r--r--content/skin/report_open.pngbin0 -> 21829 bytes
-rw-r--r--content/skin/report_send.pngbin0 -> 21829 bytes
-rw-r--r--content/skin/settings32.pngbin0 -> 1535 bytes
-rw-r--r--content/skin/slider-off.pngbin0 -> 17567 bytes
-rw-r--r--content/skin/slider-on.pngbin0 -> 17586 bytes
-rw-r--r--content/skin/spinner.gifbin0 -> 1849 bytes
-rw-r--r--content/skin/src/LICENSE131
-rw-r--r--content/skin/src/slider-off.pngbin0 -> 17567 bytes
-rw-r--r--content/skin/src/slider-on.pngbin0 -> 17586 bytes
-rw-r--r--content/skin/src/slider.xcfbin0 -> 2970 bytes
-rw-r--r--content/skin/sync16.pngbin0 -> 18091 bytes
-rw-r--r--content/skin/sync16_1.pngbin0 -> 18101 bytes
-rw-r--r--content/skin/sync16_2.pngbin0 -> 18078 bytes
-rw-r--r--content/skin/sync16_3.pngbin0 -> 18091 bytes
-rw-r--r--content/skin/sync16_4.pngbin0 -> 18102 bytes
-rw-r--r--content/skin/tbsync.pngbin0 -> 1121 bytes
-rw-r--r--content/skin/tbsync64.pngbin0 -> 2206 bytes
-rw-r--r--content/skin/tick16.pngbin0 -> 1324 bytes
-rw-r--r--content/skin/todo16.pngbin0 -> 650 bytes
-rw-r--r--content/skin/trash16.pngbin0 -> 774 bytes
-rw-r--r--content/skin/update32.pngbin0 -> 398 bytes
-rw-r--r--content/skin/user48.pngbin0 -> 442 bytes
-rw-r--r--content/skin/warning16.pngbin0 -> 483 bytes
-rw-r--r--content/tbsync.jsm166
-rw-r--r--crowdin.yml5
-rw-r--r--manifest.json32
-rw-r--r--screenshots/AdvancedConfig.pngbin0 -> 57535 bytes
-rw-r--r--screenshots/CalDAV-UserName.pngbin0 -> 11110 bytes
-rw-r--r--screenshots/ExportImport_001.PNGbin0 -> 59436 bytes
-rw-r--r--screenshots/ExportImport_002.PNGbin0 -> 58840 bytes
-rw-r--r--screenshots/ExportImport_005.PNGbin0 -> 7389 bytes
-rw-r--r--screenshots/TbSync_000.pngbin0 -> 67269 bytes
-rw-r--r--screenshots/TbSync_001.pngbin0 -> 32588 bytes
-rw-r--r--screenshots/TbSync_004.pngbin0 -> 30084 bytes
-rw-r--r--screenshots/TbSync_005.pngbin0 -> 48677 bytes
-rw-r--r--screenshots/TbSync_Enable_debug_logging.pngbin0 -> 35746 bytes
-rw-r--r--screenshots/TbSync_Send_logs_by_mail.pngbin0 -> 35730 bytes
-rw-r--r--screenshots/beta-release-channel.PNGbin0 -> 51259 bytes
-rw-r--r--screenshots/custom_provider.PNGbin0 -> 15416 bytes
-rw-r--r--screenshots/de/TbSync.PNGbin0 -> 82724 bytes
-rw-r--r--screenshots/de/acl_sharing.pngbin0 -> 83034 bytes
-rw-r--r--screenshots/de/add_dav_1.pngbin0 -> 8973 bytes
-rw-r--r--screenshots/de/add_dav_2.pngbin0 -> 47231 bytes
-rw-r--r--screenshots/de/add_eas_1.pngbin0 -> 9099 bytes
-rw-r--r--screenshots/de/add_eas_2.pngbin0 -> 17161 bytes
-rw-r--r--screenshots/de/custom.pngbin0 -> 26037 bytes
-rw-r--r--screenshots/de/eas_done.pngbin0 -> 83312 bytes
-rw-r--r--screenshots/de/emails.PNGbin0 -> 21040 bytes
-rw-r--r--screenshots/install-from-file.PNGbin0 -> 8825 bytes
-rw-r--r--screenshots/missing_provider.PNGbin0 -> 22138 bytes
-rw-r--r--screenshots/update-addon.PNGbin0 -> 8654 bytes
-rw-r--r--screenshots/update_notification_statusbar.pngbin0 -> 1741 bytes
-rw-r--r--unused/abDirectory.js325
-rw-r--r--unused/abDirectory2.js339
-rw-r--r--unused/abDirectoryFactory.js120
153 files changed, 14462 insertions, 0 deletions
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 0000000..b133c26
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,20 @@
+## Your environment
+
+TbSync version:
+Thunderbird version:
+
+[ ] Yes, I have installed the latest available beta version from
+https://tbsync.jobisoft.de
+and my issue is not yet fixed, I can still reproduce it.
+
+
+## Expected behavior
+...
+
+## Actual behavior
+...
+
+## Steps to reproduce
+...
+
+To help resolving your issue, enable debug logging (TbSync Account Manager -> Help) and send me the debug.log via e-mail (use the title of your issue as subject of the email).
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..05526f6
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,16 @@
+## Creator
+* John Bieling
+
+## Contributors
+* John Bieling
+* Jan Dagefoerde
+* Nam Ldmpub
+
+
+## Translators
+* John Bieling (de, en-US)
+* Wanderlei Hüttel (pt-BR)
+* Alessandro Menti (it)
+* Óvári (hu)
+* Alexey Sinitsyn (ru)
+* Daniel Wróblewski (pl)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a612ad9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/Makebeta b/Makebeta
new file mode 100644
index 0000000..9de5967
--- /dev/null
+++ b/Makebeta
@@ -0,0 +1,37 @@
+#!/bin/bash
+# This file is part of TbSync.
+#
+# 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/.
+
+# $1 link to base web server : https://tbsync.jobisoft.de/beta
+# $2 local path of base web server : /var/www/jobisoft.de/tbsync/beta
+# $3 name of XPI : TbSync.xpi
+
+git clean -df
+git checkout -- .
+git pull
+
+version=$(cat manifest.json | jq -r .version)
+updatefile=update-tbsync.json
+
+sed -i "s/%VERSION%/$version/g" "beta-release-channel-update.json"
+sed -i "s|%LINK%|$1/$3|g" "beta-release-channel-update.json"
+sed -i "s/apiVersion: \"/apiVersion: \"Beta /g" "content/tbsync.jsm"
+sed -i "s|https://addons.thunderbird.net/addon/dav-4-tbsync/|$1/DAV-4-TbSync.xpi|g" "content/modules/providers.js"
+sed -i "s|https://addons.thunderbird.net/addon/eas-4-tbsync/|$1/EAS-4-TbSync.xpi|g" "content/modules/providers.js"
+
+
+echo "Releasing version $version via beta release channel (will include updateURL)"
+sed -i "s|\"name\": \"TbSync\",|\"name\": \"TbSync [Beta Release Channel]\",|g" "manifest.json"
+sed -i "s|\"gecko\": {|\"gecko\": {\n \"update_url\": \"$1/$updatefile\",|g" "manifest.json"
+
+cp beta-release-channel-update.json $2/$updatefile
+
+rm -f $3
+rm -f $3.tar.gz
+zip -r $3 content _locales manifest.json background.js LICENSE CONTRIBUTORS.md
+tar cfvz $3.tar.gz content _locales manifest.json background.js LICENSE CONTRIBUTORS.md
+cp $3 $2/$3
+cp $3.tar.gz $2/$3.tar.gz
diff --git a/Makefile.bat b/Makefile.bat
new file mode 100644
index 0000000..846c48e
--- /dev/null
+++ b/Makefile.bat
@@ -0,0 +1,19 @@
+@echo off
+REM This file is part of TbSync.
+REM
+REM This Source Code Form is subject to the terms of the Mozilla Public
+REM License, v. 2.0. If a copy of the MPL was not distributed with this
+REM file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+del TbSync-beta.xpi
+"C:\Program Files\7-Zip\7zG.exe" a -tzip TbSync-beta.xpi content _locales manifest.json LICENSE README.md background.js CONTRIBUTORS.md
+
+
+REM Copy sources to doc repository
+rd /s /q ..\Provider-4-TbSync\docs\sources
+mkdir ..\Provider-4-TbSync\docs\sources
+
+copy content\OverlayManager.jsm ..\Provider-4-TbSync\docs\sources\
+
+Xcopy /E /I content\passwordPrompt ..\Provider-4-TbSync\docs\sources\passwordPrompt\
+Xcopy /E /I content\modules ..\Provider-4-TbSync\docs\sources\modules\
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..de2d4c1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,99 @@
+# TbSync
+
+1. [Introduction](https://github.com/jobisoft/TbSync#introduction)
+2. [Where is this going?](https://github.com/jobisoft/TbSync#where-is-this-going)
+3. [External data sources](https://github.com/jobisoft/TbSync#external-data-sources)
+4. [Icon sources and attributions](https://github.com/jobisoft/TbSync#icon-sources-and-attributions)
+
+## Introduction
+
+[TbSync](https://addons.thunderbird.net/addon/tbsync/) is a central user interface to manage cloud accounts and to synchronize their contact, task and calendar information with [Thunderbird](https://www.thunderbird.net/). Its main objective is to simplify the setup process for such accounts. The following providers (protocols) are currently supported:
+* CalDAV & CardDAV, via [DAV-4-TbSync](https://github.com/jobisoft/DAV-4-TbSync)
+[[compatibility list (DAV)](https://github.com/jobisoft/DAV-4-TbSync/wiki/Compatibility-list-(DAV))]
+* Exchange ActiveSync (EAS v2.5 & v14.0), via [EAS-4-TbSync](https://github.com/jobisoft/EAS-4-TbSync)
+[[compatibility list (EAS)](https://github.com/jobisoft/EAS-4-TbSync/wiki/Compatibility-list-(EAS))]
+
+Further details can be found in the [wiki](https://github.com/jobisoft/TbSync/wiki) of the TbSync project and in the [how-to-get-started guide](https://github.com/jobisoft/TbSync/wiki/How-to-get-started).
+
+If you like TbSync and want to support its development, please consider a donation.
+
+[![](https://www.paypalobjects.com/en_US/DK/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/johnbieling)
+
+
+## Want to add or fix a localization?
+To help translating this project, please visit [crowdin.com](https://crowdin.com/profile/jobisoft), where the localizations are managed. If you want to add a new language, just contact me and I will set it up.
+
+Here are some general information regarding translations:
+
+* [Localization content best practices](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_content_best_practices)
+* [Summary table of quotation marks per language](https://en.wikipedia.org/wiki/Quotation_mark#Summary_table)
+* [Transvision](https://transvision.mozfr.org/) provides translations for various languages
+* by Thunderbird supported [locale codes](https://searchfox.org/comm-central/source/mail/locales/all-locales)
+
+
+## Where is this going?
+
+I started to work on TbSync, because we needed ActiveSync (EAS) support in Thunderbird. Soon after, I realized that the current situation for sync accounts is very confusing in terms of user experience: There was no central place to set up sync accounts. The same DAV account had to be setup in lightning and again in the sogo-connector or in CardBook. EWS accounts are setup differently again and for google we need 3 different add-ons for contacts, calendars and tasks.
+
+With TbSync I want to unify that: A central manager to setup sync accounts (DAV, EAS, EWS, Google, ...) and get contacts, tasks and calendars. I knew that I will not be able to re-create and maintain all the different providers for TbSync by myself. I thus created a TbSync Provider API, which allows other add-ons to hook into TbSync and re-use most of the glue code.
+
+I am in contact with Thunderbird staff and we are trying to get TbSync integrated directly into Thunderbird. No ETA yet.
+
+Future plans:
+* cooperate with [CardBook](https://github.com/jobisoft/TbSync/issues/105), so it does not matter, if the user wants to use the standard Thunderbird address book or the cardbook address book.
+* add support for Google
+* add support for [EteSync](https://www.etesync.com/)
+* support the [EWS community](https://github.com/ExchangeCalendar/exchangecalendar), which is interested in turning their add-on into a provider for TbSync.
+
+All this requires funding. If you like TbSync and want to support its development, please consider a donation.
+
+
+## Adding support for other sync protocolls by creating a TbSync provider add-on
+
+All the information needed, to build a new provider add-on for TbSync and thus extending its sync capabilities, can be found here:
+
+```
+https://tbsync.readthedocs.org
+```
+
+
+## Icon sources and attributions
+
+#### WTFPL
+* [spinner.gif] by [Yannick Croissant](http://www.ajaxload.info/)
+
+#### CC0-1.0
+* [add16.png] by [Jean Victor Balin](https://openclipart.org/detail/16950/add)
+* [del16.png] by [Jean Victor Balin](https://openclipart.org/detail/16982/cross)
+* [tick16.png] by [Jean Victor Balin](https://openclipart.org/detail/17056/tick)
+* [sync16.png] by [Willleam](https://openclipart.org/detail/287463/circular-arrow-blue)
+* [slider-on.png] by [John Bieling](https://github.com/jobisoft/TbSync/blob/master/content/skin/src/LICENSE)
+* [slider-off.png] by [John Bieling](https://github.com/jobisoft/TbSync/blob/master/content/skin/src/LICENSE)
+
+#### CC-BY 3.0
+* [contacts16.png] by [Yusuke Kamiyamane](https://www.iconfinder.com/icons/25910/)
+* [todo16.png] by [Yusuke Kamiyamane](https://www.iconfinder.com/icons/45913/)
+* [error16.png] by [Yusuke Kamiyamane](https://www.iconfinder.com/icons/46013/exclamation_frame_icon)
+* [connect16.png] by [Yusuke Kamiyamane](https://www.iconfinder.com/icons/58341/connect_plug_icon)
+* [info16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/64363/info_rhombus_icon)
+* [warning16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/36026/)
+* [calendar16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/35805/)
+* [calendar/contacts16_shared.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/64490/network_share_icon)
+* [trash16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/35727/bin_empty_metal_icon)
+* [acl_rw.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/36322/pencil_icon)
+* [acl_ro.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/36324/delete_pencil_icon)
+* [provider16.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/64634)
+* [report_send.png] based on work by FatCow Web Hosting [#1](https://www.iconfinder.com/icons/36365/) and [#2](https://www.iconfinder.com/icons/93180)
+* [report_open.png] by [FatCow Web Hosting](https://www.iconfinder.com/icons/36373)
+* [lock24.png] by [Paomedia](https://www.iconfinder.com/icons/285646/lock_icon)
+* [tbsync.png] by [Paomedia](https://www.iconfinder.com/icons/299097)
+* [settings32.png] by [Paomedia](https://www.iconfinder.com/icons/299098/cogs_icon)
+* [update32.png] by [Google](https://www.iconfinder.com/icons/352158/)
+* [group32.png] by [Dumitriu Robert](https://www.iconfinder.com/icons/3289557/clan_group_partners_peers_people_icon)
+* [catman32.png] based on 'Venn Diagram' by [WARPAINT Media Inc., CA](https://thenounproject.com/search/?q=three%20circles&i=31898#) from Noun Project ([info](https://github.com/jobisoft/CategoryManager/tree/master/sendtocategory/skin/catman))
+
+#### Apache Software License 2.0
+* [disabled16.png] by [Google](https://github.com/google/material-design-icons/blob/master/notification/1x_web/ic_do_not_disturb_alt_black_18dp.png)
+
+#### GPL
+* [help32.png] by [WooThemes](https://www.iconfinder.com/icons/58495/button_help_white_icon)
diff --git a/UPDATE68.md b/UPDATE68.md
new file mode 100644
index 0000000..3f0f9c7
--- /dev/null
+++ b/UPDATE68.md
@@ -0,0 +1,24 @@
+# Update instructions for Thunderbird 68
+
+TbSync has been mostly rewritten for Thunderbird 68 (the next major release being due in a few weeks). To ensure a seamless transition from Thunderbird 60 to Thunderbird 68, I recommend to do the following **before** upgrading to Thunderbird 68:
+
+* synchronize all your TbSync accounts
+* disable all your TbSync accounts
+
+After the upgrade to Thunderbird 68 has completed, your TbSync accounts can be enabled again.
+
+## How to disable TbSync accounts
+
+Each TbSync account can be disabled by unchecking the box shown in the following image:
+
+![](https://user-images.githubusercontent.com/5830621/63053657-9a2c6d80-bee2-11e9-9019-7035830a873b.png)]
+
+## Why is this necessary ?
+
+It could happen, that after the upgrade your synchronized address books and calendars still exists in Thunderbird and can be used as before, but are no longer connected to your servers. If you make local changes, they will never make it to your servers. So these changes will be lost.
+
+That is why I ask to disable all accounts during the upgrade from Thunderbird 60 to Thunderbird 68. After re-enabling your accounts in Thunderbird 68, they will start a clean sync which ensures a proper connection between Thunderbird and your servers.
+
+## System-specific tips
+
+On Arch Linux and derivatives, you can add a `pacman` hook [like this one](https://gist.github.com/MayeulC/400adbfba72effc29fca4d8666fc4571) to print a reminder and cancel the thunderbird upgrade when it becomes available.
diff --git a/_locales/Readme.txt b/_locales/Readme.txt
new file mode 100644
index 0000000..e2dceeb
--- /dev/null
+++ b/_locales/Readme.txt
@@ -0,0 +1,8 @@
+Want to add or fix a localization?
+
+To help translating this project, please visit
+
+ https://crowdin.com/profile/jobisoft
+
+where the localizations are managed. If you want to add
+a new language, just contact me and I will set it up.
diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json
new file mode 100644
index 0000000..c77e06f
--- /dev/null
+++ b/_locales/bg/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "За отстраняване на тази грешка може да изпратите доклад на разработчика. Желаете ли да ви покажем доклада за изпращане?"
+ },
+ "NoDebugLog": {
+ "message": "Липсват съществени протоколирани съобщения. Включете режима за подробно протоколиране, рестартирайте Thunderbird и повторете всички стъпки за да се стигне отново до грешка."
+ },
+ "OopsMessage": {
+ "message": "Ужас! TbSync не можа да се зареди!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Режимът за подробно протоколиране на събитията е включен. Рестартирайте Thunderbird и опитайте да отворите отново TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Не е възможно да се изследва грешката, тъй като не всички действия са протоколирани. Желаете ли да започне подробно протоколиране на събитията, за да може грешката да се анализира и отстрани?"
+ },
+ "accountacctions.delete": {
+ "message": "Изтриване на регистрация '##accountname##'"
+ },
+ "accountacctions.disable": {
+ "message": "Изключване на регистрация '##accountname##'"
+ },
+ "accountacctions.enable": {
+ "message": "Включване на регистрация '##accountname##' и свързване със сървъра"
+ },
+ "accountacctions.sync": {
+ "message": "Синхронизиране на регистрация '##accountname##'"
+ },
+ "addressbook.searchall": {
+ "message": "Претърсване на всички адресни указатели"
+ },
+ "addressbook.searchgal": {
+ "message": "Претърсване на този адресен указател и на глобалния указател на сървъра (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Претърсване на този адресен указател"
+ },
+ "eventlog.clear": {
+ "message": "Изчистване"
+ },
+ "eventlog.close": {
+ "message": "Затваряне"
+ },
+ "eventlog.title": {
+ "message": "Протокол на събитията"
+ },
+ "extensionDescription": {
+ "message": "TbSync е добавка за управление на регистрации и синхронизиране със сървъри на вашите контакти, задачи и събития с Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "bg"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Грешка"
+ },
+ "info.idle": {
+ "message": "Празен ход"
+ },
+ "installProvider.header": {
+ "message": "Доставчик '##replace.1##' за TbSync не е инсталиран."
+ },
+ "manager.AccountActions": {
+ "message": "Действия с регистрацията"
+ },
+ "manager.AddAccount": {
+ "message": "Добавяне на регистрация"
+ },
+ "manager.DeleteAccount": {
+ "message": "Изтриване на регистрацията"
+ },
+ "manager.DisableAccount": {
+ "message": "Изключване на регистрацията"
+ },
+ "manager.EnableAccount": {
+ "message": "Включване на регистрацията и свързване със сървъра"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Опитай отново да се свържеш със сървъра"
+ },
+ "manager.ShowEventLog": {
+ "message": "Показване на протокола със събитията"
+ },
+ "manager.SyncAll": {
+ "message": "Синхронизиране на всички включени регистрации"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Синхронизиране на регистрацията"
+ },
+ "manager.accounts": {
+ "message": "Регистрации"
+ },
+ "manager.accountsettings": {
+ "message": "Настройки на регистрацията"
+ },
+ "manager.catman.text": {
+ "message": "TbSync синхронизира и категории контакти, които са заместител за несинхронизираните списъци с контакти на Thunderbird. За да разширите възможностите на адресния указател на Thunderbird, инсталирайте добавката 'Category Manager'. Тя позволява вмъкването на контакти в много категории (групи) и др. Добавката може да се инсталира от официалното депо за добавки на Mozilla:"
+ },
+ "manager.community": {
+ "message": "Общество/Потребители"
+ },
+ "manager.connecting": {
+ "message": "Осъществяване на връзка"
+ },
+ "manager.help": {
+ "message": "Помощ"
+ },
+ "manager.help.createbugreport": {
+ "message": "Създаване на доклад за грешка"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "За да събере TbSync единствено информацията, която се отнася за грешката, рестартирайте Thunderbird, и повторете стъпките, за да се стигне отново до грешка. Накрая може тук да създадете доклад за грешката."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Изключено"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Включено: Протоколиране само на грешки"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Включено: протоколиране на всички изпратени и получени данни"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Включено: протоколиране на всички данни и някои подробности за отстраняване на грешки"
+ },
+ "manager.help.debugmode": {
+ "message": "Режим на подробно протоколиране:"
+ },
+ "manager.help.fixit": {
+ "message": "Може да помогнете за отстраняването ѝ, като изпратите на разработчика доклад за грешката. За това е необходимо да включите режима за подробно протоколиране."
+ },
+ "manager.help.foundabug": {
+ "message": "Открихте ли грешка?"
+ },
+ "manager.help.needhelp": {
+ "message": "Трябва ли ви помощ?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Покажи подробния протокол"
+ },
+ "manager.help.wiki": {
+ "message": "Посетете вики страниците на проекта TbSync, където има допълнителна информация, ръководства за потребителя и подробни описания на настройките."
+ },
+ "manager.installprovider.link": {
+ "message": "Последвайте препратката за да посетите страницата на липсващия TbSync доставчик. Там ще намерите допълнителна информация за доставчика и може да го инсталирате от там:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Този доставчик не произхожда от официалното депо за добавки на Thunderbird и не е бил проверен от екипа на Thunderbird. Този TbSync доставчик би могъл да направи пакости. Използвайте на собствена отговорност."
+ },
+ "manager.lockedsettings.description": {
+ "message": "За да се избегнат грешки по време на синхронизирането, някои настройки не могат да бъдат променяни, докато регистрацията е включена."
+ },
+ "manager.missingprovider": {
+ "message": "Тази регистрация изисква TbSync доставчика ##provider##, който не е инсталиран."
+ },
+ "manager.noaccounts": {
+ "message": "Още няма настроени регистрации."
+ },
+ "manager.provider": {
+ "message": "Инсталиране на TbSync доставчик"
+ },
+ "manager.provider4tbsync": {
+ "message": "Доставчик за TbSync"
+ },
+ "manager.resource": {
+ "message": "Ресурс"
+ },
+ "manager.shorttitle": {
+ "message": "Управление на регистрацията"
+ },
+ "manager.status": {
+ "message": "Статус"
+ },
+ "manager.supporter.contributors": {
+ "message": "Разработчици и преводачи"
+ },
+ "manager.supporter.details": {
+ "message": "Детайли"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Предоставили регистрации за експерименти"
+ },
+ "manager.tabs.status": {
+ "message": "Състояние на синхронизирането"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Периодично синхронизиране (в минути)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Включване на регистрацията и синхронизиране"
+ },
+ "manager.tabs.status.general": {
+ "message": "Общи"
+ },
+ "manager.tabs.status.never": {
+ "message": "Стойност 0 изключва периодичното синхронизиране. Промяната на данни, местни или на сървъра, сама по себе си не предизвиква автоматична синхронизация."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Налични ресурси"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Изберете кои от откритите ресурси да се синхронизират с Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Синхронизирай сега"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Опитай отново да се свържеш със сървъра"
+ },
+ "manager.title": {
+ "message": "TbSync Управление на регистрацията"
+ },
+ "manager.tryagain": {
+ "message": "Опитай отново да се свържеш със сървъра"
+ },
+ "menu.settingslabel": {
+ "message": "Настройка на синхронизациите (TbSync)"
+ },
+ "password.account": {
+ "message": "Регистрация:"
+ },
+ "password.description": {
+ "message": "Моля подновете данните за следната TbSync регистрация:"
+ },
+ "password.password": {
+ "message": "Парола:"
+ },
+ "password.title": {
+ "message": "TbSync информация за регистрацията"
+ },
+ "password.user": {
+ "message": "Потребителско име:"
+ },
+ "popup.opensettings": {
+ "message": "TbSync отвори управлението на регистрациите"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Сигурни ли сте, че искате да изтриете регистрация ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Сигурни ли сте, че искате да изключите тази регистрация? Всички местни промени, които още не са синхронизирани, ще се изпарят!"
+ },
+ "prompt.Erase": {
+ "message": "Сигурни ли сте, че тази регистрация от непознат доставчик искате да я премахнете от списъка с регистрации?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Сигурни ли сте, че за този ресурс не желаете да сте повече абониран? Всички местни промени, които още не са синхронизирани, ще се изпарят!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript грешка! Проверете протокола със събитията за подробности."
+ },
+ "status.OAuthAbortError": {
+ "message": "Процесът на OAuth 2.0 удоствоверяване беше прекъснат от потребителя."
+ },
+ "status.OAuthHttpError": {
+ "message": "Удостоверяването по OAuth 2.0 не стана (HTTP грешка ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Не можах да се свържа с OAuth 2.0 сървъра."
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 сървъра отвърна с ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Не е синхронизиран"
+ },
+ "status.apiError": {
+ "message": "API Грешка при програмирането"
+ },
+ "status.disabled": {
+ "message": "Регистрацията и синхронизацията са изключени."
+ },
+ "status.foldererror": {
+ "message": "Поне при един ресурс се появи грешка при синхронизирането. Проверете протокола със събията за подробности."
+ },
+ "status.modified": {
+ "message": "Местни промени"
+ },
+ "status.network": {
+ "message": "Не можеше да се установи връзка със сървъра (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "На сървъра не бяха намерени ресурси."
+ },
+ "status.notargets": {
+ "message": "Синхронизирането прекъсна, тъй като на компютъра ви не можеха да се създадат нови ресурси."
+ },
+ "status.notsyncronized": {
+ "message": "Регистрацията трябва да бъде синхронизира."
+ },
+ "status.pending": {
+ "message": "Чакане за синхронизация"
+ },
+ "status.security": {
+ "message": "Грешка при осъществяването на защитена връзка. Използвате ли собственоръчно направен сертификат или сертификат, на който Thunderbird няма доверие? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Не се поддържа, пропуснато"
+ },
+ "status.success": {
+ "message": "Добре"
+ },
+ "status.syncing": {
+ "message": "Синхронизиране"
+ },
+ "supportwizard.footer": {
+ "message": "От тази информация на следващата стъпка ще се отвори незавършено писмо, което можете да променяте (например да включите снимки от екрана). Едва чрез изпращане на писмото, докладът за грешка ще замине."
+ },
+ "supportwizard.label.description": {
+ "message": "Подробно описание на грешката:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Компонента на TbSync, при която наблюдавахте грешката:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Изберете компонента…"
+ },
+ "supportwizard.label.summary": {
+ "message": "Обобщение на грешката:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Събиране на всякаква информация за да може да може от доклада за грешки да се разбере какво куца."
+ },
+ "supportwizard.provider": {
+ "message": "Доставчик: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Създаване на доклад за грешка"
+ },
+ "syncstate.accountdone": {
+ "message": "Синхронизирането на регистрацията приключи"
+ },
+ "syncstate.done": {
+ "message": "Подготовка на следващия ресурс за синхронизация"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 удостоверяване"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Подкана за въвеждане на информация за регистрация"
+ },
+ "syncstate.preparing": {
+ "message": "Подготовка на следващия ресурс за синхронизация"
+ },
+ "syncstate.syncing": {
+ "message": "Започване на синхронизацията"
+ },
+ "target.orphaned": {
+ "message": "Прекъсната връзка"
+ },
+ "toolbar.label": {
+ "message": "Синхронизирай всички TbSync регистрации"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Синхронизирай информацията от всички TbSync регистрации със сървърите"
+ }
+} \ No newline at end of file
diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json
new file mode 100644
index 0000000..f87b1e3
--- /dev/null
+++ b/_locales/cs/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "To help fix this error, you could send a debug log to the TbSync developer. Prepare that email now?"
+ },
+ "NoDebugLog": {
+ "message": "Could not find any useful debug messages. Please activate debug mode, restart Thunderbird and repeat all the steps needed to trigger the erroneous behavior."
+ },
+ "OopsMessage": {
+ "message": "Oops! TbSync was not able to start!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "TbSync debug log has been enabled, please restart Thunderbird and again try to open TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "It is not possible to trace this error, because debug log is currently not enabled. Do you want to enable debug log now, to help fix this error?"
+ },
+ "accountacctions.delete": {
+ "message": "Delete account “##accountname##”"
+ },
+ "accountacctions.disable": {
+ "message": "Disable account “##accountname##”"
+ },
+ "accountacctions.enable": {
+ "message": "Enable account “##accountname##” & try to connect to server"
+ },
+ "accountacctions.sync": {
+ "message": "Synchronize account “##accountname##”"
+ },
+ "addressbook.searchall": {
+ "message": "Search all address books"
+ },
+ "addressbook.searchgal": {
+ "message": "Search this address book and the global directory (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Search this address book"
+ },
+ "eventlog.clear": {
+ "message": "Vymazat"
+ },
+ "eventlog.close": {
+ "message": "Zavřít"
+ },
+ "eventlog.title": {
+ "message": "Protokol událostí"
+ },
+ "extensionDescription": {
+ "message": "TbSync je společné rozhraní pro správu cloudových účtů a synchronizaci kontaktů, úkolů a kalendářů s Thunderbirdem."
+ },
+ "google.translate.code": {
+ "message": "en"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Error"
+ },
+ "info.idle": {
+ "message": "Idle"
+ },
+ "installProvider.header": {
+ "message": "Provider “##replace.1##” for TbSync is not yet installed."
+ },
+ "manager.AccountActions": {
+ "message": "Akce s účty"
+ },
+ "manager.AddAccount": {
+ "message": "Přidat nový účet"
+ },
+ "manager.DeleteAccount": {
+ "message": "Odstranit účet"
+ },
+ "manager.DisableAccount": {
+ "message": "Deaktivovat Účet"
+ },
+ "manager.EnableAccount": {
+ "message": "Povolit účet a pokusit se připojit k serveru"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Zkusit znovu připojit k serveru"
+ },
+ "manager.ShowEventLog": {
+ "message": "Otevřít log"
+ },
+ "manager.SyncAll": {
+ "message": "Synchronizovat povolené účty"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Synchronizovat účet"
+ },
+ "manager.accounts": {
+ "message": "Účty"
+ },
+ "manager.accountsettings": {
+ "message": "Nastavení účtu"
+ },
+ "manager.catman.text": {
+ "message": "TbSync také synchronizuje kategorie kontaktů, které jsou užitečnou náhradou za seznamy kontaktů. Chcete-li je používat v adresáři Thunderbird, můžete nainstalovat doplněk Správce kategorií. Umožňuje spravovat překrývající se skupiny kontaktů s kategoriemi a poskytuje spoustu dalších funkcí souvisejících s kategoriemi. Doplněk je v oficiálním úložišti doplňků Mozilla:"
+ },
+ "manager.community": {
+ "message": "Komunita"
+ },
+ "manager.connecting": {
+ "message": "Connecting to server"
+ },
+ "manager.help": {
+ "message": "Nápověda"
+ },
+ "manager.help.createbugreport": {
+ "message": "Vytvořit hlášení o chybě"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Aby TbSync shromažďoval pouze data o ladění, která jsou relevantní pro Vaší zprávu o chybě, prosím restartujte Thunderbird a pak opakujte přesně kroky, které reprodukují chybové chování. Pak zde můžete vytvořit hlášení o chybě."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Zakázán"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Povoleno: pouze chyby"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Povoleno: Záznam všech odeslaných a přijatých dat"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Povoleno: Záznam všech dat a některých interních hodnot ladění"
+ },
+ "manager.help.debugmode": {
+ "message": "Režim ladění:"
+ },
+ "manager.help.fixit": {
+ "message": "Můžete pomoci ji opravit zasláním hlášení o chybě. K tomu je potřeba aktivace režimu ladění."
+ },
+ "manager.help.foundabug": {
+ "message": "Našli jste chybu?"
+ },
+ "manager.help.needhelp": {
+ "message": "Potřebujete pomoc?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Zobrazit debug log"
+ },
+ "manager.help.wiki": {
+ "message": "Otevřete wiki stránky projektu TbSync, kde najdete další informace, návody a detailní popisy konfigurace."
+ },
+ "manager.installprovider.link": {
+ "message": "Klikněte na následující odkaz pro otevření stránky chybějícího poskytovatele synchronizace. Zde naleznete další informace o poskytovateli a budete mít možnost jej nainstalovat:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Tento poskytovatel synchronizace není hostován v oficiálním úložišti doplňků Thunderbird, a proto nebyl zkontrolován zaměstnanci Thunderbirdu. Poskytovatel by mohl dělat špatné věci ve vašem systému. Použijte na vlastní riziko."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Aby se zabránilo chybám synchronizace, některá nastavení nelze upravit u aktivního účtu."
+ },
+ "manager.missingprovider": {
+ "message": "Tento účet vyžaduje synchronizačního poskytovatele ##provider##, který momentálně není nainstalován."
+ },
+ "manager.noaccounts": {
+ "message": "Zatím nejsou definovány žádné účty."
+ },
+ "manager.provider": {
+ "message": "Instalovat poskytovatele"
+ },
+ "manager.provider4tbsync": {
+ "message": "Poskytovatel pro TbSync"
+ },
+ "manager.resource": {
+ "message": "Resource"
+ },
+ "manager.shorttitle": {
+ "message": "Správce účtů"
+ },
+ "manager.status": {
+ "message": "Stav"
+ },
+ "manager.supporter.contributors": {
+ "message": "Přispěvatelé a překladatelé"
+ },
+ "manager.supporter.details": {
+ "message": "Podrobnosti"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponzoři testovacích účtů"
+ },
+ "manager.tabs.status": {
+ "message": "Stav synchronizace"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Periodická synchronizace (v minutách)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Povolit a synchronizovat účet"
+ },
+ "manager.tabs.status.general": {
+ "message": "Obecné"
+ },
+ "manager.tabs.status.never": {
+ "message": "Nastav 0 pro zákaz periodické synchronizace. Push synchronizace zatím není implementována."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Dostupné zdroje"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Vyberte, které z nalezených skupin by měly být synchronizovány s Thunderbirdem."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Synchronizovat"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Zkusit znovu připojit k serveru"
+ },
+ "manager.title": {
+ "message": "Správce účtů TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Zkusit znovu připojit k serveru"
+ },
+ "menu.settingslabel": {
+ "message": "Nastavení synchronizace (TbSync)"
+ },
+ "password.account": {
+ "message": "Účet:"
+ },
+ "password.description": {
+ "message": "Prosím aktualizujte přihlašovací údaje pro následující účet TbSync:"
+ },
+ "password.password": {
+ "message": "Heslo:"
+ },
+ "password.title": {
+ "message": "TbSync žádost o pověření"
+ },
+ "password.user": {
+ "message": "Uživatel:"
+ },
+ "popup.opensettings": {
+ "message": "Správce účtů TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Are you sure you want to delete account ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Are you sure you want to disable this account? All local modifications, which have not been synced yet, will be lost!"
+ },
+ "prompt.Erase": {
+ "message": "Are you sure you want to remove this account of an unknown provider from the accounts lists?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Are you sure you want to unsubscribe this item? All local modifications, which have not been synced yet, will be lost!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript Error! Please check the event log for more details."
+ },
+ "status.OAuthAbortError": {
+ "message": "OAuth 2.0 authentication process aborted by user."
+ },
+ "status.OAuthHttpError": {
+ "message": "OAuth 2.0 authentication process failed (HTTP error ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Could not connect to OAuth 2.0 authentication server."
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 authentication server returned: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Not synchronized"
+ },
+ "status.apiError": {
+ "message": "API implementation error"
+ },
+ "status.disabled": {
+ "message": "Account is not enabled, synchronization is disabled."
+ },
+ "status.foldererror": {
+ "message": "At least one resource encountered a synchronization error. Please check the event log for more details."
+ },
+ "status.modified": {
+ "message": "Local modifications"
+ },
+ "status.network": {
+ "message": "Could not connect to server (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Could not find any resources on the server."
+ },
+ "status.notargets": {
+ "message": "Aborting synchronization, because sync targets could not be created."
+ },
+ "status.notsyncronized": {
+ "message": "Account needs to be synchronized, at least one item is out of sync."
+ },
+ "status.pending": {
+ "message": "Waiting to be synchronized"
+ },
+ "status.security": {
+ "message": "Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Not yet supported, skipped"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Synchronizing"
+ },
+ "supportwizard.footer": {
+ "message": "Z informací zde uvedených bude v dalším kroku vygenerován e-mail, který můžete následně upravit (například přidání screenshotů). Zasláním e-mailu bude teprve zaslána chybová zpráva."
+ },
+ "supportwizard.label.description": {
+ "message": "Podrobný popis chyby:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Komponenta TbSync, kde jste zaznamenali chybu:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Vyberte komponentu…"
+ },
+ "supportwizard.label.summary": {
+ "message": "Krátké shrnutí chyby:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Shromažďování všech informací pro efektivní zpracování hlášení o chybě."
+ },
+ "supportwizard.provider": {
+ "message": "Provider: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Vytvořit hlášení o chybě"
+ },
+ "syncstate.accountdone": {
+ "message": "Finished account"
+ },
+ "syncstate.done": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 authentication"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Prompting for credentials"
+ },
+ "syncstate.preparing": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.syncing": {
+ "message": "Initialize synchronization"
+ },
+ "target.orphaned": {
+ "message": "Disconnected"
+ },
+ "toolbar.label": {
+ "message": "Synchronizovat všechny TbSync účty"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Synchronizovat poslední změny"
+ }
+} \ No newline at end of file
diff --git a/_locales/de/messages.json b/_locales/de/messages.json
new file mode 100644
index 0000000..727886c
--- /dev/null
+++ b/_locales/de/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Um diesen Fehler zu beheben, können Sie einen Fehlerbericht an den Entwickler von TbSync schicken. Soll der Fehlerbericht jetzt angefertigt werden?"
+ },
+ "NoDebugLog": {
+ "message": "Es liegen keine aussagekräftigen Debug-Meldungen vor. Bitte aktivieren Sie den Debug-Modus, starten Thunderbird neu und wiederholen dann alle Schritte um das fehlerhafte Verhalten zu reproduzieren."
+ },
+ "OopsMessage": {
+ "message": "Oops! TbSync konnte nicht starten!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Der TbSync Debug-Modus wurde aktiviert, bitte starten Sie Thunderbird neu und versuchen Sie dann noch einmal TbSync zu öffnen."
+ },
+ "UnableToTraceError": {
+ "message": "Es ist im Augenblick nicht möglich, diesen Fehler zu untersuchen, da der TbSync Debug-Modus nicht aktiviert ist. Soll der Debug-Modus aktiviert werden, damit dieser Fehler untersucht und behoben werden kann?"
+ },
+ "accountacctions.delete": {
+ "message": "Konto '##accountname##' löschen"
+ },
+ "accountacctions.disable": {
+ "message": "Konto '##accountname##' deaktivieren"
+ },
+ "accountacctions.enable": {
+ "message": "Konto '##accountname##' aktivieren & Server kontaktieren"
+ },
+ "accountacctions.sync": {
+ "message": "Konto '##accountname##' synchronisieren"
+ },
+ "addressbook.searchall": {
+ "message": "Alle Adressbücher durchsuchen"
+ },
+ "addressbook.searchgal": {
+ "message": "Dieses Adressbuch und das globale Serververzeichnis durchsuchen (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Dieses Adressbuch durchsuchen"
+ },
+ "eventlog.clear": {
+ "message": "Leeren"
+ },
+ "eventlog.close": {
+ "message": "Schließen"
+ },
+ "eventlog.title": {
+ "message": "Ereignisprotokoll"
+ },
+ "extensionDescription": {
+ "message": "TbSync ist eine zentrale Benutzeroberfläche zur Verwaltung von Cloud-Konten und zur Synchronisierung ihrer Kontakt-, Aufgaben- und Kalenderinformationen mit Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "de"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Fehler"
+ },
+ "info.idle": {
+ "message": "Leerlauf"
+ },
+ "installProvider.header": {
+ "message": "Provider '##replace.1##' für TbSync ist noch nicht installiert."
+ },
+ "manager.AccountActions": {
+ "message": "Konto Aktionen"
+ },
+ "manager.AddAccount": {
+ "message": "Neues Konto hinzufügen"
+ },
+ "manager.DeleteAccount": {
+ "message": "Konto löschen"
+ },
+ "manager.DisableAccount": {
+ "message": "Konto deaktivieren"
+ },
+ "manager.EnableAccount": {
+ "message": "Konto aktivieren & mit dem Server verbinden"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Erneut versuchen mit dem Server zu verbinden"
+ },
+ "manager.ShowEventLog": {
+ "message": "Ereignisprotokoll anzeigen"
+ },
+ "manager.SyncAll": {
+ "message": "Synchronisiere alle aktivierten Konten"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Konto synchronisieren"
+ },
+ "manager.accounts": {
+ "message": "Konten"
+ },
+ "manager.accountsettings": {
+ "message": "Kontoeinstellungen"
+ },
+ "manager.catman.text": {
+ "message": "TbSync synchronisiert auch Kontaktkategorien, die einen effizienten Ersatz für die nicht synchronisierbaren Kontaktlisten darstellen. Um diese im Thunderbird Adressbuch zu nutzen, können Sie das Add-On 'Category Manager' installieren. Dieses Add-On ermöglicht die Verwaltung überlappender kategoriebasierter Kontaktgruppen und stellt eine Reihe anderer kategoriespezifischer Funktionen bereit. Es kann aus dem offiziellen Mozilla Add-On Repository heruntergeladen werden:"
+ },
+ "manager.community": {
+ "message": "Community"
+ },
+ "manager.connecting": {
+ "message": "Verbindung wird hergestellt"
+ },
+ "manager.help": {
+ "message": "Hilfe"
+ },
+ "manager.help.createbugreport": {
+ "message": "Fehlerbericht erstellen"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Damit TbSync nur die für Ihren Fehlerbericht relevanten Debug-Daten sammelt, starten Sie Thunderbird bitte neu und wiederholen dann genau die Schritte, die das fehlerhafte Verhalten reproduzieren. Anschließend können Sie hier Ihren Fehlerbericht erstellen."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Deaktiviert"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Aktiviert: Protokolliert alle Fehler"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Aktiviert: Protokolliert alle gesendeten & empfangenen Daten"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Aktiviert: Protokolliert alle Daten und einige interne Debug-Werte"
+ },
+ "manager.help.debugmode": {
+ "message": "Debug-Modus:"
+ },
+ "manager.help.fixit": {
+ "message": "Sie können helfen den Fehler zu korrigieren, indem Sie dem Entwickler einen Fehlerbericht zusenden. Bitte aktivieren Sie dazu den Debug-Modus."
+ },
+ "manager.help.foundabug": {
+ "message": "Sie haben einen Fehler gefunden?"
+ },
+ "manager.help.needhelp": {
+ "message": "Benötigen Sie Hilfe?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Debug-Log anzeigen"
+ },
+ "manager.help.wiki": {
+ "message": "Besuchen Sie die Wikiseiten des TbSync Projektes, diese beinhalten zusätzliche Informationen, Benutzeranleitungen und detaillierte Konfigurationsbeschreibungen."
+ },
+ "manager.installprovider.link": {
+ "message": "Klicken Sie auf den folgenden Link, um die Informationsseite des fehlenden Provider aufzurufen. Auf dieser Seite finden Sie weitere Informationen zu dem Provider und haben die Möglichkeit, diesen zu installieren:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Dieser Provider stammt nicht aus dem offiziellen Thunderbird Add-On Verzeichnis und wurde demnach nicht von Thunderbird-Mitarbeitern überprüft. Dieser Provider könnte schlimme Dinge mit Ihrem System anstellen. Benutzung auf eigene Gefahr."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Um Synchronisierungsfehler zu vermeiden, können einige Einstellungen nicht bearbeitet werden, während das Konto aktiviert ist."
+ },
+ "manager.missingprovider": {
+ "message": "Dieses Konto benötigt den ##provider## Synchronisationsprovider, der zur Zeit aber nicht installiert ist."
+ },
+ "manager.noaccounts": {
+ "message": "Es sind aktuell noch keine Konten konfiguriert."
+ },
+ "manager.provider": {
+ "message": "Provider installieren"
+ },
+ "manager.provider4tbsync": {
+ "message": "Provider für TbSync"
+ },
+ "manager.resource": {
+ "message": "Ressource"
+ },
+ "manager.shorttitle": {
+ "message": "Kontoverwaltung"
+ },
+ "manager.status": {
+ "message": "Status"
+ },
+ "manager.supporter.contributors": {
+ "message": "Mitwirkende & Übersetzer"
+ },
+ "manager.supporter.details": {
+ "message": "Details"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsoren von Test-Accounts"
+ },
+ "manager.tabs.status": {
+ "message": "Synchronisationsstatus"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Periodische Synchronisation (in Minuten)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Konto aktivieren und synchronisieren"
+ },
+ "manager.tabs.status.general": {
+ "message": "Allgemein"
+ },
+ "manager.tabs.status.never": {
+ "message": "Eine Einstellung von 0 deaktiviert periodische Synchronisation. Push Synchronisation wird noch nicht unterstützt."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Verfügbare Ressourcen"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Wählen Sie aus, welche der gefundenen Ressourcen mit Thunderbird synchronisiert werden sollen."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Jetzt synchronisieren"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Erneut versuchen mit dem Server zu verbinden"
+ },
+ "manager.title": {
+ "message": "TbSync Kontoverwaltung"
+ },
+ "manager.tryagain": {
+ "message": "Erneut versuchen mit dem Server zu verbinden"
+ },
+ "menu.settingslabel": {
+ "message": "Synchronisationseinstellungen (TbSync)"
+ },
+ "password.account": {
+ "message": "Konto:"
+ },
+ "password.description": {
+ "message": "Bitte aktualisieren Sie die Anmeldeinformationen für das folgende TbSync-Konto:"
+ },
+ "password.password": {
+ "message": "Passwort:"
+ },
+ "password.title": {
+ "message": "TbSync Anmeldeinformationen"
+ },
+ "password.user": {
+ "message": "Benutzer:"
+ },
+ "popup.opensettings": {
+ "message": "TbSync Kontoverwaltung öffnen"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Sind Sie sicher, dass Sie das Konto ##accountName## löschen möchten?"
+ },
+ "prompt.Disable": {
+ "message": "Sind Sie sicher, dass Sie dieses Konto deaktivieren möchten? Alle lokalen Veränderungen, die noch nicht synchronisiert wurden, gehen dabei verloren!"
+ },
+ "prompt.Erase": {
+ "message": "Sind Sie sicher, dass Sie dieses Konto eines unbekannten Providers aus der Kontoliste entfernen möchten?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Sind Sie sicher, dass Sie dieses Element nicht länger abonnieren möchten? Alle lokalen Veränderungen, die noch nicht synchronisiert wurden, gehen dabei verloren!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript Fehler! Bitte prüfen Sie das Ereignisprotokoll für weitere Details."
+ },
+ "status.OAuthAbortError": {
+ "message": "OAuth 2.0 Authentifizierungsprozess vom Benutzer abgebrochen."
+ },
+ "status.OAuthHttpError": {
+ "message": "OAuth 2.0 Authentifizierungsprozess fehlgeschlagen (HTTP Error ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Verbindung zum OAuth 2.0 Authentifizierungsserver nicht möglich."
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 Authentifizierungsserver meldet: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Nicht synchronisiert"
+ },
+ "status.apiError": {
+ "message": "API Implementierungsfehler"
+ },
+ "status.disabled": {
+ "message": "Konto ist deaktiviert, Synchronisation ist ausgeschaltet."
+ },
+ "status.foldererror": {
+ "message": "Bei mindestens einer Resource trat ein Synchronisationsfehler auf. Bitte prüfen sie das Ereignisprotokoll für weitere Details."
+ },
+ "status.modified": {
+ "message": "Lokale Änderungen"
+ },
+ "status.network": {
+ "message": "Verbindung zum Server fehlgeschlagen (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Auf dem Server wurden keine Resourcen gefunden."
+ },
+ "status.notargets": {
+ "message": "Synchronisation abgebrochen da die Elemente zum Synchronisieren nicht erstellt werden konnten."
+ },
+ "status.notsyncronized": {
+ "message": "Konto muss synchronisiert werden."
+ },
+ "status.pending": {
+ "message": "Warten auf Synchronisation"
+ },
+ "status.security": {
+ "message": "Fehler beim Aufbau einer sicherern Verbindung. Benutzen Sie eventuell ein selbst signiertes Zertifikat oder ein andersartiges nicht vertrauenswürdiges Zertifikat welches nicht in Thunderbird importiert ist? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Nicht unterstützt"
+ },
+ "status.success": {
+ "message": "Ok"
+ },
+ "status.syncing": {
+ "message": "Synchronisierung"
+ },
+ "supportwizard.footer": {
+ "message": "Aus den hier angegebenen Informationen wird im nächsten Schritt eine E-Mail erzeugt, die Sie nachträglich noch bearbeiten können (z.B. Bildschirmfotos o.Ä. hinzufügen). Erst durch das Absenden der E-Mail, wird der Fehlerbericht verschickt."
+ },
+ "supportwizard.label.description": {
+ "message": "Ausführliche Fehlerbeschreibung:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Komponente von TbSync, bei der Sie den Fehler beobachtet haben:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Komponente auswählen ..."
+ },
+ "supportwizard.label.summary": {
+ "message": "Kurze Zusammenfassung des Fehlers:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Erfassung aller Informationen zur effektiven Bearbeitung des Fehlerberichtes."
+ },
+ "supportwizard.provider": {
+ "message": "Provider: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Fehlerbericht erstellen"
+ },
+ "syncstate.accountdone": {
+ "message": "Kontosynchronisation abgeschlossen"
+ },
+ "syncstate.done": {
+ "message": "Bereite das nächste Element für die Synchronisation vor"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 Authentifizierung"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Aufforderung zur Eingabe der Anmeldeinformationen"
+ },
+ "syncstate.preparing": {
+ "message": "Bereite das nächste Element für die Synchronisation vor"
+ },
+ "syncstate.syncing": {
+ "message": "Initiiere Synchronisation"
+ },
+ "target.orphaned": {
+ "message": "Verbindung getrennt"
+ },
+ "toolbar.label": {
+ "message": "Synchronisiere alle TbSync Konten"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Gleiche alle TbSync Konten mit den Servern ab"
+ }
+} \ No newline at end of file
diff --git a/_locales/en-US/messages.json b/_locales/en-US/messages.json
new file mode 100644
index 0000000..9fdf73d
--- /dev/null
+++ b/_locales/en-US/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "To help fix this error, you could send a debug log to the TbSync developer. Prepare that email now?"
+ },
+ "NoDebugLog": {
+ "message": "Could not find any useful debug messages. Please activate debug mode, restart Thunderbird and repeat all the steps needed to trigger the erroneous behavior."
+ },
+ "OopsMessage": {
+ "message": "Oops! TbSync was not able to start!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "TbSync debug log has been enabled, please restart Thunderbird and again try to open TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "It is not possible to trace this error, because debug log is currently not enabled. Do you want to enable debug log now, to help fix this error?"
+ },
+ "accountacctions.delete": {
+ "message": "Delete account “##accountname##”"
+ },
+ "accountacctions.disable": {
+ "message": "Disable account “##accountname##”"
+ },
+ "accountacctions.enable": {
+ "message": "Enable account “##accountname##” & try to connect to server"
+ },
+ "accountacctions.sync": {
+ "message": "Synchronize account “##accountname##”"
+ },
+ "addressbook.searchall": {
+ "message": "Search all address books"
+ },
+ "addressbook.searchgal": {
+ "message": "Search this address book and the global directory (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Search this address book"
+ },
+ "eventlog.clear": {
+ "message": "Clear"
+ },
+ "eventlog.close": {
+ "message": "Close"
+ },
+ "eventlog.title": {
+ "message": "Event log"
+ },
+ "extensionDescription": {
+ "message": "TbSync is a central user interface to manage cloud accounts and to synchronize their contact, task and calendar information with Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "en"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Error"
+ },
+ "info.idle": {
+ "message": "Idle"
+ },
+ "installProvider.header": {
+ "message": "Provider “##replace.1##” for TbSync is not yet installed."
+ },
+ "manager.AccountActions": {
+ "message": "Account actions"
+ },
+ "manager.AddAccount": {
+ "message": "Add new account"
+ },
+ "manager.DeleteAccount": {
+ "message": "Delete account"
+ },
+ "manager.DisableAccount": {
+ "message": "Disable account"
+ },
+ "manager.EnableAccount": {
+ "message": "Enable account & try to connect to server"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Try again to connect to server"
+ },
+ "manager.ShowEventLog": {
+ "message": "Open event log"
+ },
+ "manager.SyncAll": {
+ "message": "Synchronize all enabled accounts"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Synchronize account"
+ },
+ "manager.accounts": {
+ "message": "Accounts"
+ },
+ "manager.accountsettings": {
+ "message": "Account Settings"
+ },
+ "manager.catman.text": {
+ "message": "TbSync also syncs contact categories, which are a powerful replacement for the non-synchronizable contact lists. To be able to use them in the Thunderbird address book you can install the Category Manager add-on. It allows to manage overlapping category based contact groups and provides a bunch of other category-related features. It can be found in the official Mozilla add-on repository:"
+ },
+ "manager.community": {
+ "message": "Community"
+ },
+ "manager.connecting": {
+ "message": "Connecting to server"
+ },
+ "manager.help": {
+ "message": "Help"
+ },
+ "manager.help.createbugreport": {
+ "message": "Create bug report"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "In order for TbSync to collect only the debug data relevant to your bug report, please restart Thunderbird and then repeat exactly the steps that reproduce the buggy behavior. Then you can create your bug report here."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Disabled"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Enabled: Logging of errors only"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Enabled: Logging of all sent and received data"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Enabled: Logging of all data and some internal debug values"
+ },
+ "manager.help.debugmode": {
+ "message": "Debug mode:"
+ },
+ "manager.help.fixit": {
+ "message": "You can help to fix it by sending in a bug report. This requires the activation of debug mode."
+ },
+ "manager.help.foundabug": {
+ "message": "Found a bug?"
+ },
+ "manager.help.needhelp": {
+ "message": "Need Help?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "View debug log"
+ },
+ "manager.help.wiki": {
+ "message": "Open the wiki pages of the TbSync project, they provide additional information, guides and detailed configuration descriptions."
+ },
+ "manager.installprovider.link": {
+ "message": "Click on the following link to open the info page of the missing synchronization provider. There you will find further information about the provider and you will have the option to install it:"
+ },
+ "manager.installprovider.warning": {
+ "message": "This synchronization provider is not hosted in the official Thunderbird add-on repository and thus has not been reviewed by Thunderbird staff. The provider could do bad things to your system. Use it at your own risk."
+ },
+ "manager.lockedsettings.description": {
+ "message": "To prevent synchronization errors, some settings cannot be edited while the account is enabled."
+ },
+ "manager.missingprovider": {
+ "message": "This account requires the ##provider## synchronization provider, which is currently not installed."
+ },
+ "manager.noaccounts": {
+ "message": "There are not yet any accounts defined."
+ },
+ "manager.provider": {
+ "message": "Install Provider"
+ },
+ "manager.provider4tbsync": {
+ "message": "Provider for TbSync"
+ },
+ "manager.resource": {
+ "message": "Resource"
+ },
+ "manager.shorttitle": {
+ "message": "Account manager"
+ },
+ "manager.status": {
+ "message": "Status"
+ },
+ "manager.supporter.contributors": {
+ "message": "Contributors & Translators"
+ },
+ "manager.supporter.details": {
+ "message": "Details"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsors of test accounts"
+ },
+ "manager.tabs.status": {
+ "message": "Synchronization status"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Periodic synchronization (in minutes)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Enable and synchronize this account"
+ },
+ "manager.tabs.status.general": {
+ "message": "General"
+ },
+ "manager.tabs.status.never": {
+ "message": "A setting of 0 disables periodic sync. Push sync is not yet implemented."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Available resources"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Select which of the found resources should be synchronized with Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Synchronize now"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Try again to connect server"
+ },
+ "manager.title": {
+ "message": "TbSync account manager"
+ },
+ "manager.tryagain": {
+ "message": "Try again to connect server"
+ },
+ "menu.settingslabel": {
+ "message": "Synchronization Settings (TbSync)"
+ },
+ "password.account": {
+ "message": "Account:"
+ },
+ "password.description": {
+ "message": "Please update the credentials for the following TbSync account:"
+ },
+ "password.password": {
+ "message": "Password:"
+ },
+ "password.title": {
+ "message": "TbSync Credential Request"
+ },
+ "password.user": {
+ "message": "User:"
+ },
+ "popup.opensettings": {
+ "message": "Open TbSync account manager"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Are you sure you want to delete account ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Are you sure you want to disable this account? All local modifications, which have not been synced yet, will be lost!"
+ },
+ "prompt.Erase": {
+ "message": "Are you sure you want to remove this account of an unknown provider from the accounts lists?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Are you sure you want to unsubscribe this item? All local modifications, which have not been synced yet, will be lost!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript Error! Please check the event log for more details."
+ },
+ "status.OAuthAbortError": {
+ "message": "OAuth 2.0 authentication process aborted by user."
+ },
+ "status.OAuthHttpError": {
+ "message": "OAuth 2.0 authentication process failed (HTTP error ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Could not connect to OAuth 2.0 authentication server."
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 authentication server returned: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Not synchronized"
+ },
+ "status.apiError": {
+ "message": "API implementation error"
+ },
+ "status.disabled": {
+ "message": "Account is not enabled, synchronization is disabled."
+ },
+ "status.foldererror": {
+ "message": "At least one resource encountered a synchronization error. Please check the event log for more details."
+ },
+ "status.modified": {
+ "message": "Local modifications"
+ },
+ "status.network": {
+ "message": "Could not connect to server (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Could not find any resources on the server."
+ },
+ "status.notargets": {
+ "message": "Aborting synchronization, because sync targets could not be created."
+ },
+ "status.notsyncronized": {
+ "message": "Account needs to be synchronized, at least one item is out of sync."
+ },
+ "status.pending": {
+ "message": "Waiting to be synchronized"
+ },
+ "status.security": {
+ "message": "Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Not yet supported, skipped"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Synchronizing"
+ },
+ "supportwizard.footer": {
+ "message": "From the information given here, an e-mail will be generated in the next step, which you can subsequently edit (for example, adding screenshots or similar). Only by actually sending that e-mail, the error report will be sent."
+ },
+ "supportwizard.label.description": {
+ "message": "Detailed error description:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Component of TbSync where you have observed the error:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Select component…"
+ },
+ "supportwizard.label.summary": {
+ "message": "Short summary of the error:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Gathering of all information to effectively process the bug report."
+ },
+ "supportwizard.provider": {
+ "message": "Provider: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Create bug report"
+ },
+ "syncstate.accountdone": {
+ "message": "Finished account"
+ },
+ "syncstate.done": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 authentication"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Prompting for credentials"
+ },
+ "syncstate.preparing": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.syncing": {
+ "message": "Initialize synchronization"
+ },
+ "target.orphaned": {
+ "message": "Disconnected"
+ },
+ "toolbar.label": {
+ "message": "Synchronize all TbSync accounts"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Synchronize latest changes"
+ }
+} \ No newline at end of file
diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json
new file mode 100644
index 0000000..47af9ec
--- /dev/null
+++ b/_locales/fr/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Pour aider à la résolution de cette erreur, vous pourriez envoyer le journal de débogage au développeur de TbSync. Voulez-vous qu'on prépare un courriel à cette fin ?"
+ },
+ "NoDebugLog": {
+ "message": "Il n'a pas été possible de trouver un message de débogage utile. Merci d'activer le mode de débogage, de redémarrer Thunderbird puis de répéter les étapes qui ont menées au comportement anormal du programme."
+ },
+ "OopsMessage": {
+ "message": "Oups! TbSync n'a pas réussi à démarrer !"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Le journal de débogage de TbSync a été activé. Veuillez redémarrer Thunderbird et essayer à nouveau d'ouvrir TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Il n'est pas possible de localiser cette erreur, parce que le journal de débogage n'est actuellement pas activé. Désirez-vous activer le journal de débogage maintenant, afin d'aider à résoudre cette erreur ?"
+ },
+ "accountacctions.delete": {
+ "message": "Supprimer le compte '##accountname##'"
+ },
+ "accountacctions.disable": {
+ "message": "Désactiver le compte '##accountname##'"
+ },
+ "accountacctions.enable": {
+ "message": "Activer le compte '##accountname##' et tenter de se connecter au serveur"
+ },
+ "accountacctions.sync": {
+ "message": "Synchroniser le compte '##accountname##'"
+ },
+ "addressbook.searchall": {
+ "message": "Rechercher dans tous les carnets d'adresses"
+ },
+ "addressbook.searchgal": {
+ "message": "Recherche dans ce carnet d'adresse et dans l'annuaire global (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Rechercher dans ce carnet d'adresse"
+ },
+ "eventlog.clear": {
+ "message": "Effacer"
+ },
+ "eventlog.close": {
+ "message": "Fermer"
+ },
+ "eventlog.title": {
+ "message": "Journal d'événements"
+ },
+ "extensionDescription": {
+ "message": "TbSync est une interface permettant de gérer de manière centralisée ses comptes cloud et d'en synchroniser les contacts, tâches et agendas avec Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "fr"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-non-rusted-certificats%3F"
+ },
+ "info.error": {
+ "message": "Erreur"
+ },
+ "info.idle": {
+ "message": "Inactif"
+ },
+ "installProvider.header": {
+ "message": "Le provider TbSync '##replace.1##' n'est pas encore installé."
+ },
+ "manager.AccountActions": {
+ "message": "Actions sur les comptes"
+ },
+ "manager.AddAccount": {
+ "message": "Ajouter un nouveau compte"
+ },
+ "manager.DeleteAccount": {
+ "message": "Supprimer le compte"
+ },
+ "manager.DisableAccount": {
+ "message": "Désactiver le compte"
+ },
+ "manager.EnableAccount": {
+ "message": "Activer le compte et tester la connexion au serveur"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Essayer à nouveau de se connecter au serveur"
+ },
+ "manager.ShowEventLog": {
+ "message": "Montrer le journal d'événements (log)"
+ },
+ "manager.SyncAll": {
+ "message": "Synchroniser tous les comptes activés"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Synchroniser le compte"
+ },
+ "manager.accounts": {
+ "message": "Comptes"
+ },
+ "manager.accountsettings": {
+ "message": "Paramètres du compte"
+ },
+ "manager.catman.text": {
+ "message": "TbSync synchronise également les catégories de contacts, ce qui remplace avantageusement les listes de contacts, qui ne peuvent être synchronisées. Afin de pouvoir les utiliser dans le carnet d'adresses de Thunderbird, vous pouvez installer le module complémentaire Category Manager. Celui-ci vous permet de gérer les contacts sur base de catégories (un contact pouvant en avoir plusieurs) et qui fournit d'autres fonctionnalités liées aux catégories. Ce module peut être trouvé sur le dépôt officiel de Mozilla:"
+ },
+ "manager.community": {
+ "message": "Communauté"
+ },
+ "manager.connecting": {
+ "message": "Connexion au serveur en cours"
+ },
+ "manager.help": {
+ "message": "Aide"
+ },
+ "manager.help.createbugreport": {
+ "message": "Créer un rapport d'erreur"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Afin que TbSync ne collecte que les données de débogages pertinentes pour résoudre votre bogue, veuillez redémarrer Thunderbird et ensuite répéter exactement les étapes qui causent le comportement indésiré. Ensuite, vous pourrez créer un rapport d'erreur depuis cette fenêtre-ci."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Désactivé"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Activé : Seules les erreurs seront journalisées"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Activé : Toutes les données reçues et envoyées seront journalisées"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Activé: Toutes les données ainsi que certaines valeurs de débogages internes seront journalisées"
+ },
+ "manager.help.debugmode": {
+ "message": "Mode de débogage :"
+ },
+ "manager.help.fixit": {
+ "message": "Vous pouvez nous aider à la corriger en nous envoyant un rapport d'erreur. Cette opération nécessite l'activation du mode de débogage."
+ },
+ "manager.help.foundabug": {
+ "message": "Vous avez trouvé un bogue ?"
+ },
+ "manager.help.needhelp": {
+ "message": "Besoin d'aide ?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Voir le journal de débogage"
+ },
+ "manager.help.wiki": {
+ "message": "Ouvrez la page du wiki du projet TbSync; celle-ci fournit des informations complémentaire, un guide d'utilisation ainsi qu'une description détaillé de la configuration du programme."
+ },
+ "manager.installprovider.link": {
+ "message": "Cliquez sur le lien ci-dessous pour ouvrir la page d'information sur le provider de synchronisation manquant. Vous y trouverez de plus amples informations sur le provider et aurez la possibilité de l'installer :"
+ },
+ "manager.installprovider.warning": {
+ "message": "Ce provider de synchronisation n'est pas hébergé sur le dépôt Thunderbird officiel et n'a donc pas été validé par l'équipe de Thunderbird. Il se peut donc qu'il ait un comportement néfaste pour votre système. Utilisez-le à vos propres risques."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Afin d'éviter des erreurs de synchronisation, certains paramètres ne peuvent être modifié tant que ce compte est activé."
+ },
+ "manager.missingprovider": {
+ "message": "Ce compte nécessite le provider de synchronisation (##provider##), lequel n'est pas installé."
+ },
+ "manager.noaccounts": {
+ "message": "Aucun compte n'a encore été configuré."
+ },
+ "manager.provider": {
+ "message": "Installer un provider"
+ },
+ "manager.provider4tbsync": {
+ "message": "Provider pour TbSync"
+ },
+ "manager.resource": {
+ "message": "Ressource"
+ },
+ "manager.shorttitle": {
+ "message": "Gestionnaire de comptes"
+ },
+ "manager.status": {
+ "message": "État"
+ },
+ "manager.supporter.contributors": {
+ "message": "Contributeurs et traducteurs"
+ },
+ "manager.supporter.details": {
+ "message": "Détails"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsors fournissant des comptes de test"
+ },
+ "manager.tabs.status": {
+ "message": "Statut de synchronisation"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Synchronisation périodique (minutes)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Activer et synchroniser ce compte"
+ },
+ "manager.tabs.status.general": {
+ "message": "Général"
+ },
+ "manager.tabs.status.never": {
+ "message": "Fixer ce paramètre à 0 désactive la synchronisation périodique. La synchronisation push n'est pas encore implémentée."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Ressources disponibles"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Choisissez, parmi les ressources disponibles, celles que vous désirez synchroniser avec Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Synchroniser maintenant"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Essayer à nouveau de se connecter au serveur"
+ },
+ "manager.title": {
+ "message": "Gestionnaire de comptes TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Essayer à nouveau de se connecter au serveur"
+ },
+ "menu.settingslabel": {
+ "message": "Paramètres de synchronisation (TbSync)"
+ },
+ "password.account": {
+ "message": "Compte :"
+ },
+ "password.description": {
+ "message": "Veuillez mettre à jour le mot de passe pour le compte TbSync suivant :"
+ },
+ "password.password": {
+ "message": "Mot de passe :"
+ },
+ "password.title": {
+ "message": "Demande de mots de passe TbSync"
+ },
+ "password.user": {
+ "message": "Utilisateur :"
+ },
+ "popup.opensettings": {
+ "message": "Ouvrir le gestionnaire de comptes TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Êtes-vous certain de vouloir supprimer le compte ##accountName## ?"
+ },
+ "prompt.Disable": {
+ "message": "Êtes-vous certain de vouloir désactiver ce compte ? Toutes les modifications locales non encore synchronisées seront perdues !"
+ },
+ "prompt.Erase": {
+ "message": "Êtes-vous certain de vouloir supprimer ce compte d'un provider inconnu de la liste des comptes ?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Êtes-vous certain de vouloir supprimer l'abonnement à cet élément ? Toutes les modifications locales non encore synchronisées seront perdues !"
+ },
+ "status.JavaScriptError": {
+ "message": "Erreur Javascript ! Veuillez lire le journal de débogage pour plus de détails."
+ },
+ "status.OAuthAbortError": {
+ "message": "Processus d'authentification OAuth 2.0 abandonné par l'utilisateur."
+ },
+ "status.OAuthHttpError": {
+ "message": "Le processus d'authentification OAuth 2.0 a échoué (erreur HTTP ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Impossible de se connecter au serveur d'authentification OAuth 2.0."
+ },
+ "status.OAuthServerError": {
+ "message": " Le serveur d’authentification OAuth 2.0 a retourné : ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Non synchronisé"
+ },
+ "status.apiError": {
+ "message": "Erreur dans l'implémentation de l'API"
+ },
+ "status.disabled": {
+ "message": "Le compte n'est pas activé, la synchronisation est désactivée."
+ },
+ "status.foldererror": {
+ "message": "Au moins une ressource a rencontré une erreur de synchronisation. Veuillez lire le journal de débogage pour plus de détails."
+ },
+ "status.modified": {
+ "message": "Modifications locales présentes"
+ },
+ "status.network": {
+ "message": "Impossible de se connecter au serveur (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Impossible de trouver les ressources sur le serveur."
+ },
+ "status.notargets": {
+ "message": "Annulation de la synchronisation en cours : il est impossible de créer les destinations de synchronisation."
+ },
+ "status.notsyncronized": {
+ "message": "Le compte doit être synchronisé : au moins un éléments n'est pas synchronisé."
+ },
+ "status.pending": {
+ "message": "En attente de synchronisation"
+ },
+ "status.security": {
+ "message": "Impossible d'établir une connexion sécurisée. Votre serveur utilise-t-il un certificat auto-signé ou non certifié de confiance, que vous n'avez pas importé dans Thunderbird ? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Pas encore pris en charge, ignoré"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Synchronisation en cours"
+ },
+ "supportwizard.footer": {
+ "message": "Un courriel sera généré à partir des informations fournies ici. Vous aurez la possibilité de le modifier par la suite (par exemple, pour y ajouter des captures d'écrans). Ce n'est que lorsque vous aurez envoyé l'e-mail que le rapport d'erreur nous sera envoyé."
+ },
+ "supportwizard.label.description": {
+ "message": "Description détaillée de l'erreur :"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Composant de TbSync où vous avez observé l'erreur :"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Sélectionnez un composant…"
+ },
+ "supportwizard.label.summary": {
+ "message": "Bref résumé de l'erreur :"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Récolte de toutes les informations nécessaires pour gérer efficacement le rapport d'erreur."
+ },
+ "supportwizard.provider": {
+ "message": "Fournisseur : ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Créer un rapport d'erreur"
+ },
+ "syncstate.accountdone": {
+ "message": "Synchronisation du compte terminée"
+ },
+ "syncstate.done": {
+ "message": "Préparation de la synchronisation du prochain élément"
+ },
+ "syncstate.oauthprompt": {
+ "message": "Authentification OAuth 2.0"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Demande d'insertion du mot de passe"
+ },
+ "syncstate.preparing": {
+ "message": "Préparation de la synchronisation du prochain élément"
+ },
+ "syncstate.syncing": {
+ "message": "Initialisation de la synchronisation"
+ },
+ "target.orphaned": {
+ "message": "Connexion rompue"
+ },
+ "toolbar.label": {
+ "message": "Synchroniser tous les comptes TbSync"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Synchroniser les dernières modifications"
+ }
+} \ No newline at end of file
diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json
new file mode 100644
index 0000000..6078aee
--- /dev/null
+++ b/_locales/hu/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "A hiba javításához hibakeresési naplót küldhet a Thunderbird-összehangolás fejlesztőnek. Készüljön fel most arra az e-mailre?"
+ },
+ "NoDebugLog": {
+ "message": "Nem találtak hasznos hibaelhárító üzeneteket. Engedélyezze a hibakeresési módot, indítsa újra a Thunderbird programot, és ismételje meg a hibás viselkedést elindító lépéseket."
+ },
+ "OopsMessage": {
+ "message": "Hoppá, valami hiba történt a Thunderbird-összehangolás indításakor."
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "A Thunderbird-összehangolás hibakeresési napló engedélyezve van, kérjük, indítsa újra a Thunderbird programot, és próbálkozzon újra a Thunderbird-összehangolás megnyitásával."
+ },
+ "UnableToTraceError": {
+ "message": "Nem lehet nyomon követni ezt a hibát, mert a hibakeresési napló jelenleg nincs engedélyezve. Szeretné engedélyezni a hibakeresési naplót, hogy segítsen javítani a hibát?"
+ },
+ "accountacctions.delete": {
+ "message": "A(z) „##accountname##” fiók törlése"
+ },
+ "accountacctions.disable": {
+ "message": "A(z) „##accountname##” fiók letiltása"
+ },
+ "accountacctions.enable": {
+ "message": "A(z) „##accountname##” fiók engedélyezze és próbálja meg csatlakozni a kiszolgálóhoz"
+ },
+ "accountacctions.sync": {
+ "message": "A(z) „##accountname##” fiók összehangolása"
+ },
+ "addressbook.searchall": {
+ "message": "Keresése az összes névjegyzékben"
+ },
+ "addressbook.searchgal": {
+ "message": "Keresése ezt a névjegyzékben és a globális könyvtárban (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Keresése csak ezt a címjegyzékben"
+ },
+ "eventlog.clear": {
+ "message": "üres"
+ },
+ "eventlog.close": {
+ "message": "közel"
+ },
+ "eventlog.title": {
+ "message": "Eseménynapló"
+ },
+ "extensionDescription": {
+ "message": "A Thunderbird-összehangolás egy központi felhasználói felület kezeli a felhőfiókokat, és összehangolja névjegyzékait, feladatait és naptáradatait a Thunderbird programmal."
+ },
+ "google.translate.code": {
+ "message": "hu"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Hiba"
+ },
+ "info.idle": {
+ "message": "Tétlen"
+ },
+ "installProvider.header": {
+ "message": "A(z) „##replace.1##” szolgáltató a Thunderbird-összehangoláshoz még nincs telepítve."
+ },
+ "manager.AccountActions": {
+ "message": "Fiókműveletek"
+ },
+ "manager.AddAccount": {
+ "message": "Új fiók hozzáadása"
+ },
+ "manager.DeleteAccount": {
+ "message": "Fiók törlése"
+ },
+ "manager.DisableAccount": {
+ "message": "Fiók letiltása"
+ },
+ "manager.EnableAccount": {
+ "message": "Engedélyezze a fiókot és próbáljon csatlakozni a kiszolgálóhoz"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Próbálkozzon újra a kiszolgálóhoz való csatlakozással"
+ },
+ "manager.ShowEventLog": {
+ "message": "Eseménynapló megjelenítése"
+ },
+ "manager.SyncAll": {
+ "message": "Az összes engedélyezett fiók összehangolása"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Fiók összehangolása"
+ },
+ "manager.accounts": {
+ "message": "Fiókok"
+ },
+ "manager.accountsettings": {
+ "message": "Fiók beállítások"
+ },
+ "manager.catman.text": {
+ "message": "A Thunderbird-összehangolásja az érintkezési kategóriákat is, amelyek hatékonyan helyettesítik a nem összehangolható kapcsolattartókat. A Thunderbird névjegyzékben való használatához telepítheti a Kategóriakezelő kiegészítőt. Lehetővé teszi az átfedő kategóriákon alapuló kapcsolattartó csoportok kezelését és egy sor más kategóriához kapcsolódó funkciót. Ez megtalálható a hivatalos Thunderbird-kiegészítő adattárban:"
+ },
+ "manager.community": {
+ "message": "Közösség"
+ },
+ "manager.connecting": {
+ "message": "Kiszolgáló összekapcsolása"
+ },
+ "manager.help": {
+ "message": "Segítség"
+ },
+ "manager.help.createbugreport": {
+ "message": "Hibabejelentés létrehozása"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Indítsa újra a Thunderbird alkalmazást, majd ismételje meg a pontos lépéseket a hiba reprodukálásához. Ez csak a hibajelentéssel kapcsolatos hibajelentéseket gyűjti össze. Hibabejelentés létrehozása itt."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Tiltva"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Engedélyezve: Csak hibanaplózása"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Engedélyezve: Az összes küldött és fogadott adat naplózása"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Engedélyezve: Minden adat naplózása és néhány belső hibakeresési érték"
+ },
+ "manager.help.debugmode": {
+ "message": "Hibakeresési mód:"
+ },
+ "manager.help.fixit": {
+ "message": "Hibajelentés küldése a javításhoz. Hibakeresési mód bekapcsolása engedélyeket igényel."
+ },
+ "manager.help.foundabug": {
+ "message": "Talált egy hibát?"
+ },
+ "manager.help.needhelp": {
+ "message": "Segítségre van szüksége?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Hibakeresési napló megtekintése"
+ },
+ "manager.help.wiki": {
+ "message": "További tudnivalók, útmutatók és részletes beállítások, olvassa el a Thunderbird-összehangolás projekt wiki oldalát."
+ },
+ "manager.installprovider.link": {
+ "message": "Kattintson a következő linkre a hiányzó összhangzós szolgáltató info oldalának megnyitásához. Ott talál további információkat a szolgáltatóról, és lehetősége van a telepítésre:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Ez az összehangoló szolgáltató nem a hivatalos Thunderbird-kiegészítő adattárban található, így a Thunderbird személyzete nem vizsgálta felül. A szolgáltató rossz dolgokat tehet a rendszerében. Használja saját felelősségére."
+ },
+ "manager.lockedsettings.description": {
+ "message": "A összehangolási hibák elkerülése érdekében egyes beállítások nem szerkeszthetők a fiók engedélyezése közben."
+ },
+ "manager.missingprovider": {
+ "message": "A összehangolás szolgáltató (##provider##), amelyhez a fiók társítva van, nincs telepítve."
+ },
+ "manager.noaccounts": {
+ "message": "Még nincsenek fiókok meghatározott."
+ },
+ "manager.provider": {
+ "message": "Szolgáltató telepítése"
+ },
+ "manager.provider4tbsync": {
+ "message": "Thunderbird-összehangolás szolgáltató"
+ },
+ "manager.resource": {
+ "message": "Mappa"
+ },
+ "manager.shorttitle": {
+ "message": "Fiókkezelő"
+ },
+ "manager.status": {
+ "message": "Állapot"
+ },
+ "manager.supporter.contributors": {
+ "message": "Közreműködők és fordítók"
+ },
+ "manager.supporter.details": {
+ "message": "Részletek"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Tesztszámla támogatók"
+ },
+ "manager.tabs.status": {
+ "message": "Összehangolási állapot"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Időszakos összehangolás (perc)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Fiók engedélyezze és összehangolása"
+ },
+ "manager.tabs.status.general": {
+ "message": "Tábornok"
+ },
+ "manager.tabs.status.never": {
+ "message": "A 0 beállítás letiltja az időszakos összehangolást. A nyomösszehangolást még meg nem valósult."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Elérhető erőforrások"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Válassza ki, hogy a talált erőforrások közül melyiket összehangolni kell a Thunderbird programmal."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Összehangolás"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Próbálkozzon újra a kiszolgálóhoz való csatlakozással"
+ },
+ "manager.title": {
+ "message": "Thunderbird-összehangolás fiókkezelő"
+ },
+ "manager.tryagain": {
+ "message": "Próbálkozzon újra a kiszolgálóhoz való csatlakozással"
+ },
+ "menu.settingslabel": {
+ "message": "Összehangolási beállítások"
+ },
+ "password.account": {
+ "message": "Számla:"
+ },
+ "password.description": {
+ "message": "Kérjük, frissítse a következő TbSync-fiók hitelesítő adatait:"
+ },
+ "password.password": {
+ "message": "Jelszó:"
+ },
+ "password.title": {
+ "message": "TbSync hitelesítési kérés"
+ },
+ "password.user": {
+ "message": "Használó:"
+ },
+ "popup.opensettings": {
+ "message": "Thunderbird-összehangolás fiókkezelő megnyitasa"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Biztos benne, hogy törölni szeretne fiók ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Biztosan letiltja ezt a fiókot? Minden helyi módosítás, amely még nincs összehangolva, elveszik!"
+ },
+ "prompt.Erase": {
+ "message": "Biztosan eltávolít egy ismeretlen szolgáltató fiókját a fiókok listáiról?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Biztosan le szeretné iratkozni ezt az elemet? Minden helyi módosítás, amely még nincs összehangolva, elveszik!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript hiba! További részletekért nézze meg az eseménynaplót."
+ },
+ "status.OAuthAbortError": {
+ "message": "Az OAuth 2.0 hitelesítési folyamatot a felhasználó megszakította."
+ },
+ "status.OAuthHttpError": {
+ "message": "Az OAuth 2.0 hitelesítési folyamat sikertelen (HTTP hibakód: ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Nem sikerült csatlakozni az OAuth 2.0 hitelesítési kiszolgálóhoz."
+ },
+ "status.OAuthServerError": {
+ "message": " Az OAuth 2.0 hitelesítési kiszolgáló visszatért: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Nincs összehangolva"
+ },
+ "status.apiError": {
+ "message": "API végrehajtási hiba"
+ },
+ "status.disabled": {
+ "message": "A fiók nem engedélyezett, az összehangolás le van tiltva."
+ },
+ "status.foldererror": {
+ "message": "Legalább egy erőforrás szinkronizálási hibát észlelt. További részletekért nézze meg az eseménynaplót."
+ },
+ "status.modified": {
+ "message": "Helyi módosítások"
+ },
+ "status.network": {
+ "message": "Nem tudott csatlakozni a kiszolgálóhoz (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "A kiszolgálón nem található erőforrás."
+ },
+ "status.notargets": {
+ "message": "A összehangolás abbahagyása, mert a összehangolási célokat nem lehetett létrehozni."
+ },
+ "status.notsyncronized": {
+ "message": "A fiókját összehangolni kell. Legalább egy elem összehangolatlan."
+ },
+ "status.pending": {
+ "message": "Várakozás összehangolásra"
+ },
+ "status.security": {
+ "message": "Biztonsági kapcsolat létrehozása nem sikerült. Ön aláírta vagy egyébként nem megbízható tanúsítványt importálta a Thunderbirdbe? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Még nem támogatott, kimarad"
+ },
+ "status.success": {
+ "message": "Rendben"
+ },
+ "status.syncing": {
+ "message": "Összehangolás folyamatban"
+ },
+ "supportwizard.footer": {
+ "message": "Ebből az információból e-mail készül. További tájékoztatás adhatók hozzá, pl. képernyőképek. A hibajelentés elküldéséhez az e-mailt el kell küldeni."
+ },
+ "supportwizard.label.description": {
+ "message": "Részletes hiba leírása:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Thunderbird-összehangolás összetevő, ahol hiba történt:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Összetevő kiválasztása…"
+ },
+ "supportwizard.label.summary": {
+ "message": "A hiba rövid összefoglalása:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Információgyűjtés a hibajelentés feldolgozásához."
+ },
+ "supportwizard.provider": {
+ "message": "Szolgáltató: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Hibabejelentés létrehozása"
+ },
+ "syncstate.accountdone": {
+ "message": "Fiók befejezve"
+ },
+ "syncstate.done": {
+ "message": "Az összehangolás következő elem előkészítése"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 hitelesítés"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Hitelesítő adatok megadásának kérése"
+ },
+ "syncstate.preparing": {
+ "message": "Az összehangolás következő elem előkészítése"
+ },
+ "syncstate.syncing": {
+ "message": "Összehangolás előkészítése"
+ },
+ "target.orphaned": {
+ "message": "A kapcsolat megszakadt"
+ },
+ "toolbar.label": {
+ "message": "Az összes Thunderbird-összehangolás fiók összehangolása"
+ },
+ "toolbar.tooltiptext": {
+ "message": "A legújabb változtatások összehangolása"
+ }
+} \ No newline at end of file
diff --git a/_locales/it/messages.json b/_locales/it/messages.json
new file mode 100644
index 0000000..ae4425e
--- /dev/null
+++ b/_locales/it/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Per contribuire a correggere quest'errore potresti inviare un log di debug allo sviluppatore di TbSync. Preparare tale messaggio di posta elettronica ora?"
+ },
+ "NoDebugLog": {
+ "message": "Non è stato possibile trovare alcun messaggio di debug utile. Attiva la modalità di debug, riavvia Thunderbird e ripeti tutti i passaggi richiesti per scatenare il comportamento errato."
+ },
+ "OopsMessage": {
+ "message": "Oops! TbSync non è stato in grado di avviarsi!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Il log di debug di TbSync è stato abilitato, riavviare Thunderbird e riprovare ad aprire TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Non è possibile localizzare quest'errore perché i log di debug non sono attualmente abilitati. Abilitare ora il log di debug per contribuire a correggere quest'errore?"
+ },
+ "accountacctions.delete": {
+ "message": "Elimina l'account '##accountname##'"
+ },
+ "accountacctions.disable": {
+ "message": "Disabilita l'account '##accountname##'"
+ },
+ "accountacctions.enable": {
+ "message": "Abilita l'account '##accountname##' e tenta la connessione al server"
+ },
+ "accountacctions.sync": {
+ "message": "Sincronizza l'account '##accountname##'"
+ },
+ "addressbook.searchall": {
+ "message": "Cerca in tutte le rubriche"
+ },
+ "addressbook.searchgal": {
+ "message": "Cerca in questa rubrica e nella directory globale (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Cerca in questa rubrica"
+ },
+ "eventlog.clear": {
+ "message": "Svuota"
+ },
+ "eventlog.close": {
+ "message": "Chiudi"
+ },
+ "eventlog.title": {
+ "message": "Registro eventi"
+ },
+ "extensionDescription": {
+ "message": "TbSync è un'interfaccia utente che consente di gestire in modo centralizzato account cloud e di sincronizzare i loro contatti, attività e calendari con Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "it"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Errore"
+ },
+ "info.idle": {
+ "message": "Inattivo"
+ },
+ "installProvider.header": {
+ "message": "Il provider '##replace.1##' per TbSync non è ancora installato."
+ },
+ "manager.AccountActions": {
+ "message": "Azioni account"
+ },
+ "manager.AddAccount": {
+ "message": "Aggiungi nuovo account"
+ },
+ "manager.DeleteAccount": {
+ "message": "Elimina account"
+ },
+ "manager.DisableAccount": {
+ "message": "Disabilita account"
+ },
+ "manager.EnableAccount": {
+ "message": "Abilita account e testa la connessione al server"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Riprova a collegarti al server"
+ },
+ "manager.ShowEventLog": {
+ "message": "Mostra registro eventi"
+ },
+ "manager.SyncAll": {
+ "message": "Sincronizza tutti gli account abilitati"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Sincronizza account"
+ },
+ "manager.accounts": {
+ "message": "Account"
+ },
+ "manager.accountsettings": {
+ "message": "Impostazioni account"
+ },
+ "manager.catman.text": {
+ "message": "TbSync sincronizza anche le categorie dei contatti, che sono un potente sostituto degli elenchi contatti (non sincronizzabili). Per poterle utilizzare nella Rubrica di Thunderbird è possibile installare il componente aggiuntivo Category Manager, che consente di gestire gruppi di contatti basati su categorie (anche sovrapposte) e che fornisce altre funzionalità legate alle categorie. Lo si può trovare nel repository ufficiale Mozilla dei componenti aggiuntivi:"
+ },
+ "manager.community": {
+ "message": "Comunità"
+ },
+ "manager.connecting": {
+ "message": "Connessione al server in corso"
+ },
+ "manager.help": {
+ "message": "Aiuto"
+ },
+ "manager.help.createbugreport": {
+ "message": "Crea segnalazione d'errore"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Per far sì che TbSync raccolga solo i messaggi di debug significativi per la segnalazione d'errore, riavvia Thunderbird e ripeti esattamente i passaggi necessari per riprodurre il comportamento errato. In seguito puoi creare qui la segnalazione d'errore."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Disabilitata"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Abilitata: registra solo gli errori"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Abilitata: registra tutti i dati inviati e ricevuti"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Abilitata: registra tutti i dati e alcuni valori interni di debug"
+ },
+ "manager.help.debugmode": {
+ "message": "Modalità debug:"
+ },
+ "manager.help.fixit": {
+ "message": "Puoi contribuire a correggerlo inviando una segnalazione d'errore. Quest'operazione richiede di attivare la modalità di debug."
+ },
+ "manager.help.foundabug": {
+ "message": "Hai trovato un bug?"
+ },
+ "manager.help.needhelp": {
+ "message": "Serve aiuto?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Visualizza log di debug"
+ },
+ "manager.help.wiki": {
+ "message": "Apri le pagine del wiki del progetto TbSync; queste forniscono informazioni aggiuntive, guide e descrizioni dettagliate sulla configurazione."
+ },
+ "manager.installprovider.link": {
+ "message": "Clicca sul collegamento seguente per aprire la pagina informativa del provider di sincronizzazione mancante. In essa sarà possibile trovare ulteriori informazioni sul provider e si avrà la possibilità di installarlo:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Questo provider di sincronizzazione non è ospitato nel repository ufficiale componenti aggiuntivi di Thunderbird e quindi non è stato esaminato dallo staff di Thunderbird. Il provider potrebbe eseguire azioni malevole sul proprio sistema. Lo si utilizzi a proprio rischio."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Per prevenire errori di sincronizzazione, alcune impostazioni non possono essere modificate quando l'account è abilitato."
+ },
+ "manager.missingprovider": {
+ "message": "Il provider di sincronizzazione a cui è associato quest'account (##provider##) non è installato."
+ },
+ "manager.noaccounts": {
+ "message": "Non sono stati definiti ancora account."
+ },
+ "manager.provider": {
+ "message": "Installa provider"
+ },
+ "manager.provider4tbsync": {
+ "message": "Provider per TbSync"
+ },
+ "manager.resource": {
+ "message": "Cartella"
+ },
+ "manager.shorttitle": {
+ "message": "Gestore account"
+ },
+ "manager.status": {
+ "message": "Stato"
+ },
+ "manager.supporter.contributors": {
+ "message": "Collaboratori e traduttori"
+ },
+ "manager.supporter.details": {
+ "message": "Dettagli"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsor per account di test"
+ },
+ "manager.tabs.status": {
+ "message": "Stato sincronizzazione"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Sincronizza periodicamente ogni (minuti)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Abilita e sincronizza quest'account"
+ },
+ "manager.tabs.status.general": {
+ "message": "Generale"
+ },
+ "manager.tabs.status.never": {
+ "message": "Se l'impostazione è a 0, la sincronizzazione periodica è disabilitata. La sincronizzazione push non è ancora stata implementata."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Risorse disponibili"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Selezionare le risorse fra quelle trovate che devono essere sincronizzate con Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Sincronizza ora"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Riprova a collegarti al server"
+ },
+ "manager.title": {
+ "message": "Gestore account TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Riprova a collegarti al server"
+ },
+ "menu.settingslabel": {
+ "message": "Impostazioni sincronizzazione (TbSync)"
+ },
+ "password.account": {
+ "message": "Account:"
+ },
+ "password.description": {
+ "message": "Aggiorna le credenziali per il seguente account TbSync:"
+ },
+ "password.password": {
+ "message": "Password:"
+ },
+ "password.title": {
+ "message": "Richiesta credenziali TbSync"
+ },
+ "password.user": {
+ "message": "Utente:"
+ },
+ "popup.opensettings": {
+ "message": "Apri il Gestore account TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Eliminare l'account ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Disabilitare quest'account? Tutte le modifiche locali non ancora sincronizzate andranno perse!"
+ },
+ "prompt.Erase": {
+ "message": "Rimuovere quest'account di un provider sconosciuto dall'elenco degli account?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Annullare la sottoscrizione a questo elemento? Tutte le modifiche locali non ancora sincronizzate andranno perse!"
+ },
+ "status.JavaScriptError": {
+ "message": "Errore Javascript! Si prega di controllare il registro eventi per maggiori dettagli."
+ },
+ "status.OAuthAbortError": {
+ "message": "Processo di autenticazione OAuth 2.0 interrotto dall'utente."
+ },
+ "status.OAuthHttpError": {
+ "message": "Processo di autenticazione OAuth 2.0 non riuscito (errore HTTP ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Impossibile connettersi al server di autenticazione OAuth 2.0."
+ },
+ "status.OAuthServerError": {
+ "message": " Il server di autenticazione OAuth 2.0 ha restituito: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Non sincronizzato"
+ },
+ "status.apiError": {
+ "message": "Errore di implementazione dell'API"
+ },
+ "status.disabled": {
+ "message": "L'account non è abilitato, la sincronizzazione è disabilitata."
+ },
+ "status.foldererror": {
+ "message": "Almeno una risorsa ha riscontrato un errore di sincronizzazione. Si prega di controllare il registro eventi per maggiori dettagli."
+ },
+ "status.modified": {
+ "message": "Modifiche locali presenti"
+ },
+ "status.network": {
+ "message": "Impossibile collegarsi al server (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Impossibile trovare risorse sul server."
+ },
+ "status.notargets": {
+ "message": "Interruzione sincronizzazione in corso: non è possibile creare le destinazioni sincronizzazione."
+ },
+ "status.notsyncronized": {
+ "message": "L'account deve essere sincronizzato, almeno un elemento non è sincronizzato."
+ },
+ "status.pending": {
+ "message": "In attesa di sincronizzazione"
+ },
+ "status.security": {
+ "message": "Impossibile stabilire una connessione sicura. Si sta utilizzando un certificato autofirmato o non affidabile senza averlo importato in Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Non ancora supportato, omesso"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Sincronizzazione in corso"
+ },
+ "supportwizard.footer": {
+ "message": "Nel prossimo passaggio sarà generato un messaggio di posta elettronica a partire dalle informazioni fornite qui; puoi modificarlo in seguito (ad esempio, aggiungendo schermate o simili). La segnalazione d'errore verrà inviata solo inviando tale messaggio."
+ },
+ "supportwizard.label.description": {
+ "message": "Descrizione dettagliata dell'errore:'"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Componente di TbSync dove hai osservato l'errore:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Seleziona componente..."
+ },
+ "supportwizard.label.summary": {
+ "message": "Breve riassunto dell'errore:'"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Raccolta di tutte le informazioni necessarie per gestire efficacemente la segnalazione d'errore."
+ },
+ "supportwizard.provider": {
+ "message": "Provider: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Crea segnalazione d'errore"
+ },
+ "syncstate.accountdone": {
+ "message": "Sincronizzazione account completata"
+ },
+ "syncstate.done": {
+ "message": "Preparazione prossimo elemento per la sincronizzazione in corso"
+ },
+ "syncstate.oauthprompt": {
+ "message": "Autenticazione OAuth 2.0"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Richiesta di inserire le credenziali"
+ },
+ "syncstate.preparing": {
+ "message": "Preparazione prossimo elemento per la sincronizzazione in corso"
+ },
+ "syncstate.syncing": {
+ "message": "Inizializzazione sincronizzazione in corso"
+ },
+ "target.orphaned": {
+ "message": "Connessione disconnessa"
+ },
+ "toolbar.label": {
+ "message": "Sincronizza tutti gli account TbSync"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Sincronizza le ultime modifiche"
+ }
+} \ No newline at end of file
diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json
new file mode 100644
index 0000000..a73f656
--- /dev/null
+++ b/_locales/ja/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "エラー修正を補助するために、 TbSync 開発者にデバッグログを送ることができます。今すぐメールを準備しますか?"
+ },
+ "NoDebugLog": {
+ "message": "有用なデバッグメッセージを見つけることができませんでした。デバッグモードを有効化し、 Thunderbird を再起動した後、誤動作を引き起こすのに必要な全ての手順を繰り返してください。"
+ },
+ "OopsMessage": {
+ "message": "おおっと、 TbSync を開始できませんでした!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "TbSync のデバッグログが有効化されました。Thunderbird を再起動し、もう一度 TbSync を開いてみてください。"
+ },
+ "UnableToTraceError": {
+ "message": "デバッグログは現在有効ではないため、このエラーを追跡することはできません。 エラー修正を補助するために、デバッグログを今すぐ有効にしますか?"
+ },
+ "accountacctions.delete": {
+ "message": "アカウント “##accountname##” を削除"
+ },
+ "accountacctions.disable": {
+ "message": "アカウント “##accountname##” を無効化"
+ },
+ "accountacctions.enable": {
+ "message": "アカウント “##accountname##” を有効化し、サーバーへの接続を試みる"
+ },
+ "accountacctions.sync": {
+ "message": "アカウント “##accountname##” を同期"
+ },
+ "addressbook.searchall": {
+ "message": "全てのアドレス帳を検索"
+ },
+ "addressbook.searchgal": {
+ "message": "Search this address book and the global directory (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "このアドレス帳を検索"
+ },
+ "eventlog.clear": {
+ "message": "クリア"
+ },
+ "eventlog.close": {
+ "message": "閉じる"
+ },
+ "eventlog.title": {
+ "message": "イベントログ"
+ },
+ "extensionDescription": {
+ "message": "TbSync は Thunderbird において、クラウド・アカウントを管理したり、連絡先、タスクやカレンダーの情報を同期したりする、中央ユーザーインターフェイスです。"
+ },
+ "google.translate.code": {
+ "message": "ja"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "エラー"
+ },
+ "info.idle": {
+ "message": "待機中"
+ },
+ "installProvider.header": {
+ "message": "TbSync のプロパイダ “##replace.1##” は、まだインストールされていません。"
+ },
+ "manager.AccountActions": {
+ "message": "アカウント操作"
+ },
+ "manager.AddAccount": {
+ "message": "新規アカウントを追加"
+ },
+ "manager.DeleteAccount": {
+ "message": "アカウントを削除"
+ },
+ "manager.DisableAccount": {
+ "message": "アカウントを無効化"
+ },
+ "manager.EnableAccount": {
+ "message": "アカウントを有効化し、サーバーへの接続を試みる"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Try again to connect to server"
+ },
+ "manager.ShowEventLog": {
+ "message": "イベントログを開く"
+ },
+ "manager.SyncAll": {
+ "message": "全ての有効化されているアカウントを同期"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "アカウントを同期"
+ },
+ "manager.accounts": {
+ "message": "アカウント"
+ },
+ "manager.accountsettings": {
+ "message": "アカウント設定"
+ },
+ "manager.catman.text": {
+ "message": "TbSync は、非同期の連絡先リストに代わる、強力なコンタクトカテゴリも同期します。 Thunderbird のアドレス帳でそれらを使用できるようにするには、Category Manager アドオンをインストールすることができます。 重複するカテゴリベースのコンタクトグループを管理し、その他たくさんのカテゴリ関連機能を提供します。 Mozilla 公式のアドオンリポジトリにあります:"
+ },
+ "manager.community": {
+ "message": "コミュニティ"
+ },
+ "manager.connecting": {
+ "message": "サーバーに接続中"
+ },
+ "manager.help": {
+ "message": "ヘルプ"
+ },
+ "manager.help.createbugreport": {
+ "message": "バグレポートを作成"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "TbSync がバグレポートに関連するデバッグデータだけを収集するためには、Thunderbird を再起動してから、不具合のある動作を再現する手順を、正確に繰り返してください。その後、ここでバグレポートを作成することができます。"
+ },
+ "manager.help.debuglevel.0": {
+ "message": "無効"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "有効: エラーのみ記録"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "有効: 送受信データを全て記録"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "有効: 全てのデータといくつかの内部デバッグ値を記録"
+ },
+ "manager.help.debugmode": {
+ "message": "デバッグ モード:"
+ },
+ "manager.help.fixit": {
+ "message": "バグレポートを送信することにより、修正するのを助けることができます。これにはデバッグモードの有効化が必要です。"
+ },
+ "manager.help.foundabug": {
+ "message": "不具合を見つけたら?"
+ },
+ "manager.help.needhelp": {
+ "message": "お困りですか?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "デバッグ ログを見る"
+ },
+ "manager.help.wiki": {
+ "message": "TbSync プロジェクトの Wiki ページを開いてください。そこでは追加の情報、ガイドや、詳細設定に関する説明を提供しています。"
+ },
+ "manager.installprovider.link": {
+ "message": "Click on the following link to open the info page of the missing synchronization provider. There you will find further information about the provider and you will have the option to install it:"
+ },
+ "manager.installprovider.warning": {
+ "message": "この同期プロバイダは、公式の Thunderbird アドオンリポジトリではホストされていないため、 Thunderbird スタッフによって確認されていません。 プロバイダはシステムに悪影響を及ぼす可能性があります。自己責任で使用してください。"
+ },
+ "manager.lockedsettings.description": {
+ "message": "同期エラーを防ぐため、アカウントが有効化されている状態では、いくつかの設定は変更できません。"
+ },
+ "manager.missingprovider": {
+ "message": "このアカウントには ##provider## 同期プロバイダが必要です。これは現在インストールされていません。"
+ },
+ "manager.noaccounts": {
+ "message": "There are not yet any accounts defined."
+ },
+ "manager.provider": {
+ "message": "プロパイダをインストール"
+ },
+ "manager.provider4tbsync": {
+ "message": "TbSync のプロパイダ"
+ },
+ "manager.resource": {
+ "message": "リソース"
+ },
+ "manager.shorttitle": {
+ "message": "アカウントマネージャー"
+ },
+ "manager.status": {
+ "message": "状態"
+ },
+ "manager.supporter.contributors": {
+ "message": "貢献者と翻訳者"
+ },
+ "manager.supporter.details": {
+ "message": "詳細"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsors of test accounts"
+ },
+ "manager.tabs.status": {
+ "message": "同期状態"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "定期的な同期間隔 (分)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "このアカウントを有効化し同期"
+ },
+ "manager.tabs.status.general": {
+ "message": "一般設定"
+ },
+ "manager.tabs.status.never": {
+ "message": "0を設定すると、定期的な同期をしません。プッシュ同期には、まだ対応していません。"
+ },
+ "manager.tabs.status.resources": {
+ "message": "利用可能なリソース"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Thunderbird と同期させるリソースを選択します。"
+ },
+ "manager.tabs.status.sync": {
+ "message": "今すぐ同期"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Try again to connect server"
+ },
+ "manager.title": {
+ "message": "TbSync アカウントマネージャー"
+ },
+ "manager.tryagain": {
+ "message": "Try again to connect server"
+ },
+ "menu.settingslabel": {
+ "message": "同期設定 (TbSync)"
+ },
+ "password.account": {
+ "message": "アカウント:"
+ },
+ "password.description": {
+ "message": "次の TbSync アカウントの資格情報を更新してください:"
+ },
+ "password.password": {
+ "message": "パスワード:"
+ },
+ "password.title": {
+ "message": "TbSync 資格情報リクエスト"
+ },
+ "password.user": {
+ "message": "ユーザー:"
+ },
+ "popup.opensettings": {
+ "message": "TbSync アカウントマネージャーを開く"
+ },
+ "prompt.DeleteAccount": {
+ "message": "アカウント ##accountName## を削除しますか?"
+ },
+ "prompt.Disable": {
+ "message": "このアカウントを無効化しますか? 同期されていない全てのローカルの変更は失われます!"
+ },
+ "prompt.Erase": {
+ "message": "Are you sure you want to remove this account of an unknown provider from the accounts lists?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "この項目の登録を解除しますか? 同期されていない全てのローカルの変更は失われます!"
+ },
+ "status.JavaScriptError": {
+ "message": "Javascript エラー! 詳細はイベントログを確認してください。"
+ },
+ "status.OAuthAbortError": {
+ "message": "OAuth 2.0 認証プロセスは、ユーザーによりキャンセルされました。"
+ },
+ "status.OAuthHttpError": {
+ "message": "OAuth 2.0 認証プロセスは失敗しました (HTTP エラー ##replace.1##) 。"
+ },
+ "status.OAuthNetworkError": {
+ "message": "OAuth 2.0 認証サーバーに接続できません。"
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 認証サーバーが返答しました: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "同期されていません"
+ },
+ "status.apiError": {
+ "message": "API 実装エラー"
+ },
+ "status.disabled": {
+ "message": "アカウントは有効化されておらず、同期は無効化されています。"
+ },
+ "status.foldererror": {
+ "message": "一つ以上のリソースが同期エラーになりました。詳しくはイベントログをご覧ください。"
+ },
+ "status.modified": {
+ "message": "ローカルの変更"
+ },
+ "status.network": {
+ "message": "サーバーに接続できません (##replace.1##)。"
+ },
+ "status.no-folders-found-on-server": {
+ "message": "サーバー上のリソースが見つかりませんでした。"
+ },
+ "status.notargets": {
+ "message": "Aborting synchronization, because sync targets could not be created."
+ },
+ "status.notsyncronized": {
+ "message": "アカウントを同期する必要があります。一つ以上のアイテムが同期されていません。"
+ },
+ "status.pending": {
+ "message": "同期待ち"
+ },
+ "status.security": {
+ "message": "Could not establish a secure connection. Are you using a self-signed or otherwise untrusted certificate without importing it into Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "サポートされていません。スキップします。"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "同期中"
+ },
+ "supportwizard.footer": {
+ "message": "ここに記載されている情報から、次のステップで電子メールが生成され、その後編集することができます (例えば、スクリーンショットや類似したものを追加するなど)。 そのメールを実際に送信することによってのみ、エラーレポートが送信されます。"
+ },
+ "supportwizard.label.description": {
+ "message": "エラーの詳細な説明:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Component of TbSync where you have observed the error:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "コンポーネントを選択…"
+ },
+ "supportwizard.label.summary": {
+ "message": "エラーの概要:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Gathering of all information to effectively process the bug report."
+ },
+ "supportwizard.provider": {
+ "message": "プロパイダ: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "バグレポートを作成"
+ },
+ "syncstate.accountdone": {
+ "message": "Finished account"
+ },
+ "syncstate.done": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 認証"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Prompting for credentials"
+ },
+ "syncstate.preparing": {
+ "message": "Preparing next item for synchronization"
+ },
+ "syncstate.syncing": {
+ "message": "Initialize synchronization"
+ },
+ "target.orphaned": {
+ "message": "Disconnected"
+ },
+ "toolbar.label": {
+ "message": "全ての TbSync アカウントを同期"
+ },
+ "toolbar.tooltiptext": {
+ "message": "最新の変更を同期"
+ }
+} \ No newline at end of file
diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json
new file mode 100644
index 0000000..9d439b8
--- /dev/null
+++ b/_locales/pl/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Aby naprawić ten błąd, możesz wysłać dziennik debugowania do programisty TbSync. Przygotować ten e-mail teraz?"
+ },
+ "NoDebugLog": {
+ "message": "Nie można znaleźć żadnych przydatnych wiadomości debugowania. Aktywuj tryb debugowania, uruchom ponownie Thunderbirda i powtórz wszystkie kroki potrzebne do wywołania błędnego zachowania."
+ },
+ "OopsMessage": {
+ "message": "Ups! TbSync nie mógł się uruchomić!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Dziennik debugowania TbSync został włączony, zrestartuj Thunderbirda i ponownie spróbuj otworzyć TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Nie można prześledzić tego błędu, ponieważ dziennik debugowania nie jest obecnie włączony. Czy chcesz teraz włączyć dziennik debugowania, aby pomóc naprawić ten błąd?"
+ },
+ "accountacctions.delete": {
+ "message": "Usuń konto “##accountname##”"
+ },
+ "accountacctions.disable": {
+ "message": "Wyłącz konto “##accountname##”"
+ },
+ "accountacctions.enable": {
+ "message": "Włącz konto “##accountname##” i spróbuj połączyć się z serwerem"
+ },
+ "accountacctions.sync": {
+ "message": "Synchronizuj konto “##accountname##”"
+ },
+ "addressbook.searchall": {
+ "message": "Wyszukuj we wszystkich książkach adresowych"
+ },
+ "addressbook.searchgal": {
+ "message": "Wyszukuj w tej książce adresowej i w katalogu globalnym (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Wyszukuj w tej książce adresowej"
+ },
+ "eventlog.clear": {
+ "message": "Wyczyść"
+ },
+ "eventlog.close": {
+ "message": "Zamknij"
+ },
+ "eventlog.title": {
+ "message": "Dziennik zdarzeń"
+ },
+ "extensionDescription": {
+ "message": "TbSync to centralny interfejs użytkownika do zarządzania kontami w chmurze i synchronizowania ich danych kontaktowych, zadań i kalendarza z Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "pl"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Błąd"
+ },
+ "info.idle": {
+ "message": "Bezczynny"
+ },
+ "installProvider.header": {
+ "message": "Dostawca “##replace.1##” dla TbSync nie jest jeszcze zainstalowany."
+ },
+ "manager.AccountActions": {
+ "message": "Akcje konta"
+ },
+ "manager.AddAccount": {
+ "message": "Dodaj nowe konto"
+ },
+ "manager.DeleteAccount": {
+ "message": "Usuń konto"
+ },
+ "manager.DisableAccount": {
+ "message": "Wyłącz konto"
+ },
+ "manager.EnableAccount": {
+ "message": "Włącz konto i spróbuj połączyć się z serwerem"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Spróbuj ponownie połączyć do serwera"
+ },
+ "manager.ShowEventLog": {
+ "message": "Otwórz dziennik zdarzeń"
+ },
+ "manager.SyncAll": {
+ "message": "Synchronizuj wszystkie włączone konta"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Synchronizuj konto"
+ },
+ "manager.accounts": {
+ "message": "Konta"
+ },
+ "manager.accountsettings": {
+ "message": "Ustawienia konta"
+ },
+ "manager.catman.text": {
+ "message": "TbSync synchronizuje również kategorie kontaktów, które są potężnym zamiennikiem dla niesynchronizowanych list kontaktów. Aby móc z nich korzystać w książce adresowej Thunderbird, możesz zainstalować dodatek Category Manager. Pozwala zarządzać nakładającymi się grupami kontaktów opartymi na kategoriach i zapewnia szereg innych funkcji związanych z kategoriami. Można go znaleźć w oficjalnym repozytorium dodatków Mozilla:"
+ },
+ "manager.community": {
+ "message": "Społeczność"
+ },
+ "manager.connecting": {
+ "message": "Łączenie z serwerem"
+ },
+ "manager.help": {
+ "message": "Pomoc"
+ },
+ "manager.help.createbugreport": {
+ "message": "Utwórz raport o błędzie"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Aby TbSync zbierał tylko dane debugowania istotne dla Twojego zgłoszenia błędu, uruchom ponownie Thunderbirda, a następnie powtórz dokładnie kroki, które odtworzą błędne zachowanie. Następnie możesz tutaj utworzyć raport o błędzie."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Wyłączony"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Włączony: Rejestrowanie tylko błędów"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Włączony: Rejestrowanie wszystkich wysłanych i odebranych danych"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Włączone: Rejestrowanie wszystkich danych i niektórych wewnętrznych wartości debugowania"
+ },
+ "manager.help.debugmode": {
+ "message": "Tryb debugowania:"
+ },
+ "manager.help.fixit": {
+ "message": "Możesz pomóc to naprawić, wysyłając raport o błędzie. Wymaga to włączenia trybu debugowania."
+ },
+ "manager.help.foundabug": {
+ "message": "Znalazłeś błąd?"
+ },
+ "manager.help.needhelp": {
+ "message": "Potrzebujesz pomocy?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Zobacz log debugowania"
+ },
+ "manager.help.wiki": {
+ "message": "Otwórz strony wiki projektu TbSync, zawierają one dodatkowe informacje, przewodniki i szczegółowe opisy konfiguracji."
+ },
+ "manager.installprovider.link": {
+ "message": "Kliknij poniższy link, aby otworzyć stronę informacyjną brakującego dostawcy synchronizacji. Znajdziesz tam dodatkowe informacje na temat dostawcy i będziesz mógł go zainstalować:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Ten dostawca synchronizacji nie jest udostępniony w oficjalnym repozytorium dodatków Thunderbird, a zatem nie został sprawdzony przez pracowników Thunderbirda. Dostawca może wyrządzić szkody w Twoim systemie. Używaj go na własne ryzyko."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Aby zapobiec błędom synchronizacji, niektórych ustawień nie można edytować, gdy konto jest włączone."
+ },
+ "manager.missingprovider": {
+ "message": "To konto wymaga dostawcy synchronizacji ##provider##, który obecnie nie jest zainstalowany."
+ },
+ "manager.noaccounts": {
+ "message": "Nie ma jeszcze zdefiniowanych kont."
+ },
+ "manager.provider": {
+ "message": "Zainstaluj dostawcę"
+ },
+ "manager.provider4tbsync": {
+ "message": "Dostawca dla TbSync"
+ },
+ "manager.resource": {
+ "message": "Zasób"
+ },
+ "manager.shorttitle": {
+ "message": "Menedżer konta"
+ },
+ "manager.status": {
+ "message": "Status"
+ },
+ "manager.supporter.contributors": {
+ "message": "Współpracownicy i Tłumacze"
+ },
+ "manager.supporter.details": {
+ "message": "Szczegóły"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Sponsorzy kont testowych"
+ },
+ "manager.tabs.status": {
+ "message": "Status synchronizacji"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Okresowa synchronizacja (w minutach)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Włącz i synchronizuj to konto"
+ },
+ "manager.tabs.status.general": {
+ "message": "Ogólne"
+ },
+ "manager.tabs.status.never": {
+ "message": "Ustawienie 0 wyłącza okresową synchronizację. Synchronizacja push nie jest jeszcze zaimplementowana."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Dostępne zasoby"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Wybierz, które ze znalezionych zasobów powinny być zsynchronizowane z Thunderbirdem."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Synchronizuj teraz"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Spróbuj ponownie połączyć z serwerem"
+ },
+ "manager.title": {
+ "message": "Menedżer konta TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Spróbuj ponownie połączyć z serwerem"
+ },
+ "menu.settingslabel": {
+ "message": "Ustawienia synchronizacji (TbSync)"
+ },
+ "password.account": {
+ "message": "Konto:"
+ },
+ "password.description": {
+ "message": "Zaktualizuj dane logowania dla następującego konta TbSync:"
+ },
+ "password.password": {
+ "message": "Hasło:"
+ },
+ "password.title": {
+ "message": "Żądanie Danych Logowania TbSync"
+ },
+ "password.user": {
+ "message": "Użytkownik:"
+ },
+ "popup.opensettings": {
+ "message": "Otwórz menedżer konta TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Czy na pewno chcesz usunąć konto ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Czy na pewno chcesz wyłączyć to konto? Wszystkie lokalne modyfikacje, które nie zostały jeszcze zsynchronizowane, zostaną utracone!"
+ },
+ "prompt.Erase": {
+ "message": "Czy jesteś pewien, że chcesz usunąć to konto nieznanego dostawcy z list kont?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Czy na pewno chcesz anulować subskrypcję tego elementu? Wszystkie lokalne modyfikacje, które nie zostały jeszcze zsynchronizowane, zostaną utracone!"
+ },
+ "status.JavaScriptError": {
+ "message": "Błąd Javascript! Sprawdź dziennik zdarzeń, aby uzyskać więcej informacji."
+ },
+ "status.OAuthAbortError": {
+ "message": "Proces uwierzytelniania OAuth 2.0 przerwany przez użytkownika."
+ },
+ "status.OAuthHttpError": {
+ "message": "Proces uwierzytelniania OAuth 2.0 nie powiódł się (błąd HTTP ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Nie można połączyć się z serwerem uwierzytelniania OAuth 2.0."
+ },
+ "status.OAuthServerError": {
+ "message": " Serwer uwierzytelniania OAuth 2.0 zwrócił: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Nie zsynchronizowane"
+ },
+ "status.apiError": {
+ "message": "Błąd implementacji API"
+ },
+ "status.disabled": {
+ "message": "Konto nie jest włączone, synchronizacja jest wyłączona."
+ },
+ "status.foldererror": {
+ "message": "Przynajmniej jeden zasób napotkał błąd synchronizacji. Sprawdź dziennik zdarzeń, aby uzyskać więcej informacji."
+ },
+ "status.modified": {
+ "message": "Zmiany lokalne"
+ },
+ "status.network": {
+ "message": "Nie można połączyć z serwerem (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Nie można znaleźć żadnych zasobów na serwerze."
+ },
+ "status.notargets": {
+ "message": "Przerywanie synchronizacji, ponieważ nie można utworzyć celów synchronizacji."
+ },
+ "status.notsyncronized": {
+ "message": "Konto musi zostać zsynchronizowane, co najmniej jeden element nie jest zsynchronizowany."
+ },
+ "status.pending": {
+ "message": "Oczekiwanie na synchronizację"
+ },
+ "status.security": {
+ "message": "Nie można ustanowić bezpiecznego połączenia. Czy używasz certyfikatu self-signed lub innego niezaufanego certyfikatu bez importowania go do Thunderbirda? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Jeszcze nie wspierane, pominięto"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Synchronizuję"
+ },
+ "supportwizard.footer": {
+ "message": "Z podanych tutaj informacji w następnym kroku zostanie wygenerowany e-mail, który możesz następnie edytować (na przykład dodając zrzuty ekranu lub podobne). Tylko poprzez faktyczne wysłanie tej wiadomości zostanie wysłany raport o błędzie."
+ },
+ "supportwizard.label.description": {
+ "message": "Szczegółowy opis błędu:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Składnik TbSync, w którym zaobserwowano błąd:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Wybierz składnik…"
+ },
+ "supportwizard.label.summary": {
+ "message": "Krótkie podsumowanie błędu:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Zbieranie wszystkich informacji w celu skutecznego przetworzenia zgłoszenia błędu."
+ },
+ "supportwizard.provider": {
+ "message": "Dostawca: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Utwórz raport o błędzie"
+ },
+ "syncstate.accountdone": {
+ "message": "Konto gotowe"
+ },
+ "syncstate.done": {
+ "message": "Przygotowuję następny element do synchronizacji"
+ },
+ "syncstate.oauthprompt": {
+ "message": "Uwierzytelnianie OAuth 2.0"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Monitowanie o dane logowania"
+ },
+ "syncstate.preparing": {
+ "message": "Przygotowuję następny element do synchronizacji"
+ },
+ "syncstate.syncing": {
+ "message": "Zainicjuj synchronizację"
+ },
+ "target.orphaned": {
+ "message": "Rozłączony"
+ },
+ "toolbar.label": {
+ "message": "Synchronizuj wszystkie konta TbSync"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Synchronizuj najnowsze zmiany"
+ }
+} \ No newline at end of file
diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json
new file mode 100644
index 0000000..e35753c
--- /dev/null
+++ b/_locales/pt_BR/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Para ajudar a corrigir esse erro, você poderia enviar um log de depuração para o desenvolvedor do TbSync. Enviar esse email agora?"
+ },
+ "NoDebugLog": {
+ "message": "Não foi possível encontrar nenhuma mensagem de depuração útil. Por favor, ative o modo de depuração, reinicie o Thunderbird e repita todas as etapas necessárias para acionar o comportamento errado."
+ },
+ "OopsMessage": {
+ "message": "Erro! O TbSync não pode iniciar!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "O log de depuração do TbSync foi ativado, reinicie o Thunderbird e tente abrir novamente o TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Não é possível rastrear este erro, porque o log de depuração não está habilitado no momento. Deseja ativar o log de depuração agora para ajudar a corrigir esse erro?"
+ },
+ "accountacctions.delete": {
+ "message": "Excluir conta '##accountname##'"
+ },
+ "accountacctions.disable": {
+ "message": "Desativar conta '##accountname##'"
+ },
+ "accountacctions.enable": {
+ "message": "Ativar conta '##accountname##' e tentar conectar-se ao servidor"
+ },
+ "accountacctions.sync": {
+ "message": "Sincronizar conta '##accountname##'"
+ },
+ "addressbook.searchall": {
+ "message": "Pesquisar todos os catálogos de endereços"
+ },
+ "addressbook.searchgal": {
+ "message": "Pesquisar este catálogo de endereços e o diretório global (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Pesquisar este catálogo de endereços"
+ },
+ "eventlog.clear": {
+ "message": "Limpar"
+ },
+ "eventlog.close": {
+ "message": "Fechar"
+ },
+ "eventlog.title": {
+ "message": "log de eventos"
+ },
+ "extensionDescription": {
+ "message": "O TbSync é uma central para gerenciar contas em nuvem e para sincronizar as informações de contatos, tarefas e calendários com o Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "pt"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Erro"
+ },
+ "info.idle": {
+ "message": "Ocioso"
+ },
+ "installProvider.header": {
+ "message": "O provedor '##replace.1##' para o TbSync ainda não está instalado."
+ },
+ "manager.AccountActions": {
+ "message": "Ações da conta"
+ },
+ "manager.AddAccount": {
+ "message": "Adicionar nova conta"
+ },
+ "manager.DeleteAccount": {
+ "message": "Excluir conta"
+ },
+ "manager.DisableAccount": {
+ "message": "Desativar conta"
+ },
+ "manager.EnableAccount": {
+ "message": "Ativar conta e tentar conectar ao servidor"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Tentar conectar no servidor novamente"
+ },
+ "manager.ShowEventLog": {
+ "message": "Mostrar log de eventos"
+ },
+ "manager.SyncAll": {
+ "message": "Sincronizar todas as contas ativadas"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Sincronizar conta"
+ },
+ "manager.accounts": {
+ "message": "Contas"
+ },
+ "manager.accountsettings": {
+ "message": "Configurações da conta"
+ },
+ "manager.catman.text": {
+ "message": "O TbSync também sincroniza as categorias de contatos, que são um poderoso substituto para as listas de contatos não sincronizáveis. Para poder usá-los no catálogo de endereços do Thunderbird, você pode instalar o complemento do «Category Manager». Ele permite gerenciar grupos de contatos com base em categorias sobrepostas e fornece vários outros recursos relacionados a categorias. Pode ser encontrado no repositório oficial do Mozilla:"
+ },
+ "manager.community": {
+ "message": "Comunidade"
+ },
+ "manager.connecting": {
+ "message": "Conectando ao servidor"
+ },
+ "manager.help": {
+ "message": "Ajuda"
+ },
+ "manager.help.createbugreport": {
+ "message": "Criar relatório de bug"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Para que o TbSync colete apenas os dados de depuração relevantes para o seu relatório de erros, reinicie o Thunderbird e repita exatamente as etapas que reproduzem o comportamento de bugs. Então você pode criar seu relatório de bug aqui."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Desabilitado"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Habilitado: Somente registro de erros"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Habilitado: Registro de todos os dados enviados e recebidos"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Habilitado: Log de todos os dados e alguns valores de depuração internos"
+ },
+ "manager.help.debugmode": {
+ "message": "Modo de depuração:"
+ },
+ "manager.help.fixit": {
+ "message": "Você pode ajudar a consertá-lo enviando um relatório de bug. Isso requer a ativação do modo de depuração."
+ },
+ "manager.help.foundabug": {
+ "message": "Encontrou um bug?"
+ },
+ "manager.help.needhelp": {
+ "message": "Precisa de ajuda?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "Exibir log de depuração"
+ },
+ "manager.help.wiki": {
+ "message": "Abra as páginas wiki do projeto TbSync, eles fornecem informações adicionais, guias e descrições detalhadas da configuração."
+ },
+ "manager.installprovider.link": {
+ "message": "Clique no link a seguir para abrir a página de informações do provedor de sincronização ausente. Lá você encontrará mais informações sobre o provedor e terá a opção de instalá-lo:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Este provedor de sincronização não é hospedado no repositório oficial do Thunderbird e, portanto, não foi revisado pela equipe do Thunderbird. O provedor pode prejudicar o seu sistema. Use por sua conta e risco."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Para evitar erros de sincronização, algumas configurações não podem ser editadas enquanto a conta está ativada."
+ },
+ "manager.missingprovider": {
+ "message": "Essa conta requer o provedor de sincronização ##provider##, que atualmente não está instalado."
+ },
+ "manager.noaccounts": {
+ "message": "Não existem contas definidas."
+ },
+ "manager.provider": {
+ "message": "Instalar provedor"
+ },
+ "manager.provider4tbsync": {
+ "message": "Provedor para o TbSync"
+ },
+ "manager.resource": {
+ "message": "Recurso"
+ },
+ "manager.shorttitle": {
+ "message": "Gerenciar conta"
+ },
+ "manager.status": {
+ "message": "Status"
+ },
+ "manager.supporter.contributors": {
+ "message": "Contribuidores e Tradutores"
+ },
+ "manager.supporter.details": {
+ "message": "Detalhes"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Patrocinadores de contas de teste"
+ },
+ "manager.tabs.status": {
+ "message": "Status de sincronização"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Sincronização periódica (em minutos)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Ativar e sincronizar esta conta"
+ },
+ "manager.tabs.status.general": {
+ "message": "Geral"
+ },
+ "manager.tabs.status.never": {
+ "message": "Deixar a configuração como 0 desabilita a sincronização periódica. A sincronização push ainda não está implementada."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Recursos disponíveis"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Selecione qual dos recursos encontrados deve ser sincronizado com o Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Sincronizar agora"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Tente conectar no servidor novamente"
+ },
+ "manager.title": {
+ "message": "Gerenciador de contas do TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Tente conectar no servidor novamente"
+ },
+ "menu.settingslabel": {
+ "message": "Configurações de sincronização (TbSync)"
+ },
+ "password.account": {
+ "message": "Conta:"
+ },
+ "password.description": {
+ "message": "Por favor, atualize as credenciais para a seguinte conta TbSync:"
+ },
+ "password.password": {
+ "message": "Senha:"
+ },
+ "password.title": {
+ "message": "Solicitação de Credencial do TbSync"
+ },
+ "password.user": {
+ "message": "Usuário:"
+ },
+ "popup.opensettings": {
+ "message": "Abra o gerenciador de contas do TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Tem certeza de que deseja excluir a conta ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Tem certeza de que deseja desativar esta conta? Todas as modificações locais, que ainda não foram sincronizadas, serão perdidas!"
+ },
+ "prompt.Erase": {
+ "message": "Tem certeza de que deseja remover essa conta de um provedor desconhecido das listas de contas?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Tem certeza de que deseja cancelar a inscrição deste item? Todas as modificações locais, que ainda não foram sincronizadas, serão perdidas!"
+ },
+ "status.JavaScriptError": {
+ "message": "Erro de Javascript! Por favor, verifique o log de eventos para mais detalhes."
+ },
+ "status.OAuthAbortError": {
+ "message": "Processo de autenticação OAuth 2.0 abortado pelo usuário."
+ },
+ "status.OAuthHttpError": {
+ "message": "O processo de autenticação OAuth 2.0 falhou (HTTP error ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Não foi possível conectar ao servidor de autenticação OAuth 2.0."
+ },
+ "status.OAuthServerError": {
+ "message": " O servidor de autenticação OAuth 2.0 retornou: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Não sincronizado"
+ },
+ "status.apiError": {
+ "message": "Erro de implementação da API"
+ },
+ "status.disabled": {
+ "message": "A conta não está ativada, a sincronização está desativada."
+ },
+ "status.foldererror": {
+ "message": "Pelo menos um recurso encontrou um erro de sincronização. Por favor, verifique o log de eventos para mais detalhes."
+ },
+ "status.modified": {
+ "message": "Modificações locais"
+ },
+ "status.network": {
+ "message": "Não foi possível conectar-se ao servidor (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Não foi possível encontrar nenhum recurso no servidor."
+ },
+ "status.notargets": {
+ "message": "Anulando a sincronização, porque os destinos de sincronização não puderam ser criados."
+ },
+ "status.notsyncronized": {
+ "message": "Conta precisa ser sincronizada, pelo menos, um item não está sincronizado."
+ },
+ "status.pending": {
+ "message": "Aguardando para ser sincronizado"
+ },
+ "status.security": {
+ "message": "Não foi possível estabelecer uma conexão segura. Você está usando um certificado autoassinado ou não confiável sem importá-lo para o Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Ainda não suportado, ignorado"
+ },
+ "status.success": {
+ "message": "OK"
+ },
+ "status.syncing": {
+ "message": "Sincronizando"
+ },
+ "supportwizard.footer": {
+ "message": "A partir das informações fornecidas aqui, um e-mail será gerado na próxima etapa, que você pode editar posteriormente (por exemplo, adicionando capturas de tela ou similar). Apenas enviando esse e-mail, o relatório de erro será enviado."
+ },
+ "supportwizard.label.description": {
+ "message": "Descrição detalhada do erro:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Componente do TbSync onde você observou o erro:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Selecione o componente..."
+ },
+ "supportwizard.label.summary": {
+ "message": "Breve resumo do erro:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Coletar todas as informações para processar efetivamente o relatório de erros."
+ },
+ "supportwizard.provider": {
+ "message": "Provedor: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Criar relatório de bug"
+ },
+ "syncstate.accountdone": {
+ "message": "Conta finalizada"
+ },
+ "syncstate.done": {
+ "message": "Preparando o próximo item para sincronização"
+ },
+ "syncstate.oauthprompt": {
+ "message": "Autenticação OAuth 2.0"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Solicitação para inserir credenciais"
+ },
+ "syncstate.preparing": {
+ "message": "Preparando o próximo item para sincronização"
+ },
+ "syncstate.syncing": {
+ "message": "Inicializar sincronização"
+ },
+ "target.orphaned": {
+ "message": "Conexão desconectada"
+ },
+ "toolbar.label": {
+ "message": "Sincronizar todas as contas do TbSync"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Sincronizar as alterações mais recentes"
+ }
+} \ No newline at end of file
diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json
new file mode 100644
index 0000000..b3a928e
--- /dev/null
+++ b/_locales/ru/messages.json
@@ -0,0 +1,359 @@
+{
+ "HelpFixStartupError": {
+ "message": "Чтобы исправить эту ошибку, вы можете отправить журнал отладки разработчику TbSync. Подготовьте это письмо сейчас?"
+ },
+ "NoDebugLog": {
+ "message": "Не удалось найти пригодные отладочные сообщения. Включите режим отладки, перезапустите Thunderbird и повторите все шаги, необходимые для запуска ошибочного поведения."
+ },
+ "OopsMessage": {
+ "message": "Неудача! TbSync не смог запуститься!"
+ },
+ "RestartThunderbirdAndTryAgain": {
+ "message": "Журнал отладки TbSync включен, перезапустите Thunderbird и снова попытайтесь открыть TbSync."
+ },
+ "UnableToTraceError": {
+ "message": "Невозможно проследить эту ошибку, поскольку журнал отладки в данный момент не включен. Вы хотите включить журнал отладки сейчас, чтобы исправить эту ошибку?"
+ },
+ "accountacctions.delete": {
+ "message": "Удалить аккаунт “##accountname##”"
+ },
+ "accountacctions.disable": {
+ "message": "Запретить аккаунт “##accountname##”"
+ },
+ "accountacctions.enable": {
+ "message": "Разрешить аккаунт “##accountname##” и подключиться к серверу"
+ },
+ "accountacctions.sync": {
+ "message": "Синхронизировать аккаунт “##accountname##”"
+ },
+ "addressbook.searchall": {
+ "message": "Поиск по всем адресным книгам"
+ },
+ "addressbook.searchgal": {
+ "message": "Поиск в этой адресной книге и в глобальном каталоге (##replace.1##)"
+ },
+ "addressbook.searchthis": {
+ "message": "Поиск в этой адресной книге"
+ },
+ "eventlog.clear": {
+ "message": "Стереть"
+ },
+ "eventlog.close": {
+ "message": "Убрать окно"
+ },
+ "eventlog.title": {
+ "message": "журнал событий"
+ },
+ "extensionDescription": {
+ "message": "TbSync - это центральный пользовательский интерфейс для управления облачными учетными записями и синхронизации данных контактов, задач и календаря с Thunderbird."
+ },
+ "google.translate.code": {
+ "message": "ru"
+ },
+ "helplink.security": {
+ "message": "https://github.com/jobisoft/TbSync/wiki/How-to-use-TbSync-with-self-signed-or-otherwise-untrusted-certificates%3F"
+ },
+ "info.error": {
+ "message": "Ошибка"
+ },
+ "info.idle": {
+ "message": "Ожидание"
+ },
+ "installProvider.header": {
+ "message": "Провайдер “##replace.1##” для TbSync не установлен."
+ },
+ "manager.AccountActions": {
+ "message": "Действия для аккаунта"
+ },
+ "manager.AddAccount": {
+ "message": "Добавить новый аккаунт"
+ },
+ "manager.DeleteAccount": {
+ "message": "Удалить аккаунт"
+ },
+ "manager.DisableAccount": {
+ "message": "Запретить аккаунт"
+ },
+ "manager.EnableAccount": {
+ "message": "Разрешить аккаунт & подключить к серверу"
+ },
+ "manager.RetryConnectAccount": {
+ "message": "Повторить подключение к серверу"
+ },
+ "manager.ShowEventLog": {
+ "message": "Показать журнал событий"
+ },
+ "manager.SyncAll": {
+ "message": "Синхронизировать все разрешенные аккаунты"
+ },
+ "manager.SynchronizeAccount": {
+ "message": "Синхронизировать аккаунт"
+ },
+ "manager.accounts": {
+ "message": "Аккаунты"
+ },
+ "manager.accountsettings": {
+ "message": "Настройки аккаунта"
+ },
+ "manager.catman.text": {
+ "message": "TbSync также синхронизирует категории контактов, которые являются мощной заменой несинхронизируемых списков контактов. Чтобы иметь возможность использовать их в адресной книге Thunderbird, вы можете установить надстройку Менеджера Категорий. Он позволяет управлять перекрывающимися контактными группами на основе категорий и предоставляет много других функций, связанных с категорией. Его можно найти в официальном репозитории Mozilla Add-On:"
+ },
+ "manager.community": {
+ "message": "Сообщество"
+ },
+ "manager.connecting": {
+ "message": "Подсоединиться к серверу"
+ },
+ "manager.help": {
+ "message": "помощь"
+ },
+ "manager.help.createbugreport": {
+ "message": "Создать отчет об ошибке"
+ },
+ "manager.help.createbugreportinfo": {
+ "message": "Чтобы TbSync собирал только отладочные сообщения, относящиеся к вашему отчету об ошибке, перезапустите Thunderbird, а затем повторите шаги, которые воспроизводят поведение ошибки. Затем вы можете создать свой отчет об ошибках здесь."
+ },
+ "manager.help.debuglevel.0": {
+ "message": "Disabled"
+ },
+ "manager.help.debuglevel.1": {
+ "message": "Enabled: Logging of errors only"
+ },
+ "manager.help.debuglevel.2": {
+ "message": "Enabled: Logging of all sent and received data"
+ },
+ "manager.help.debuglevel.3": {
+ "message": "Enabled: Logging of all data and some internal debug values"
+ },
+ "manager.help.debugmode": {
+ "message": "Debug mode:"
+ },
+ "manager.help.fixit": {
+ "message": "Вы можете помочь исправить это дополнение, отправив сообщение об ошибке. Для этого требуется активация режима отладки."
+ },
+ "manager.help.foundabug": {
+ "message": "Нашли ошибку?"
+ },
+ "manager.help.needhelp": {
+ "message": "Нужна справка?"
+ },
+ "manager.help.viewdebuglog": {
+ "message": "View debug log"
+ },
+ "manager.help.wiki": {
+ "message": "Откройте wiki-страницы проекта TbSync, они предоставят дополнительную информацию, руководства и подробные описания конфигурации."
+ },
+ "manager.installprovider.link": {
+ "message": "Нажмите на следующую ссылку, чтобы открыть информационную страницу отсутствующего провайдера синхронизации. Там вы найдете дополнительную информацию о провайдере, и у вас будет возможность установить его:"
+ },
+ "manager.installprovider.warning": {
+ "message": "Этот провайдер синхронизации не размещен в официальном репозитории Thunderbird AddOn и, таким образом, не был рассмотрен сотрудниками Thunderbird. Провайдер синхронизации может сделать деструктивные действия в вашей системе. Используйте его на свой страх и риск."
+ },
+ "manager.lockedsettings.description": {
+ "message": "Чтобы предотвратить ошибки синхронизации, некоторые параметры нельзя редактировать, пока учетная запись включена."
+ },
+ "manager.missingprovider": {
+ "message": "Провайдер синхронизации (## provider ##), с которым связана эта учетная запись, не установлен."
+ },
+ "manager.noaccounts": {
+ "message": "Определенных аккаунтов пока нет."
+ },
+ "manager.provider": {
+ "message": "Установить провайдера"
+ },
+ "manager.provider4tbsync": {
+ "message": "Провайдер для TbSync"
+ },
+ "manager.resource": {
+ "message": "Папка"
+ },
+ "manager.shorttitle": {
+ "message": "Менеджер аккаунта"
+ },
+ "manager.status": {
+ "message": "Статус"
+ },
+ "manager.supporter.contributors": {
+ "message": "Помощники & Переводчики"
+ },
+ "manager.supporter.details": {
+ "message": "Подробности"
+ },
+ "manager.supporter.sponsors": {
+ "message": "Спонсоры тестовых аккаунтов"
+ },
+ "manager.tabs.status": {
+ "message": "Статус синхронизации"
+ },
+ "manager.tabs.status.autotime": {
+ "message": "Периодическая синхронизация (в минутах)"
+ },
+ "manager.tabs.status.enableThisAccount": {
+ "message": "Разрешить и синхронизировать этот аккаунт"
+ },
+ "manager.tabs.status.general": {
+ "message": "Главный"
+ },
+ "manager.tabs.status.never": {
+ "message": "Установка 0 отключает периодическую синхронизацию. Push-синхронизации еще не реализована."
+ },
+ "manager.tabs.status.resources": {
+ "message": "Доступные ресурсы"
+ },
+ "manager.tabs.status.resources.intro": {
+ "message": "Выберите, какой из найденных ресурсов следует синхронизировать с Thunderbird."
+ },
+ "manager.tabs.status.sync": {
+ "message": "Синхронизировать сейчас"
+ },
+ "manager.tabs.status.tryagain": {
+ "message": "Повторить подсоединение к серверу"
+ },
+ "manager.title": {
+ "message": "Менеджер аккаунта TbSync"
+ },
+ "manager.tryagain": {
+ "message": "Повторить подсоединение к серверу"
+ },
+ "menu.settingslabel": {
+ "message": "Настройки синхронизации (TbSync)"
+ },
+ "password.account": {
+ "message": "учетная запись:"
+ },
+ "password.description": {
+ "message": "Обновите учетные данные для следующей учетной записи TbSync:"
+ },
+ "password.password": {
+ "message": "пароль:"
+ },
+ "password.title": {
+ "message": "Запрос учетных данных TbSync"
+ },
+ "password.user": {
+ "message": "пользователь:"
+ },
+ "popup.opensettings": {
+ "message": "Открыть менеджер аккаунта TbSync"
+ },
+ "prompt.DeleteAccount": {
+ "message": "Вы уверены, что хотите удалить аккаунт ##accountName##?"
+ },
+ "prompt.Disable": {
+ "message": "Вы действительно хотите запретить этот аккаунт? Все локальные изменения, которые еще не синхронизированы, будут потеряны!"
+ },
+ "prompt.Erase": {
+ "message": "Вы действительно хотите удалить этот аккаунт неизвестного провайдера из списка аккаунтов?"
+ },
+ "prompt.Unsubscribe": {
+ "message": "Вы действительно хотите отменить подписку на этот элемент? Все локальные изменения, которые еще не синхронизированы, будут потеряны!"
+ },
+ "status.JavaScriptError": {
+ "message": "Ошибка JavaScript! Пожалуйста, проверьте журнал событий для более подробной информации."
+ },
+ "status.OAuthAbortError": {
+ "message": "OAuth 2.0 authentication process aborted by user."
+ },
+ "status.OAuthHttpError": {
+ "message": "OAuth 2.0 authentication process failed (HTTP error ##replace.1##)."
+ },
+ "status.OAuthNetworkError": {
+ "message": "Could not connect to OAuth 2.0 authentication server."
+ },
+ "status.OAuthServerError": {
+ "message": " OAuth 2.0 authentication server returned: ##replace.1##"
+ },
+ "status.aborted": {
+ "message": "Не синхронизировано"
+ },
+ "status.apiError": {
+ "message": "Ошибка реализации API"
+ },
+ "status.disabled": {
+ "message": "Аккаунт не включен, синхронизация отключена."
+ },
+ "status.foldererror": {
+ "message": "По крайней мере один ресурс обнаружил ошибку синхронизации. Пожалуйста, проверьте журнал событий для более подробной информации."
+ },
+ "status.modified": {
+ "message": "Локальные изменения"
+ },
+ "status.network": {
+ "message": "Не удалось подключиться к серверу (##replace.1##)."
+ },
+ "status.no-folders-found-on-server": {
+ "message": "Не удалось найти какие-либо ресурсы на сервере."
+ },
+ "status.notargets": {
+ "message": "Отмена синхронизации, поскольку цели синхронизации не могут быть созданы."
+ },
+ "status.notsyncronized": {
+ "message": "Аккаунт должен быть синхронизирован, по крайней мере один элемент не синхронизирован."
+ },
+ "status.pending": {
+ "message": "Ожидание пока синхронизируется"
+ },
+ "status.security": {
+ "message": "Не удалось установить безопасное соединение. Вы используете самоподписанный или ненадежный сертификат без импорта его в Thunderbird? (##replace.1##)"
+ },
+ "status.skipped": {
+ "message": "Пока не поддерживается, пропущено"
+ },
+ "status.success": {
+ "message": "Готово"
+ },
+ "status.syncing": {
+ "message": "Синхронизация"
+ },
+ "supportwizard.footer": {
+ "message": "Из приведенной здесь информации на следующем шаге будет создано электронное письмо, которое вы можете впоследствии редактировать (например, добавлять скриншоты или аналогичное). Только при отправке этого электронного письма будет отправлен и сам отчет об ошибке."
+ },
+ "supportwizard.label.description": {
+ "message": "Подробное описание ошибки:"
+ },
+ "supportwizard.label.faultycomponent": {
+ "message": "Компонент TbSync, где вы заметили ошибку:"
+ },
+ "supportwizard.label.selectcomponent": {
+ "message": "Выбрать компонент ..."
+ },
+ "supportwizard.label.summary": {
+ "message": "Краткое описание ошибки:"
+ },
+ "supportwizard.pagetitle": {
+ "message": "Сбор всей информации для эффективной обработки отчета об ошибке."
+ },
+ "supportwizard.provider": {
+ "message": "Провайдер: ##replace.1##"
+ },
+ "supportwizard.title": {
+ "message": "Создать отчет об ошибке"
+ },
+ "syncstate.accountdone": {
+ "message": "Завершенный аккаунт"
+ },
+ "syncstate.done": {
+ "message": "Подготовка следующего элемента для синхронизации"
+ },
+ "syncstate.oauthprompt": {
+ "message": "OAuth 2.0 authentication"
+ },
+ "syncstate.passwordprompt": {
+ "message": "Запрос на ввод учетных данных"
+ },
+ "syncstate.preparing": {
+ "message": "Подготовка следующего элемента для синхронизации"
+ },
+ "syncstate.syncing": {
+ "message": "Инициализация синхронизации"
+ },
+ "target.orphaned": {
+ "message": "Соединение разорвано"
+ },
+ "toolbar.label": {
+ "message": "Синхронизировать все аккаунты TbSync"
+ },
+ "toolbar.tooltiptext": {
+ "message": "Синхронизировать последние изменения"
+ }
+} \ No newline at end of file
diff --git a/background.js b/background.js
new file mode 100644
index 0000000..f47499d
--- /dev/null
+++ b/background.js
@@ -0,0 +1,14 @@
+function handleUpdateAvailable(details) {
+ console.log("Update available for TbSync");
+}
+
+async function main() {
+ // just by registering this listener, updates will not install until next restart
+ //messenger.runtime.onUpdateAvailable.addListener(handleUpdateAvailable);
+
+ await messenger.BootstrapLoader.registerChromeUrl([ ["content", "tbsync", "content/"] ]);
+ await messenger.BootstrapLoader.registerOptionsPage("chrome://tbsync/content/manager/addonoptions.xhtml");
+ await messenger.BootstrapLoader.registerBootstrapScript("chrome://tbsync/content/scripts/bootstrap.js");
+}
+
+main();
diff --git a/beta-release-channel-update.json b/beta-release-channel-update.json
new file mode 100644
index 0000000..76de8bc
--- /dev/null
+++ b/beta-release-channel-update.json
@@ -0,0 +1,13 @@
+{
+ "addons": {
+ "tbsync@jobisoft.de": {
+ "updates": [
+ { "version": "%VERSION%",
+ "update_info_url": "https://github.com/jobisoft/TbSync/releases",
+ "update_link": "%LINK%",
+ "applications": {
+ "gecko": { "strict_min_version": "78.0" } } }
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/content/HttpRequest.jsm b/content/HttpRequest.jsm
new file mode 100644
index 0000000..fa28f97
--- /dev/null
+++ b/content/HttpRequest.jsm
@@ -0,0 +1,755 @@
+/*
+ * This file is part of TbSync, contributed by John Bieling.
+ *
+ * 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/.
+ *
+ * Limitations:
+ * ============
+ * - no real event support (cannot add eventlisteners)
+ * - send only supports string body
+ * - onprogress not supported
+ * - readyState 2 & 3 not supported
+ *
+ * Note about HttpRequest.open(method, url, async, username, password):
+ * ============================================================================
+ * If an Authorization header is specified, HttpRequest will use the
+ * given header.
+ *
+ * If no Authorization header is specified, but a username, HttpRequest
+ * will delegate the authentication process to nsIHttpChannel. If a password is
+ * specified as well, it will be used for authentication. If no password is
+ * specified, it will call the passwordCallback(username, realm, host) callback to
+ * request a password for the given username, host and realm send back from
+ * the server (in the WWW-Authenticate header).
+ *
+ */
+
+ "use strict";
+
+var EXPORTED_SYMBOLS = ["HttpRequest"];
+
+var bug669675 = [];
+var containers = [];
+var sandboxes = {};
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var HttpRequest = class {
+ constructor() {
+ // a private object to store xhr related properties
+ this._xhr = {};
+
+ // HttpRequest supports two methods to receive data, using the
+ // streamLoader seems to be the more modern approach.
+ // BUT in order to overide MimeType, we need to call onStartRequest
+ this._xhr.useStreamLoader = false;
+ this._xhr.responseAsBase64 = false;
+
+ this._xhr.loadFlags = Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
+ this._xhr.headers = {};
+ this._xhr.readyState = 0;
+ this._xhr.responseStatus = null;
+ this._xhr.responseStatusText = null;
+ this._xhr.responseText = null;
+ this._xhr.httpchannel = null;
+ this._xhr.method = null;
+ this._xhr.uri = null;
+ this._xhr.permanentlyRedirectedUrl = null;
+ this._xhr.username = "";
+ this._xhr.password = "";
+ this._xhr.overrideMimeType = null;
+ this._xhr.mozAnon = false;
+ this._xhr.mozBackgroundRequest = false;
+ this._xhr.timeout = 0;
+ this._xhr.redirectFlags = null;
+ this._xhr.containerReset = false;
+ this._xhr.containerRealm = "default";
+
+ this.onreadystatechange = function () {};
+ this.onerror = function () {};
+ this.onload = function () {};
+ this.ontimeout = function () {};
+
+ // Redirects are handled internally, this callback is just called to
+ // inform the caller about the redirect.
+ // Flags: (https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIChannelEventSink)
+ // - REDIRECT_TEMPORARY = 1 << 0;
+ // - REDIRECT_PERMANENT = 1 << 1;
+ // - REDIRECT_INTERNAL = 1 << 2;
+ // TB enforces a redirect from http -> https without having received a redirect,
+ // but an STS header
+ // - REDIRECT_STS_UPGRADE = 1 << 3;
+ // This is a custom bit set by HttpRequest to indicate, that this redirect was missed by the nsIHttpChannel implementation
+ // probably due to a CORS violation (like https -> http)
+ // - REDIRECT_MISSED = 1 << 7;
+ this.onredirect = function(flags, newUri) {};
+
+ // Whenever a WWW-Authenticate header has been parsed, this callback is
+ // called to inform the caller about the found realm.
+ this.realmCallback = function (username, realm, host) {};
+
+ // Whenever a channel needs authentication, but the caller has only provided a username
+ // this callback is called to request the password.
+ this.passwordCallback = function (username, realm, host) {return null};
+
+ var self = this;
+
+ this.notificationCallbacks = {
+ // nsIInterfaceRequestor
+ getInterface : function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
+ // implement a custom nsIAuthPrompt2 - needed for auto authorization
+ if (!self._xhr.authPrompt) {
+ self._xhr.authPrompt = new HttpRequestPrompt(self._xhr.username, self._xhr.password, self.passwordCallback, self.realmCallback);
+ }
+ return self._xhr.authPrompt;
+ } else if (aIID.equals(Components.interfaces.nsIAuthPrompt)) {
+ // implement a custom nsIAuthPrompt
+ } else if (aIID.equals(Components.interfaces.nsIAuthPromptProvider)) {
+ // implement a custom nsIAuthPromptProvider
+ } else if (aIID.equals(Components.interfaces.nsIPrompt)) {
+ // implement a custom nsIPrompt
+ } else if (aIID.equals(Components.interfaces.nsIProgressEventSink)) {
+ // implement a custom nsIProgressEventSink
+ } else if (aIID.equals(Components.interfaces.nsIChannelEventSink)) {
+ // implement a custom nsIChannelEventSink
+ return self.redirect;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ };
+
+ this.redirect = {
+ // nsIChannelEventSink implementation
+ asyncOnChannelRedirect: function(aOldChannel, aNewChannel, aFlags, aCallback) {
+ // Disallow redirects from https to http.
+ if (aOldChannel.URI.scheme == "https" && aNewChannel.URI.scheme == "http") {
+ // Using an unused error code according to https://developer.mozilla.org/en-US/docs/Mozilla/Errors.
+ // REJECTED_REDIRECT_FROM_HTTPS_TO_HTTP'
+ aCallback.onRedirectVerifyCallback(0x804B002F);
+ return;
+ }
+
+ let uploadData;
+ let uploadContent;
+ if (aOldChannel instanceof Ci.nsIUploadChannel &&
+ aOldChannel instanceof Ci.nsIHttpChannel &&
+ aOldChannel.uploadStream) {
+ uploadData = aOldChannel.uploadStream;
+ uploadContent = aOldChannel.getRequestHeader("Content-Type");
+ }
+
+ aNewChannel.QueryInterface(Ci.nsIHttpChannel);
+ aOldChannel.QueryInterface(Ci.nsIHttpChannel);
+
+ function copyHeader(aHdr) {
+ try {
+ let hdrValue = aOldChannel.getRequestHeader(aHdr);
+ if (hdrValue) {
+ aNewChannel.setRequestHeader(aHdr, hdrValue, false);
+ }
+ } catch (e) {
+ if (e.code != Components.results.NS_ERROR_NOT_AVAILIBLE) {
+ // The header could possibly not be available, ignore that
+ // case but throw otherwise
+ throw e;
+ }
+ }
+ }
+
+ // Copy manually added headers
+ for (let header in self._xhr.headers) {
+ if (self._xhr.headers.hasOwnProperty(header)) {
+ copyHeader(header);
+ }
+ }
+
+ prepHttpChannelUploadData(
+ aNewChannel,
+ aOldChannel.requestMethod,
+ uploadData,
+ uploadContent);
+
+ self._xhr.redirectFlags = aFlags;
+ if (aFlags & Ci.nsIChannelEventSink.REDIRECT_PERMANENT) {
+ self._xhr.permanentlyRedirectedUrl = aNewChannel.URI.spec;
+ }
+ self.onredirect(aFlags, aNewChannel.URI);
+ aCallback.onRedirectVerifyCallback(Components.results.NS_OK);
+ }
+ };
+
+ this.listener = {
+ _buffer: [],
+
+ //nsIStreamListener (aUseStreamLoader = false)
+ onStartRequest: function(aRequest) {
+ //Services.console.logStringMessage("[onStartRequest] " + aRequest.URI.spec);
+ this._buffer = [];
+
+ if (self._xhr.overrideMimeType) {
+ aRequest.contentType = self._xhr.overrideMimeType;
+ }
+ },
+ onDataAvailable: function (aRequest, aInputStream, aOffset, aCount) {
+ //Services.console.logStringMessage("[onDataAvailable] " + aRequest.URI.spec + " : " + aCount);
+ let buffer = new ArrayBuffer(aCount);
+ let stream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
+ stream.setInputStream(aInputStream);
+ stream.readArrayBuffer(aCount, buffer);
+
+ // store the chunk
+ this._buffer.push(Array.from(new Uint8Array(buffer)));
+ },
+ onStopRequest: function(aRequest, aStatusCode) {
+ //Services.console.logStringMessage("[onStopRequest] " + aRequest.URI.spec + " : " + aStatusCode);
+ // combine all binary chunks to create a flat byte array;
+ let combined = [].concat.apply([], this._buffer);
+ let data = convertByteArray(combined, self.responseAsBase64);
+ this.processResponse(aRequest.QueryInterface(Components.interfaces.nsIHttpChannel), aStatusCode, data);
+ },
+
+
+
+ //nsIStreamLoaderObserver (aUseStreamLoader = true)
+ onStreamComplete: function(aLoader, aContext, aStatus, aResultLength, aResult) {
+ let result = convertByteArray(aResult, self.responseAsBase64);
+ this.processResponse(aLoader.request.QueryInterface(Components.interfaces.nsIHttpChannel), aStatus, result);
+ },
+
+ processResponse: function(aChannel, aStatus, aResult) {
+ //Services.console.logStringMessage("[processResponse] " + aChannel.URI.spec + " : " + aStatus);
+ // do not set any channal response data, before we know we failed
+ // and before we know we do not have to rerun (due to bug 669675)
+
+ let responseStatus = null;
+ try {
+ responseStatus = aChannel.responseStatus;
+ } catch (ex) {
+ switch (aStatus) {
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ self._xhr.httpchannel = aChannel;
+ self._xhr.responseText = aResult;
+ self._xhr.responseStatus = 0;
+ self._xhr.responseStatusText = "";
+ self._xhr.readyState = 4;
+ self.onreadystatechange();
+ self.ontimeout();
+ return;
+ case Components.results.NS_BINDING_ABORTED:
+ case 0x804B002F: //Custom error (REJECTED_REDIRECT_FROM_HTTPS_TO_HTTP')
+ self._xhr.httpchannel = aChannel;
+ self._xhr.responseText = aResult;
+ self._xhr.responseStatus = 0;
+ self._xhr.responseStatusText = "";
+ self._xhr.readyState = 0;
+ self.onreadystatechange();
+ self.onerror();
+ return;
+ case 0x805303F4: //NS_ERROR_DOM_BAD_URI
+ // Error on Strict-Transport-Security induced Redirect http -> https (these do not even show up in the console)
+ // The redirect has been done already and the channel is already the new channel.
+ // Due to CORS violation, it cannot be fulfilled.
+ if (self._xhr.redirectFlags && aChannel.URI.spec != self._xhr.uri.spec) {
+ self._xhr.uri = aChannel.URI;
+ self.send(self._xhr.data);
+ return;
+ }
+ default:
+ self._xhr.httpchannel = aChannel;
+ self._xhr.responseText = aResult;
+ self._xhr.responseStatus = 0;
+ self._xhr.responseStatusText = "";
+ self._xhr.readyState = 4;
+ self.onreadystatechange();
+ self.onerror();
+ return;
+ }
+ }
+
+ // Usually redirects are handled internally, but any CORS violating request is
+ // returning a 30x, if CORS is not allowed. This is not true for STS induced redirects (see above).
+ if ([301,302,307,308].includes(responseStatus)) {
+ // aChannel is still the old channel
+ let redirected = self.getResponseHeader("location");
+ if (redirected && redirected != aChannel.URI.spec) {
+ let flag = Ci.nsIChannelEventSink.REDIRECT_TEMPORARY;
+ let uri = Services.io.newURI(redirected);
+ if ([301,308].includes(responseStatus)) {
+ flag = Ci.nsIChannelEventSink.REDIRECT_PERMANENT;
+ self._xhr.permanentlyRedirectedUrl = uri.spec;
+ }
+ // inform caller about the redirect and set the REDIRECT_MISSED bit
+
+ self.onredirect(flag | 0x80, uri);
+ self._xhr.uri = uri;
+ self.send(self._xhr.data);
+ return;
+ }
+ }
+
+ // mitigation for bug https://bugzilla.mozilla.org/show_bug.cgi?id=669675
+ // we need to check, if nsIHttpChannel was in charge of auth:
+ // if there was no Authentication header provided by the user, but a username
+ // nsIHttpChannel should have added one. Is there one?
+ if (
+ (responseStatus == 401) &&
+ !self._xhr.mozAnon &&
+ !self.hasRequestHeader("Authorization") && // no user defined header, so nsIHttpChannel should have called the authPrompt
+ self._xhr.username && // we can only add basic auth header if user
+ self._xhr.password // and pass are present
+ ) {
+ // check the actual Authorization headers send
+ let unauthenticated;
+ try {
+ let header = aChannel.getRequestHeader("Authorization");
+ unauthenticated = false;
+ } catch (e) {
+ unauthenticated = true;
+ }
+
+ if (unauthenticated) {
+ if (!bug669675.includes(self._xhr.uri.spec)) {
+ bug669675.push(self._xhr.uri.spec)
+ console.log("Mitigation for bug 669675 for URL <"+self._xhr.uri.spec+"> (Once per URL per session)");
+ // rerun
+ self.send(self._xhr.data);
+ return;
+ } else {
+ console.log("Mitigation failed for URL <"+self._xhr.uri.spec+">");
+ }
+ }
+ }
+
+ self._xhr.httpchannel = aChannel;
+ self._xhr.responseText = aResult;
+ self._xhr.responseStatus = responseStatus;
+ self._xhr.responseStatusText = aChannel.responseStatusText;
+ self._xhr.readyState = 4;
+ self.onreadystatechange();
+ self.onload();
+ }
+ };
+ }
+
+
+
+
+ /** public **/
+
+ open(method, url, async = true, username = "", password = "") {
+ this._xhr.method = method;
+
+ try {
+ this._xhr.uri = Services.io.newURI(url);
+ } catch (e) {
+ Components.utils.reportError(e);
+ throw new Error("HttpRequest: Invalid URL <"+url+">");
+ }
+ if (!async) throw new Error ("HttpRequest: Synchronous requests not implemented.");
+
+ this._xhr.username = username;
+ this._xhr.password = password;
+
+ this._xhr.readyState = 1;
+ this.onreadystatechange();
+
+ }
+
+ // must be called after open, before send
+ setContainerRealm(v) {
+ this._xhr.containerRealm = v;
+ }
+
+ // must be called after open, before send
+ clearContainerCache() {
+ this._xhr.containerReset = true;
+ }
+
+ send(data) {
+ //store the data, so we can rerun
+ this._xhr.data = data;
+ this._xhr.redirectFlags = null;
+
+ // The sandbox will have a loadingNode
+ let sandbox = getSandboxForOrigin(this._xhr.username, this._xhr.uri, this._xhr.containerRealm, this._xhr.containerReset);
+
+ // The XHR in the sandbox will have the correct loadInfo, which will allow us
+ // to use cookies and a CodebasePrincipal for us to use userContextIds and to
+ // contact nextcloud servers (error 503).
+ // We will not use the XHR or the sandbox itself.
+ let XHR = new sandbox.XMLHttpRequest();
+ XHR.open(this._xhr.method, this._xhr.uri.spec);
+
+ // Create the channel with the loadInfo from the sandboxed XHR
+ let channel = Services.io.newChannelFromURIWithLoadInfo(this._xhr.uri, XHR.channel.loadInfo);
+
+ /*
+ // as of TB67 newChannelFromURI needs to specify a loading node to have access to the cookie jars
+ // using the main window
+ // another option would be workers
+ let options = {};
+ let mainWindow = Services.wm.getMostRecentWindow("mail:3pane");
+
+ let channel = Services.io.newChannelFromURI(
+ this._xhr.uri,
+ mainWindow.document,
+ Services.scriptSecurityManager.createContentPrincipal(this._xhr.uri, options),
+ null,
+ Components.interfaces.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Components.interfaces.nsIContentPolicy.TYPE_OTHER);
+ */
+
+/*
+ // enforce anonymous access if requested (will not work with proxy, see MDN)
+ if (this._xhr.mozAnon) {
+ channel.loadFlag |= Components.interfaces.nsIRequest.LOAD_ANONYMOUS;
+ }
+
+ // set background request
+ if (this._xhr.mozBackgroundRequest) {
+ channel.loadFlag |= Components.interfaces.nsIRequest.LOAD_BACKGROUND;
+ }
+*/
+ this._xhr.httpchannel = channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+ this._xhr.httpchannel.loadFlags |= this._xhr.loadFlags;
+ this._xhr.httpchannel.notificationCallbacks = this.notificationCallbacks;
+
+ // Set default content type.
+ if (!this.hasRequestHeader("Content-Type")) {
+ this.setRequestHeader("Content-Type", "application/xml; charset=utf-8")
+ }
+
+ // Set default accept value.
+ if (!this.hasRequestHeader("Accept")) {
+ this.setRequestHeader("Accept", "*/*");
+ }
+
+ // Set non-standard header to request authorization (https://github.com/jobisoft/DAV-4-TbSync/issues/106)
+ if (this._xhr.username) {
+ this.setRequestHeader("X-EnforceAuthentication", "True");
+ }
+
+ // calculate length of request and add header
+ if (data) {
+ let textEncoder = new TextEncoder();
+ let encoded = textEncoder.encode(data);
+ this.setRequestHeader("Content-Length", encoded.length);
+ }
+
+ // mitigation for bug 669675
+ if (
+ bug669675.includes(this._xhr.uri.spec) &&
+ !this._xhr.mozAnon &&
+ !this.hasRequestHeader("Authorization") &&
+ this._xhr.username &&
+ this._xhr.password
+ ) {
+ this.setRequestHeader("Authorization", "Basic " + b64EncodeUnicode(this._xhr.username + ':' + this._xhr.password));
+ }
+
+ // add all headers to the channel
+ for (let header in this._xhr.headers) {
+ if (this._xhr.headers.hasOwnProperty(header)) {
+ this._xhr.httpchannel.setRequestHeader(header, this._xhr.headers[header], false);
+ }
+ }
+
+ // Will overwrite the Content-Type, so it must be called after the headers have been set.
+ prepHttpChannelUploadData(this._xhr.httpchannel, this._xhr.method, data, this.getRequestHeader("Content-Type"));
+
+ if (this._xhr.useStreamLoader) {
+ let loader = Components.classes["@mozilla.org/network/stream-loader;1"].createInstance(Components.interfaces.nsIStreamLoader);
+ loader.init(this.listener);
+ this.listener = loader;
+ }
+
+ this._startTimeout();
+ this._xhr.httpchannel.asyncOpen(this.listener, this._xhr.httpchannel);
+ }
+
+ get readyState() { return this._xhr.readyState; }
+ get responseURI() { return this._xhr.httpchannel.URI; }
+ get responseURL() { return this._xhr.httpchannel.URI.spec; }
+ get permanentlyRedirectedUrl() { return this._xhr.permanentlyRedirectedUrl; }
+ get responseText() { return this._xhr.responseText; }
+ get status() { return this._xhr.responseStatus; }
+ get statusText() { return this._xhr.responseStatusText; }
+ get channel() { return this._xhr.httpchannel; }
+ get loadFlags() { return this._xhr.loadFlags; }
+ get timeout() { return this._xhr.timeout; }
+ get mozBackgroundRequest() { return this._xhr.mozBackgroundRequest; }
+ get mozAnon() { return this._xhr.mozAnon; }
+
+ set loadFlags(v) { this._xhr.loadFlags = v; }
+ set timeout(v) { this._xhr.timeout = v; }
+ set mozBackgroundRequest(v) { this._xhr.mozBackgroundRequest = (v === true); }
+ set mozAnon(v) { this._xhr.mozAnon = (v === true); }
+
+
+ // case insensitive method to check for headers
+ hasRequestHeader(header) {
+ let lowHeaders = Object.keys(this._xhr.headers).map(x => x.toLowerCase());
+ return lowHeaders.includes(header.toLowerCase());
+ }
+
+ // if a header exists (case insensitive), it will be replaced (keeping the original capitalization)
+ setRequestHeader(header, value) {
+ let useHeader = header;
+ let lowHeader = header.toLowerCase();
+
+ for (let h in this._xhr.headers) {
+ if (this._xhr.headers.hasOwnProperty(h) && h.toLowerCase() == lowHeader) {
+ useHeader = h;
+ break;
+ }
+ }
+ this._xhr.headers[useHeader] = value;
+ }
+
+ // checks if a header (case insensitive) has been set by setRequestHeader - that does not mean it has been added to the channel!
+ getRequestHeader(header) {
+ let lowHeader = header.toLowerCase();
+
+ for (let h in this._xhr.headers) {
+ if (this._xhr.headers.hasOwnProperty(h) && h.toLowerCase() == lowHeader) {
+ return this._xhr.headers[h];
+ }
+ }
+ return null;
+ }
+
+ getResponseHeader(header) {
+ try {
+ return this._xhr.httpchannel.getResponseHeader(header);
+ } catch (e) {
+ if (e.code != Components.results.NS_ERROR_NOT_AVAILIBLE) {
+ // The header could possibly not be available, ignore that
+ // case but throw otherwise
+ throw e;
+ }
+ }
+ return null;
+ }
+
+ overrideMimeType(mime) {
+ this._xhr.overrideMimeType = mime;
+ }
+
+ abort() {
+ this._cancel(Components.results.NS_BINDING_ABORTED);
+ }
+
+ get responseAsBase64() { return this._xhr.responseAsBase64; }
+ set responseAsBase64(v) { this._xhr.responseAsBase64 = (v == true);}
+
+ /* not used */
+
+ get responseXML() { throw new Error("HttpRequest: responseXML not implemented"); }
+
+ get response() { throw new Error("HttpRequest: response not implemented"); }
+ set response(v) { throw new Error("HttpRequest: response not implemented"); }
+
+ get responseType() { throw new Error("HttpRequest: response not implemented"); }
+ set responseType(v) { throw new Error("HttpRequest: response not implemented"); }
+
+ get upload() { throw new Error("HttpRequest: upload not implemented"); }
+ set upload(v) { throw new Error("HttpRequest: upload not implemented"); }
+
+ get withCredentials() { throw new Error("HttpRequest: withCredentials not implemented"); }
+ set withCredentials(v) { throw new Error("HttpRequest: withCredentials not implemented"); }
+
+
+
+
+
+
+ /** private helper methods **/
+
+ _startTimeout() {
+ let that = this;
+
+ this._xhr.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+ let event = {
+ notify: function(timer) {
+ that._cancel(Components.results.NS_ERROR_NET_TIMEOUT)
+ }
+ }
+ this._xhr.timer.initWithCallback(
+ event,
+ this._xhr.timeout,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ _cancel(error) {
+ if (this._xhr.httpchannel && error) {
+ this._xhr.httpchannel.cancel(error);
+ }
+ }
+}
+
+
+
+
+
+var HttpRequestPrompt = class {
+ constructor(username, password, promptCallback, realmCallback) {
+ this.mCounts = 0;
+ this.mUsername = username;
+ this.mPassword = password;
+ this.mPromptCallback = promptCallback;
+ this.mRealmCallback = realmCallback;
+ }
+
+ // boolean promptAuth(in nsIChannel aChannel,
+ // in uint32_t level,
+ // in nsIAuthInformation authInfo)
+ promptAuth (aChannel, aLevel, aAuthInfo) {
+ this.mRealmCallback(this.mUsername, aAuthInfo.realm, aChannel.URI.host);
+ if (this.mUsername && this.mPassword) {
+ console.log("Passing provided credentials for user <"+this.mUsername+"> to nsIHttpChannel.");
+ aAuthInfo.username = this.mUsername;
+ aAuthInfo.password = this.mPassword;
+ } else if (this.mUsername) {
+ console.log("Using passwordCallback callback to get password for user <"+this.mUsername+"> and realm <"+aAuthInfo.realm+"> @ host <"+aChannel.URI.host+">");
+ let password = this.mPromptCallback(this.mUsername, aAuthInfo.realm, aChannel.URI.host);
+ if (password) {
+ aAuthInfo.username = this.mUsername;
+ aAuthInfo.password = password;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // The provided password could be wrong, in whichcase
+ // we would be here more than once.
+ this.mCounts++
+ return (this.mCounts < 2);
+ }
+}
+
+
+
+
+function getSandboxForOrigin(username, uri, containerRealm = "default", containerReset = false) {
+ let options = {};
+ let origin = uri.scheme + "://" + uri.hostPort;
+
+ // Note:
+ // Disabled check for username to always use containers, even if no username is given.
+ // There was an issue with NC (in its container) and google being in the default container,
+ // causing NC to fail with 503. Putting google inside a container as well fixed it.
+ options.userContextId = getContainerIdForUser(containerRealm + "::" + username);
+ origin = options.userContextId + "@" + origin;
+ if (containerReset) {
+ resetContainerWithId(options.userContextId);
+ }
+
+ if (!sandboxes.hasOwnProperty(origin)) {
+ console.log("Creating sandbox for <"+origin+">");
+ let principal = Services.scriptSecurityManager.createContentPrincipal(uri, options);
+ sandboxes[origin] = Components.utils.Sandbox(principal, {
+ wantXrays: true,
+ wantGlobalProperties: ["XMLHttpRequest"],
+ });
+ }
+
+ return sandboxes[origin];
+}
+
+function resetContainerWithId(id) {
+ Services.clearData.deleteDataFromOriginAttributesPattern({ userContextId: id });
+}
+
+function getContainerIdForUser(username) {
+ // Define the allowed range of container ids to be used
+ // TbSync is using 10000 - 19999
+ // Lightning is using 20000 - 29999
+ // Cardbook is using 30000 - 39999
+ let min = 10000;
+ let max = 19999;
+
+ //reset if adding an entry will exceed allowed range
+ if (containers.length > (max-min) && containers.indexOf(username) == -1) {
+ for (let i=0; i < containers.length; i++) {
+ resetContainerWithId(i + min);
+ }
+ containers = [];
+ }
+
+ let idx = containers.indexOf(username);
+ return (idx == -1) ? containers.push(username) - 1 + min : (idx + min);
+}
+
+// copied from cardbook
+function b64EncodeUnicode (aString) {
+ return btoa(encodeURIComponent(aString).replace(/%([0-9A-F]{2})/g, function(match, p1) {
+ return String.fromCharCode('0x' + p1);
+ }));
+}
+
+// copied from lightning
+function prepHttpChannelUploadData(aHttpChannel, aMethod, aUploadData, aContentType) {
+ if (aUploadData) {
+ aHttpChannel.QueryInterface(Components.interfaces.nsIUploadChannel);
+ let stream;
+ if (aUploadData instanceof Components.interfaces.nsIInputStream) {
+ // Make sure the stream is reset
+ stream = aUploadData.QueryInterface(Components.interfaces.nsISeekableStream);
+ stream.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
+ } else {
+ // Otherwise its something that should be a string, convert it.
+ let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ stream = converter.convertToInputStream(aUploadData.toString());
+ }
+
+ // If aContentType is empty, the protocol will assume that no content headers are to be
+ // added to the uploaded stream and that any required headers are already encoded in
+ // the stream. In the case of HTTP, if this parameter is non-empty, then its value will
+ // replace any existing Content-Type header on the HTTP request. In the case of FTP and
+ // FILE, this parameter is ignored.
+ aHttpChannel.setUploadStream(stream, aContentType, -1);
+ }
+
+ //must be set after setUploadStream
+ //https://developer.mozilla.org/en-US/docs/Mozilla/Creating_sandboxed_HTTP_connections
+ aHttpChannel.QueryInterface(Ci.nsIHttpChannel);
+ aHttpChannel.requestMethod = aMethod;
+}
+
+/**
+ * Convert a byte array to a string - copied from lightning
+ *
+ * @param {octet[]} aResult The bytes to convert
+ * @param {Boolean} responseAsBase64 Return a base64 encoded string
+ * @param {String} aCharset The character set of the bytes, defaults to utf-8
+ * @param {Boolean} aThrow If true, the function will raise an exception on error
+ * @returns {?String} The string result, or null on error
+ */
+function convertByteArray(aResult, responseAsBase64 = false, aCharset="utf-8", aThrow) {
+ if (responseAsBase64) {
+ var bin = '';
+ var bytes = Uint8Array.from(aResult);
+ var len = bytes.byteLength;
+ for (var i = 0; i < len; i++) {
+ bin += String.fromCharCode( bytes[ i ] );
+ }
+ return btoa( bin ); // if we ever need raw, return bin
+ } else {
+ try {
+ return new TextDecoder(aCharset).decode(Uint8Array.from(aResult));
+ } catch (e) {
+ if (aThrow) {
+ throw e;
+ }
+ }
+ }
+ return null;
+}
+
diff --git a/content/OverlayManager.jsm b/content/OverlayManager.jsm
new file mode 100644
index 0000000..bca4eda
--- /dev/null
+++ b/content/OverlayManager.jsm
@@ -0,0 +1,514 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var EXPORTED_SYMBOLS = ["OverlayManager"];
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function OverlayManager(extension, options = {}) {
+ this.registeredOverlays = {};
+ this.overlays = {};
+ this.stylesheets = {};
+ this.options = {verbose: 0};
+ this.extension = extension;
+
+ let userOptions = Object.keys(options);
+ for (let i=0; i < userOptions.length; i++) {
+ this.options[userOptions[i]] = options[userOptions[i]];
+ }
+
+
+
+ this.windowListener = {
+ that: this,
+ onOpenWindow: function(xulWindow) {
+ let window = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
+ this.that.injectAllOverlays(window);
+ },
+ onCloseWindow: function(xulWindow) { },
+ onWindowTitleChange: function(xulWindow, newTitle) { }
+ };
+
+
+
+
+ this.startObserving = function () {
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ let window = windows.getNext();
+ //inject overlays for this window
+ this.injectAllOverlays(window);
+ }
+
+ Services.wm.addListener(this.windowListener);
+ };
+
+ this.stopObserving = function () {
+ Services.wm.removeListener(this.windowListener);
+
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ let window = windows.getNext();
+ //remove overlays (if any)
+ this.removeAllOverlays(window);
+ }
+ };
+
+ this.hasRegisteredOverlays = function (window) {
+ return this.registeredOverlays.hasOwnProperty(window.location.href);
+ };
+
+ this.registerOverlay = async function (dst, overlay) {
+ if (overlay.startsWith("chrome://")) {
+ let xul = null;
+ try {
+ xul = await this.readChromeFile(overlay);
+ } catch (e) {
+ console.log("Error reading file <"+overlay+"> : " + e);
+ return;
+ }
+ let rootNode = this.getDataFromXULString(xul);
+
+ if (rootNode) {
+ //get urls of stylesheets to load them
+ let styleSheetUrls = this.getStyleSheetUrls(rootNode);
+ for (let i=0; i<styleSheetUrls.length; i++) {
+ //we must replace, since we do not know, if it changed - could have been an update
+ //if (!this.stylesheets.hasOwnProperty(styleSheetUrls[i])) {
+ this.stylesheets[styleSheetUrls[i]] = await this.readChromeFile(styleSheetUrls[i]);
+ //}
+ }
+
+ if (!this.registeredOverlays[dst]) this.registeredOverlays[dst] = [];
+ if (!this.registeredOverlays[dst].includes(overlay)) this.registeredOverlays[dst].push(overlay);
+
+ this.overlays[overlay] = rootNode;
+ }
+ } else {
+ console.log("Only chrome:// URIs can be registered as overlays.");
+ }
+ };
+
+ this.getDataFromXULString = function (str) {
+ let data = null;
+ let xul = "";
+ if (str == "") {
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: A provided XUL file is empty!");
+ return null;
+ }
+
+ let oParser = new DOMParser();
+ try {
+ xul = oParser.parseFromString(str, "application/xml");
+ } catch (e) {
+ //however, domparser does not throw an error, it returns an error document
+ //https://developer.mozilla.org/de/docs/Web/API/DOMParser
+ //just in case
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: A provided XUL file could not be parsed correctly, something is wrong.\n" + str);
+ return null;
+ }
+
+ //check if xul is error document
+ if (xul.documentElement.nodeName == "parsererror") {
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: A provided XUL file could not be parsed correctly, something is wrong.\n" + str);
+ return null;
+ }
+
+ if (xul.documentElement.nodeName != "overlay") {
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: A provided XUL file does not look like an overlay (root node is not overlay).\n" + str);
+ return null;
+ }
+
+ return xul;
+ };
+
+
+
+
+
+ this.injectAllOverlays = async function (window, _href = null) {
+ if (window.document.readyState != "complete") {
+ // Make sure the window load has completed.
+ await new Promise(resolve => {
+ window.addEventListener("load", resolve, { once: true });
+ });
+ }
+
+ let href = (_href === null) ? window.location.href : _href;
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] Injecting into new window: " + href);
+ let injectCount = 0;
+ for (let i=0; this.registeredOverlays[href] && i < this.registeredOverlays[href].length; i++) {
+ if (this.injectOverlay(window, this.registeredOverlays[href][i])) injectCount++;
+ }
+ if (injectCount > 0) {
+ // dispatch a custom event to indicate we finished loading the overlay
+ let event = new Event("DOMOverlayLoaded_" + this.extension.id);
+ window.document.dispatchEvent(event);
+ }
+ };
+
+ this.removeAllOverlays = function (window) {
+ if (!this.hasRegisteredOverlays(window))
+ return;
+
+ for (let i=0; i < this.registeredOverlays[window.location.href].length; i++) {
+ this.removeOverlay(window, this.registeredOverlays[window.location.href][i]);
+ }
+ };
+
+
+
+
+ this.injectOverlay = function (window, overlay) {
+ if (!window.hasOwnProperty("injectedOverlays")) window.injectedOverlays = [];
+
+ if (window.injectedOverlays.includes(overlay)) {
+ if (this.options.verbose>2) Services.console.logStringMessage("[OverlayManager] NOT Injecting: " + overlay);
+ return false;
+ }
+
+ let rootNode = this.overlays[overlay];
+
+ if (rootNode) {
+ let overlayNode = rootNode.documentElement;
+ if (overlayNode) {
+ //get and load scripts
+ let scripts = this.getScripts(rootNode, overlayNode);
+ for (let i=0; i < scripts.length; i++){
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Loading: " + scripts[i]);
+ try {
+ Services.scriptloader.loadSubScript(scripts[i], window);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ let omscopename = overlayNode.hasAttribute("omscope") ? overlayNode.getAttribute("omscope") : null;
+ let omscope = omscopename ? window[omscopename] : window;
+
+ let inject = true;
+ if (omscope.hasOwnProperty("onBeforeInject")) {
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Executing " + (omscopename ? omscopename : "window") + ".onBeforeInject()");
+ try {
+ inject = omscope.onBeforeInject(window);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ if (inject) {
+ if (this.options.verbose>2) Services.console.logStringMessage("[OverlayManager] Injecting: " + overlay);
+ window.injectedOverlays.push(overlay);
+
+ //get urls of stylesheets to add preloaded files
+ let styleSheetUrls = this.getStyleSheetUrls(rootNode);
+ for (let i=0; i<styleSheetUrls.length; i++) {
+ let namespace = overlayNode.lookupNamespaceURI("html");
+ let element = window.document.createElementNS(namespace, "style");
+ element.id = styleSheetUrls[i];
+ element.textContent = this.stylesheets[styleSheetUrls[i]];
+ window.document.documentElement.appendChild(element);
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Stylesheet: " + styleSheetUrls[i]);
+ }
+
+ this.insertXulOverlay(window, overlayNode.children);
+ if (omscope.hasOwnProperty("onInject")) {
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Executing " + (omscopename ? omscopename : "window") + ".onInject()");
+ try {
+ omscope.onInject(window);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ // add to injectCounter
+ return true;
+ }
+ }
+ }
+
+ // nothing injected, do not add to inject counter
+ return false;
+ };
+
+ this.removeOverlay = function (window, overlay) {
+ if (!window.hasOwnProperty("injectedOverlays")) window.injectedOverlays = [];
+
+ if (!window.injectedOverlays.includes(overlay)) {
+ if (this.options.verbose>2) Services.console.logStringMessage("[OverlayManager] NOT Removing: " + overlay);
+ return;
+ }
+
+ if (this.options.verbose>2) Services.console.logStringMessage("[OverlayManager] Removing: " + overlay);
+ window.injectedOverlays = window.injectedOverlays.filter(e => (e != overlay));
+
+ let rootNode = this.overlays[overlay];
+ if (rootNode) {
+ let overlayNode = rootNode.documentElement;
+ if (overlayNode) {
+ let omscopename = overlayNode.hasAttribute("omscope") ? overlayNode.getAttribute("omscope") : null;
+ let omscope = omscopename ? window[omscopename] : window;
+
+ if (omscope.hasOwnProperty("onRemove")) {
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Executing " + (omscopename ? omscopename : "window") + ".onRemove()");
+ try {
+ omscope.onRemove(window);
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ this.removeXulOverlay(window, overlayNode.children);
+ }
+
+ //get urls of stylesheets to remove styte tag
+ let styleSheetUrls = this.getStyleSheetUrls(rootNode);
+ for (let i=0; i<styleSheetUrls.length; i++) {
+ let element = window.document.getElementById(styleSheetUrls[i]);
+ if (element) {
+ element.parentNode.removeChild(element);
+ }
+ }
+ }
+ };
+
+
+
+
+
+
+
+
+
+
+
+ this.getStyleSheetUrls = function (rootNode) {
+ let sheetsIterator = rootNode.evaluate("processing-instruction('xml-stylesheet')", rootNode, null, 0, null); //PathResult.ANY_TYPE = 0
+ let urls = [];
+
+ let sheet;
+ while (sheet = sheetsIterator.iterateNext()) { //returns object XMLStylesheetProcessingInstruction]
+ let attr=sheet.data.split(" ");
+ for (let a=0; a < attr.length; a++) {
+ if (attr[a].startsWith("href=")) urls.push(attr[a].substring(6,attr[a].length-1));
+ }
+ }
+ return urls;
+ };
+
+ this.getScripts = function(rootNode, overlayNode) {
+ let nodeIterator = rootNode.evaluate("./script", overlayNode, null, 0, null); //PathResult.ANY_TYPE = 0
+ let scripts = [];
+
+ let node;
+ while (node = nodeIterator.iterateNext()) {
+ if (node.hasAttribute("src") && node.hasAttribute("type") && node.getAttribute("type").toLowerCase().includes("javascript")) {
+ scripts.push(node.getAttribute("src"));
+ }
+ }
+ return scripts;
+ };
+
+
+
+
+
+
+
+
+
+
+ this.createXulElement = function (window, node, forcedNodeName = null) {
+ //check for namespace
+ let typedef = forcedNodeName ? forcedNodeName.split(":") : node.nodeName.split(":");
+ if (typedef.length == 2) typedef[0] = node.lookupNamespaceURI(typedef[0]);
+
+ let CE = {}
+ if (node.attributes && node.attributes.getNamedItem("is")) {
+ for (let i=0; i <node.attributes.length; i++) {
+ if (node.attributes[i].name == "is") {
+ CE = { "is" : node.attributes[i].value };
+ break;
+ }
+ }
+ }
+
+ let element = (typedef.length==2) ? window.document.createElementNS(typedef[0], typedef[1]) : window.document.createXULElement(typedef[0], CE);
+ if (node.attributes) {
+ for (let i=0; i <node.attributes.length; i++) {
+ element.setAttribute(node.attributes[i].name, node.attributes[i].value);
+ }
+ }
+
+ //add text child nodes as textContent
+ if (node.hasChildNodes) {
+ let textContent = "";
+ for (let child of node.childNodes) {
+ if (child.nodeType == "3") {
+ textContent += child.nodeValue;
+ }
+ }
+ if (textContent) element.textContent = textContent
+ }
+
+ return element;
+ };
+
+ this.insertXulOverlay = function (window, nodes, parentElement = null) {
+ /*
+ The passed nodes value could be an entire window.document in a single node (type 9) or a
+ single element node (type 1) as returned by getElementById. It could however also
+ be an array of nodes as returned by getElementsByTagName or a nodeList as returned
+ by childNodes. In that case node.length is defined.
+ */
+ let nodeList = [];
+ if (nodes.length === undefined) nodeList.push(nodes);
+ else nodeList = nodes;
+
+ // nodelist contains all childs
+ for (let node of nodeList) {
+ let element = null;
+ let hookMode = null;
+ let hookName = null;
+ let hookElement = null;
+
+ if (node.nodeName == "script" && node.hasAttribute("src")) {
+ //skip, since they are handled by getScripts()
+ } else if (node.nodeType == 1) {
+
+ if (!parentElement) { //misleading: if it does not have a parentElement, it is a top level element
+ //Adding top level elements without id is not allowed, because we need to be able to remove them!
+ if (!node.hasAttribute("id")) {
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: A top level <" + node.nodeName+ "> element does not have an ID. Skipped");
+ continue;
+ }
+
+ //check for inline script tags
+ if (node.nodeName == "script") {
+ let element = this.createXulElement(window, node, "html:script"); //force as html:script
+ window.document.documentElement.appendChild(element);
+ continue;
+ }
+
+ //check for inline style
+ if (node.nodeName == "style") {
+ let element = this.createXulElement(window, node, "html:style"); //force as html:style
+ window.document.documentElement.appendChild(element);
+ continue;
+ }
+
+ if (node.hasAttribute("appendto")) hookMode = "appendto";
+ if (node.hasAttribute("insertbefore")) hookMode ="insertbefore";
+ if (node.hasAttribute("insertafter")) hookMode = "insertafter";
+
+ if (hookMode) {
+ hookName = node.getAttribute(hookMode);
+ hookElement = window.document.getElementById(hookName);
+
+ if (!hookElement) {
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: The hook element <"+hookName+"> of top level overlay element <"+ node.nodeName+"> does not exist. Skipped");
+ continue;
+ }
+ } else {
+ hookMode = "appendto";
+ hookName = "ROOT";
+ hookElement = window.document.documentElement;
+ }
+ }
+
+ element = this.createXulElement(window, node);
+ if (node.hasChildNodes) this.insertXulOverlay(window, node.children, element);
+
+ if (parentElement) {
+ // this is a child level XUL element which needs to be added to to its parent
+ parentElement.appendChild(element);
+ } else {
+ // this is a toplevel element, which needs to be added at insertafter or insertbefore
+ switch (hookMode) {
+ case "appendto":
+ hookElement.appendChild(element);
+ break;
+ case "insertbefore":
+ hookElement.parentNode.insertBefore(element, hookElement);
+ break;
+ case "insertafter":
+ hookElement.parentNode.insertBefore(element, hookElement.nextSibling);
+ break;
+ default:
+ if (this.options.verbose>1) Services.console.logStringMessage("[OverlayManager] BAD XUL: Top level overlay element <"+ node.nodeName+"> uses unknown hook type <"+hookMode+">. Skipped.");
+ continue;
+ }
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Adding <"+element.id+"> ("+element.tagName+") " + hookMode + " <" + hookName + ">");
+ }
+ }
+ }
+ };
+
+ this.removeXulOverlay = function (window, nodes, parentElement = null) {
+ //only scan toplevel elements and remove them
+ let nodeList = [];
+ if (nodes.length === undefined) nodeList.push(nodes);
+ else nodeList = nodes;
+
+ // nodelist contains all childs
+ for (let node of nodeList) {
+ let element = null;
+ switch(node.nodeType) {
+ case 1:
+ if (node.hasAttribute("id")) {
+ let element = window.document.getElementById(node.getAttribute("id"));
+ if (element) {
+ element.parentNode.removeChild(element);
+ }
+ }
+ break;
+ }
+ }
+ };
+
+
+
+
+
+
+
+
+
+
+ //read file from within the XPI package
+ this.readChromeFile = function (aURL) {
+ if (this.options.verbose>3) Services.console.logStringMessage("[OverlayManager] Reading file: " + aURL);
+ return new Promise((resolve, reject) => {
+ let uri = Services.io.newURI(aURL);
+ let channel = Services.io.newChannelFromURI(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Components.interfaces.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS,
+ Components.interfaces.nsIContentPolicy.TYPE_OTHER);
+
+ NetUtil.asyncFetch(channel, (inputStream, status) => {
+ if (!Components.isSuccessCode(status)) {
+ reject(status);
+ return;
+ }
+
+ try {
+ let data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+ resolve(data);
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ });
+ };
+
+}
diff --git a/content/api/BootstrapLoader/README.md b/content/api/BootstrapLoader/README.md
new file mode 100644
index 0000000..7e8fe2a
--- /dev/null
+++ b/content/api/BootstrapLoader/README.md
@@ -0,0 +1 @@
+Usage description can be found in the [wiki](https://github.com/thundernest/addon-developer-support/wiki/Using-the-BootstrapLoader-API-to-convert-a-Legacy-Bootstrap-WebExtension-into-a-MailExtension-for-Thunderbird-78).
diff --git a/content/api/BootstrapLoader/implementation.js b/content/api/BootstrapLoader/implementation.js
new file mode 100644
index 0000000..aae3b60
--- /dev/null
+++ b/content/api/BootstrapLoader/implementation.js
@@ -0,0 +1,237 @@
+/*
+ * This file is provided by the addon-developer-support repository at
+ * https://github.com/thundernest/addon-developer-support
+ *
+ * Version 1.4
+ * - add registerOptionsPage
+ *
+ * Version: 1.3
+ * - flush cache
+ *
+ * Version: 1.2
+ * - add support for resource urls
+ *
+ * Author: John Bieling (john@thunderbird.net)
+ *
+ * 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/.
+ */
+
+// Get various parts of the WebExtension framework that we need.
+var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
+var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm");
+var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var BootstrapLoader = class extends ExtensionCommon.ExtensionAPI {
+ getAPI(context) {
+ this.uniqueRandomID = "AddOnNS" + context.extension.instanceId;
+ this.menu_addonsManager_id ="addonsManager";
+ this.menu_addonsManager_prefs_id = "addonsManager_prefs_revived";
+ this.menu_addonPrefs_id = "addonPrefs_revived";
+
+ this.pathToBootstrapScript = null;
+ this.pathToOptionsPage = null;
+ this.chromeHandle = null;
+ this.chromeData = null;
+ this.resourceData = null;
+ this.bootstrappedObj = {};
+
+ // make the extension object and the messenger object available inside
+ // the bootstrapped scope
+ this.bootstrappedObj.extension = context.extension;
+ this.bootstrappedObj.messenger = Array.from(context.extension.views)
+ .find(view => view.viewType === "background")
+ .xulBrowser.contentWindow.wrappedJSObject.browser;
+
+
+ this.BOOTSTRAP_REASONS = {
+ APP_STARTUP: 1,
+ APP_SHUTDOWN: 2,
+ ADDON_ENABLE: 3,
+ ADDON_DISABLE: 4,
+ ADDON_INSTALL: 5,
+ ADDON_UNINSTALL: 6, // not supported
+ ADDON_UPGRADE: 7,
+ ADDON_DOWNGRADE: 8,
+ };
+
+ const aomStartup = Cc["@mozilla.org/addons/addon-manager-startup;1"].getService(Ci.amIAddonManagerStartup);
+ const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler);
+
+ let self = this;
+
+ return {
+ BootstrapLoader: {
+
+ registerOptionsPage(optionsUrl) {
+ self.pathToOptionsPage = optionsUrl.startsWith("chrome://")
+ ? optionsUrl
+ : context.extension.rootURI.resolve(optionsUrl);
+ },
+
+ registerChromeUrl(data) {
+ let chromeData = [];
+ let resourceData = [];
+ for (let entry of data) {
+ if (entry[0] == "resource") resourceData.push(entry);
+ else chromeData.push(entry)
+ }
+
+ if (chromeData.length > 0) {
+ const manifestURI = Services.io.newURI(
+ "manifest.json",
+ null,
+ context.extension.rootURI
+ );
+ self.chromeHandle = aomStartup.registerChrome(manifestURI, chromeData);
+ }
+
+ for (let res of resourceData) {
+ // [ "resource", "shortname" , "path" ]
+ let uri = Services.io.newURI(
+ res[2],
+ null,
+ context.extension.rootURI
+ );
+ resProto.setSubstitutionWithFlags(
+ res[1],
+ uri,
+ resProto.ALLOW_CONTENT_ACCESS
+ );
+ }
+
+ self.chromeData = chromeData;
+ self.resourceData = resourceData;
+ },
+
+ registerBootstrapScript: async function(aPath) {
+ self.pathToBootstrapScript = aPath.startsWith("chrome://")
+ ? aPath
+ : context.extension.rootURI.resolve(aPath);
+
+ // Get the addon object belonging to this extension.
+ let addon = await AddonManager.getAddonByID(context.extension.id);
+ //make the addon globally available in the bootstrapped scope
+ self.bootstrappedObj.addon = addon;
+
+ // add BOOTSTRAP_REASONS to scope
+ for (let reason of Object.keys(self.BOOTSTRAP_REASONS)) {
+ self.bootstrappedObj[reason] = self.BOOTSTRAP_REASONS[reason];
+ }
+
+ // Load registered bootstrap scripts and execute its startup() function.
+ try {
+ if (self.pathToBootstrapScript) Services.scriptloader.loadSubScript(self.pathToBootstrapScript, self.bootstrappedObj, "UTF-8");
+ if (self.bootstrappedObj.startup) self.bootstrappedObj.startup.call(self.bootstrappedObj, self.extension.addonData, self.BOOTSTRAP_REASONS[self.extension.startupReason]);
+ } catch (e) {
+ Components.utils.reportError(e)
+ }
+
+ // Register window listener for main TB window
+ if (self.pathToOptionsPage) {
+ ExtensionSupport.registerWindowListener("injectListener_" + self.uniqueRandomID, {
+ chromeURLs: [
+ "chrome://messenger/content/messenger.xul",
+ "chrome://messenger/content/messenger.xhtml",
+ ],
+ async onLoadWindow(window) {
+ try {
+ // add the add-on options menu if needed
+ if (!window.document.getElementById(self.menu_addonsManager_prefs_id)) {
+ let addonprefs = window.MozXULElement.parseXULToFragment(`
+ <menu id="${self.menu_addonsManager_prefs_id}" label="&addonPrefs.label;">
+ <menupopup id="${self.menu_addonPrefs_id}">
+ </menupopup>
+ </menu>
+ `, ["chrome://messenger/locale/messenger.dtd"]);
+
+ let element_addonsManager = window.document.getElementById(self.menu_addonsManager_id);
+ element_addonsManager.parentNode.insertBefore(addonprefs, element_addonsManager.nextSibling);
+ }
+
+ // add the options entry
+ let element_addonPrefs = window.document.getElementById(self.menu_addonPrefs_id);
+ let id = self.menu_addonPrefs_id + "_" + self.uniqueRandomID;
+
+ // Get the best size of the icon (16px or bigger)
+ let iconSizes = Object.keys(self.extension.manifest.icons);
+ iconSizes.sort((a,b)=>a-b);
+ let bestSize = iconSizes.filter(e => parseInt(e) >= 16).shift();
+ let icon = bestSize ? self.extension.manifest.icons[bestSize] : "";
+
+ let name = self.extension.manifest.name;
+ let entry = window.MozXULElement.parseXULToFragment(
+ `<menuitem class="menuitem-iconic" id="${id}" image="${icon}" label="${name}" />`);
+ element_addonPrefs.appendChild(entry);
+ let BL = {}
+ BL.extension = self.extension;
+ BL.messenger = Array.from(self.extension.views).find(
+ view => view.viewType === "background").xulBrowser.contentWindow
+ .wrappedJSObject.browser;
+ window.document.getElementById(id).addEventListener("command", function() {window.openDialog(self.pathToOptionsPage, "AddonOptions", null, BL)});
+ } catch (e) {
+ Components.utils.reportError(e)
+ }
+ },
+
+ onUnloadWindow(window) {
+ }
+ });
+ }
+ }
+ }
+ };
+ }
+
+ onShutdown(isAppShutdown) {
+ //remove our entry in the add-on options menu
+ if (this.pathToOptionsPage) {
+ for (let window of Services.wm.getEnumerator("mail:3pane")) {
+ let id = this.menu_addonPrefs_id + "_" + this.uniqueRandomID;
+ window.document.getElementById(id).remove();
+
+ //do we have to remove the entire add-on options menu?
+ let element_addonPrefs = window.document.getElementById(this.menu_addonPrefs_id);
+ if (element_addonPrefs.children.length == 0) {
+ window.document.getElementById(this.menu_addonsManager_prefs_id).remove();
+ }
+ }
+ // Stop listening for new windows.
+ ExtensionSupport.unregisterWindowListener("injectListener_" + this.uniqueRandomID);
+ }
+
+ // Execute registered shutdown()
+ try {
+ if (this.bootstrappedObj.shutdown) {
+ this.bootstrappedObj.shutdown(
+ this.extension.addonData,
+ isAppShutdown
+ ? this.BOOTSTRAP_REASONS.APP_SHUTDOWN
+ : this.BOOTSTRAP_REASONS.ADDON_DISABLE);
+ }
+ } catch (e) {
+ Components.utils.reportError(e)
+ }
+
+ if (this.resourceData) {
+ const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsISubstitutingProtocolHandler);
+ for (let res of this.resourceData) {
+ // [ "resource", "shortname" , "path" ]
+ resProto.setSubstitution(
+ res[1],
+ null,
+ );
+ }
+ }
+
+ if (this.chromeHandle) {
+ this.chromeHandle.destruct();
+ this.chromeHandle = null;
+ }
+ // Flush all caches
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+ console.log("BootstrapLoader for " + this.extension.id + " unloaded!");
+ }
+};
diff --git a/content/api/BootstrapLoader/schema.json b/content/api/BootstrapLoader/schema.json
new file mode 100644
index 0000000..92e856c
--- /dev/null
+++ b/content/api/BootstrapLoader/schema.json
@@ -0,0 +1,50 @@
+[
+ {
+ "namespace": "BootstrapLoader",
+ "functions": [
+ {
+ "name": "registerOptionsPage",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "aPath",
+ "type": "string",
+ "description": "Path to the options page, which should be made accessible in the (legacy) Add-On Options menu."
+ }
+ ]
+ },
+ {
+ "name": "registerChromeUrl",
+ "type": "function",
+ "description": "Register folders which should be available as chrome:// urls (as defined in the legacy chrome.manifest)",
+ "async": true,
+ "parameters": [
+ {
+ "name": "chromeData",
+ "type": "array",
+ "items": {
+ "type": "array",
+ "items" : {
+ "type": "string"
+ }
+ },
+ "description": "Array of ChromeData Arrays."
+ }
+ ]
+ },
+ {
+ "name": "registerBootstrapScript",
+ "type": "function",
+ "description": "Register a bootstrap.js style script",
+ "async": true,
+ "parameters": [
+ {
+ "name": "aPath",
+ "type": "string",
+ "description": "Either the chrome:// path to the script or its relative location from the root of the extension,"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/content/manager/accountManager.js b/content/manager/accountManager.js
new file mode 100644
index 0000000..d442222
--- /dev/null
+++ b/content/manager/accountManager.js
@@ -0,0 +1,140 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncAccountManager = {
+
+ onloadoptions: function () {
+ window.close();
+ },
+
+ onunloadoptions: function () {
+ TbSync.manager.openManagerWindow(0);
+ },
+
+ onload: function () {
+ TbSync.AccountManagerTabs = ["accounts.xhtml", "catman.xhtml", "supporter.xhtml", "help.xhtml"];
+ tbSyncAccountManager.selectTab(0);
+ },
+
+ onunload: function () {
+ TbSync.manager.prefWindowObj = null;
+ },
+
+ selectTab: function (t) {
+ const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
+
+ //set active tab (css selector for background color)
+ for (let i=0; i<TbSync.AccountManagerTabs.length; i++) {
+ if (i==t) document.getElementById("tbSyncAccountManager.t" + i).setAttribute("active","true");
+ else document.getElementById("tbSyncAccountManager.t" + i).setAttribute("active","false");
+ }
+ TbSync.manager.prefWindowObj.document.getElementById("tbSyncAccountManager.installProvider").hidden=true;
+
+ //load XUL
+ document.getElementById("tbSyncAccountManager.contentWindow").setAttribute("src", "chrome://tbsync/content/manager/"+TbSync.AccountManagerTabs[t]);
+ },
+
+
+
+ //help tab
+ getLogPref: function() {
+ let log = document.getElementById("tbSyncAccountManager.logLevel");
+ log.value = Math.min(3, TbSync.prefs.getIntPref("log.userdatalevel"));
+ },
+
+ toggleLogPref: function() {
+ let log = document.getElementById("tbSyncAccountManager.logLevel");
+ TbSync.prefs.setIntPref("log.userdatalevel", log.value);
+ },
+
+ initSupportWizard: function() {
+ document.getElementById("SupportWizard").getButton("finish").disabled = true;
+
+ let menu = document.getElementById("tbsync.supportwizard.faultycomponent");
+
+ let providers = Object.keys(TbSync.providers.loadedProviders);
+ for (let i=0; i < providers.length; i++) {
+ let item = document.createXULElement("menuitem");
+ item.setAttribute("value", providers[i]);
+ item.setAttribute("label", TbSync.getString("supportwizard.provider::" + TbSync.providers[providers[i]].Base.getProviderName()));
+ menu.appendChild(item);
+ }
+
+ document.getElementById("tbsync.supportwizard.faultycomponent.menulist").addEventListener("select", tbSyncAccountManager.checkSupportWizard);
+ document.getElementById("tbsync.supportwizard.description").addEventListener("input", tbSyncAccountManager.checkSupportWizard);
+ document.addEventListener("wizardfinish", tbSyncAccountManager.prepareBugReport);
+
+ // bug https://bugzilla.mozilla.org/show_bug.cgi?id=1618252
+ document.getElementById('SupportWizard')._adjustWizardHeader();
+ },
+
+ checkSupportWizard: function() {
+ let provider = document.getElementById("tbsync.supportwizard.faultycomponent").parentNode.value;
+ let subject = document.getElementById("tbsync.supportwizard.summary").value;
+ let description = document.getElementById("tbsync.supportwizard.description").value;
+
+ //just check and update button status
+ document.getElementById("SupportWizard").getButton("finish").disabled = (provider == "" || subject == "" || description== "");
+ },
+
+ prepareBugReport: function(event) {
+ let provider = document.getElementById("tbsync.supportwizard.faultycomponent").parentNode.value;
+ let subject = document.getElementById("tbsync.supportwizard.summary").value;
+ let description = document.getElementById("tbsync.supportwizard.description").value;
+
+ if (provider == "" || subject == "" || description== "") {
+ event.preventDefault();
+ return;
+ }
+
+ //special if core is selected, which is not a provider
+ let email = (TbSync.providers.loadedProviders.hasOwnProperty(provider)) ? TbSync.providers[provider].Base.getMaintainerEmail() : "john.bieling@gmx.de";
+ let version = (TbSync.providers.loadedProviders.hasOwnProperty(provider)) ? " " + TbSync.providers.loadedProviders[provider].version : "";
+ TbSync.manager.createBugReport(email, "[" + provider.toUpperCase() + version + "] " + subject, description);
+ },
+
+
+
+ //community tab
+ initCommunity: function() {
+ let listOfContributors = document.getElementById("listOfContributors");
+ let sponsors = {};
+
+ let providers = Object.keys(TbSync.providers.loadedProviders);
+ for (let i=0; i < providers.length; i++) {
+ let provider = providers[i];
+ let template = listOfContributors.firstElementChild.cloneNode(true);
+ template.setAttribute("provider", provider);
+ template.children[0].setAttribute("src", TbSync.providers[provider].Base.getProviderIcon(48));
+ template.children[1].children[0].textContent = TbSync.providers[provider].Base.getProviderName();
+ listOfContributors.appendChild(template);
+ Object.assign(sponsors, TbSync.providers[provider].Base.getSponsors());
+ }
+ listOfContributors.removeChild(listOfContributors.firstElementChild);
+
+ let listOfSponsors = document.getElementById("listOfSponsors");
+ let sponsorlist = Object.keys(sponsors);
+ sponsorlist.sort();
+ for (let i=0; i < sponsorlist.length; i++) {
+ let sponsor = sponsors[sponsorlist[i]];
+ let template = listOfSponsors.firstElementChild.cloneNode(true);
+ if (sponsor.link) template.setAttribute("link", sponsor.link);
+ if (sponsor.icon) template.children[0].setAttribute("src", sponsor.icon);
+ template.children[1].children[0].textContent = sponsor.name;
+ template.children[1].children[1].textContent = sponsor.description;
+ listOfSponsors.appendChild(template);
+ listOfSponsors.appendChild(template);
+ }
+ listOfSponsors.removeChild(listOfSponsors.firstElementChild);
+ }
+};
diff --git a/content/manager/accountManager.xhtml b/content/manager/accountManager.xhtml
new file mode 100644
index 0000000..d136240
--- /dev/null
+++ b/content/manager/accountManager.xhtml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="__TBSYNCMSG_manager.title__"
+ onload="tbSyncAccountManager.onload();"
+ onunload="tbSyncAccountManager.onunload();"
+ width="760" height="620" >
+
+ <vbox id="manager" flex="1">
+ <hbox id="tbtoolbar">
+ <vbox id="tbSyncAccountManager.t0" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="tbSyncAccountManager.selectTab(0)"><hbox flex="1" pack="center" style="min-width:60px"><image src="chrome://tbsync/content/skin/settings32.png" style="width: 32px; height: 32px" /></hbox><hbox flex="1" pack="center"><label value="__TBSYNCMSG_manager.accountsettings__" /></hbox></vbox>
+ <vbox id="tbSyncAccountManager.t1" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="tbSyncAccountManager.selectTab(1)"><hbox flex="1" pack="center" style="min-width:60px"><image src="chrome://tbsync/content/skin/catman32.png" style="width: 32px; height: 32px" /></hbox><hbox flex="1" pack="center"><label value="Category Manager" /></hbox></vbox>
+ <vbox id="tbSyncAccountManager.t2" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="tbSyncAccountManager.selectTab(2)"><hbox flex="1" pack="center" style="min-width:60px"><image src="chrome://tbsync/content/skin/group32.png" style="width: 32px; height: 32px" /></hbox><hbox flex="1" pack="center"><label value="__TBSYNCMSG_manager.community__" /></hbox></vbox>
+ <vbox id="tbSyncAccountManager.t3" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="tbSyncAccountManager.selectTab(3)"><hbox flex="1" pack="center" style="min-width:60px"><image src="chrome://tbsync/content/skin/help32.png" style="width: 32px; height: 32px" /></hbox><hbox flex="1" pack="center"><label value="__TBSYNCMSG_manager.help__" /></hbox></vbox>
+ <vbox id="tbSyncAccountManager.installProvider" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" hidden="true"><hbox flex="1" pack="center" style="min-width:60px"><image src="chrome://tbsync/content/skin/provider32.png" style="width: 32px; height: 32px" /></hbox><hbox flex="1" pack="center"><label value="__TBSYNCMSG_manager.provider__" /></hbox></vbox>
+ </hbox>
+ <browser id="tbSyncAccountManager.contentWindow" type="chrome" src="" disablehistory="true" flex="1"/>
+ </vbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/accounts.js b/content/manager/accounts.js
new file mode 100644
index 0000000..019852c
--- /dev/null
+++ b/content/manager/accounts.js
@@ -0,0 +1,498 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncAccounts = {
+
+ selectedAccount: null,
+
+ onload: function () {
+ //scan accounts, update list and select first entry (because no id is passed to updateAccountList)
+ //the onSelect event of the List will load the selected account
+ //also update/init add menu
+ this.updateAvailableProvider();
+
+ Services.obs.addObserver(tbSyncAccounts.updateProviderListObserver, "tbsync.observer.manager.updateProviderList", false);
+ Services.obs.addObserver(tbSyncAccounts.updateAccountsListObserver, "tbsync.observer.manager.updateAccountsList", false);
+ Services.obs.addObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.observer.manager.updateSyncstate", false);
+ Services.obs.addObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.observer.manager.updateAccountName", false);
+ Services.obs.addObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.observer.manager.toggleEnableState", false);
+ },
+
+ onunload: function () {
+ Services.obs.removeObserver(tbSyncAccounts.updateProviderListObserver, "tbsync.observer.manager.updateProviderList");
+ Services.obs.removeObserver(tbSyncAccounts.updateAccountsListObserver, "tbsync.observer.manager.updateAccountsList");
+ Services.obs.removeObserver(tbSyncAccounts.updateAccountSyncStateObserver, "tbsync.observer.manager.updateSyncstate");
+ Services.obs.removeObserver(tbSyncAccounts.updateAccountNameObserver, "tbsync.observer.manager.updateAccountName");
+ Services.obs.removeObserver(tbSyncAccounts.toggleEnableStateObserver, "tbsync.observer.manager.toggleEnableState");
+ },
+
+ hasInstalledProvider: function (accountID) {
+ let provider = TbSync.db.getAccountProperty(accountID, "provider");
+ return TbSync.providers.loadedProviders.hasOwnProperty(provider);
+ },
+
+ updateDropdown: function (selector) {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+ let selectedAccount = null;
+ let selectedAccountName = "";
+ let isActionsDropdown = (selector == "accountActions");
+
+ let isSyncing = false;
+ let isConnected = false;
+ let isEnabled = false;
+ let isInstalled = false;
+
+ if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
+ //some item is selected
+ let selectedItem = accountsList.selectedItem;
+ selectedAccount = selectedItem.value;
+ selectedAccountName = selectedItem.childNodes[1].getAttribute("value");
+ isSyncing = TbSync.core.isSyncing(selectedAccount);
+ isConnected = TbSync.core.isConnected(selectedAccount);
+ isEnabled = TbSync.core.isEnabled(selectedAccount);
+ isInstalled = tbSyncAccounts.hasInstalledProvider(selectedAccount);
+ }
+
+ //hide if no accounts are avail (which is identical to no account selected)
+ if (isActionsDropdown) document.getElementById(selector + "SyncAllAccounts").hidden = (selectedAccount === null);
+
+ //hide if no account is selected
+ if (isActionsDropdown) document.getElementById(selector + "Separator").hidden = (selectedAccount === null);
+ document.getElementById(selector + "DeleteAccount").hidden = (selectedAccount === null);
+ document.getElementById(selector + "DisableAccount").hidden = (selectedAccount === null) || !isEnabled || !isInstalled;
+ document.getElementById(selector + "EnableAccount").hidden = (selectedAccount === null) || isEnabled || !isInstalled;
+ document.getElementById(selector + "SyncAccount").hidden = (selectedAccount === null) || !isConnected || !isInstalled;
+ document.getElementById(selector + "RetryConnectAccount").hidden = (selectedAccount === null) || isConnected || !isEnabled || !isInstalled;
+
+ if (document.getElementById(selector + "ShowEventLog")) {
+ document.getElementById(selector + "ShowEventLog").hidden = false;
+ document.getElementById(selector + "ShowEventLog").disabled = false;
+ }
+
+ if (selectedAccount !== null) {
+ //disable if currently syncing (and displayed)
+ document.getElementById(selector + "DeleteAccount").disabled = isSyncing;
+ document.getElementById(selector + "DisableAccount").disabled = isSyncing;
+ document.getElementById(selector + "EnableAccount").disabled = isSyncing;
+ document.getElementById(selector + "SyncAccount").disabled = isSyncing;
+ //adjust labels - only in global actions dropdown
+ if (isActionsDropdown) document.getElementById(selector + "DeleteAccount").label = TbSync.getString("accountacctions.delete").replace("##accountname##", selectedAccountName);
+ if (isActionsDropdown) document.getElementById(selector + "SyncAccount").label = TbSync.getString("accountacctions.sync").replace("##accountname##", selectedAccountName);
+ if (isActionsDropdown) document.getElementById(selector + "EnableAccount").label = TbSync.getString("accountacctions.enable").replace("##accountname##", selectedAccountName);
+ if (isActionsDropdown) document.getElementById(selector + "DisableAccount").label = TbSync.getString("accountacctions.disable").replace("##accountname##", selectedAccountName);
+ }
+ },
+
+ synchronizeAccount: function () {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+ if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value) && !TbSync.core.isSyncing(accountsList.selectedItem.value)) {
+ if (tbSyncAccounts.hasInstalledProvider(accountsList.selectedItem.value)) {
+ TbSync.core.syncAccount(accountsList.selectedItem.value);
+ }
+ }
+ },
+
+ deleteAccount: function () {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+ if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value) && !TbSync.core.isSyncing(accountsList.selectedItem.value)) {
+ let nextAccount = -1;
+ if (accountsList.selectedIndex > 0) {
+ //first try to select the item after this one, otherwise take the one before
+ if (accountsList.selectedIndex + 1 < accountsList.getRowCount()) nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex + 1).value;
+ else nextAccount = accountsList.getItemAtIndex(accountsList.selectedIndex - 1).value;
+ }
+
+ if (!tbSyncAccounts.hasInstalledProvider(accountsList.selectedItem.value)) {
+ if (confirm(TbSync.getString("prompt.Erase").replace("##accountName##", accountsList.selectedItem.getAttribute("label")))) {
+ //delete account and all folders from db
+ TbSync.db.removeAccount(accountsList.selectedItem.value);
+ //update list
+ this.updateAccountsList(nextAccount);
+ }
+ } else if (confirm(TbSync.getString("prompt.DeleteAccount").replace("##accountName##", accountsList.selectedItem.getAttribute("label")))) {
+ //cache all folders and remove associated targets
+ TbSync.core.disableAccount(accountsList.selectedItem.value);
+
+ // the following call might fail, as not all providers provide that method, it was mainly added to cleanup stored passwords
+ try {
+ let accountData = new TbSync.AccountData(accountsList.selectedItem.value);
+ TbSync.providers[accountData.getAccountProperty("provider")].Base.onDeleteAccount(accountData);
+ } catch (e) { Components.utils.reportError(e);}
+
+ //delete account and all folders from db
+ TbSync.db.removeAccount(accountsList.selectedItem.value);
+ //update list
+ this.updateAccountsList(nextAccount);
+ }
+ }
+ },
+
+
+
+ /* * *
+ * Observer to catch update list request (upon provider load/unload)
+ */
+ updateAccountsListObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //aData is the accountID to be selected
+ //if missing, it will try to not change selection
+ tbSyncAccounts.updateAccountsList(aData);
+ }
+ },
+
+ updateProviderListObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //aData is a provider
+ tbSyncAccounts.updateAvailableProvider(aData);
+ }
+ },
+
+ toggleEnableState: function () {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+
+ if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value) && !TbSync.core.isSyncing(accountsList.selectedItem.value)) {
+ let isConnected = TbSync.core.isConnected(accountsList.selectedItem.value);
+ if (!isConnected || window.confirm(TbSync.getString("prompt.Disable"))) {
+ tbSyncAccounts.toggleAccountEnableState(accountsList.selectedItem.value);
+ }
+ }
+ },
+
+ /* * *
+ * Observer to catch enable state toggle
+ */
+ toggleEnableStateObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ tbSyncAccounts.toggleAccountEnableState(aData);
+ }
+ },
+
+ //is not prompting, this is doing the actual toggle
+ toggleAccountEnableState: function (accountID) {
+ if (tbSyncAccounts.hasInstalledProvider(accountID)) {
+ let isEnabled = TbSync.core.isEnabled(accountID);
+
+ if (isEnabled) {
+ //we are enabled and want to disable (do not ask, if not connected)
+ TbSync.core.disableAccount(accountID);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateAccountSettingsGui", accountID);
+ tbSyncAccounts.updateAccountStatus(accountID);
+ } else {
+ //we are disabled and want to enabled
+ TbSync.core.enableAccount(accountID);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateAccountSettingsGui", accountID);
+ TbSync.core.syncAccount(accountID);
+ }
+ }
+ },
+
+ /* * *
+ * Observer to catch synstate changes and to update account icons
+ */
+ updateAccountSyncStateObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ if (aData) {
+ //since we want rotating arrows on each syncstate change, we need to run this on each syncstate
+ tbSyncAccounts.updateAccountStatus(aData);
+ }
+ }
+ },
+
+ setStatusImage: function (accountID, obj) {
+ let statusImage = this.getStatusImage(accountID, obj.src);
+ if (statusImage != obj.src) {
+ obj.src = statusImage;
+ }
+ },
+
+ getStatusImage: function (accountID, current = "") {
+ let src = "";
+
+ if (!tbSyncAccounts.hasInstalledProvider(accountID)) {
+ src = "error16.png";
+ } else {
+ switch (TbSync.db.getAccountProperty(accountID, "status").split(".")[0]) {
+ case "success":
+ src = "tick16.png";
+ break;
+
+ case "disabled":
+ src = "disabled16.png";
+ break;
+
+ case "info":
+ case "notsyncronized":
+ case "modified":
+ src = "info16.png";
+ break;
+
+ case "warning":
+ src = "warning16.png";
+ break;
+
+ case "syncing":
+ switch (current.replace("chrome://tbsync/content/skin/","")) {
+ case "sync16_1.png":
+ src = "sync16_2.png";
+ break;
+ case "sync16_2.png":
+ src = "sync16_3.png";
+ break;
+ case "sync16_3.png":
+ src = "sync16_4.png";
+ break;
+ case "sync16_4.png":
+ src = "sync16_1.png";
+ break;
+ default:
+ src = "sync16_1.png";
+ TbSync.core.getSyncDataObject(accountID).accountManagerLastUpdated = 0;
+ break;
+ }
+ if ((Date.now() - TbSync.core.getSyncDataObject(accountID).accountManagerLastUpdated) < 300) {
+ return current;
+ }
+ TbSync.core.getSyncDataObject(accountID).accountManagerLastUpdated = Date.now();
+ break;
+
+ default:
+ src = "error16.png";
+ }
+ }
+
+ return "chrome://tbsync/content/skin/" + src;
+ },
+
+ updateAccountLogo: function (id) {
+ let accountData = new TbSync.AccountData(id);
+ let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
+ if (listItem) {
+ let obj = listItem.childNodes[0];
+ obj.src = tbSyncAccounts.hasInstalledProvider(id) ? TbSync.providers[accountData.getAccountProperty("provider")].Base.getProviderIcon(16, accountData) : "chrome://tbsync/content/skin/provider16.png";
+ }
+ },
+
+ updateAccountStatus: function (id) {
+ let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
+ if (listItem) {
+ let obj = listItem.childNodes[2];
+ this.setStatusImage(id, obj);
+ }
+ },
+
+ updateAccountNameObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ let pos = aData.indexOf(":");
+ let id = aData.substring(0, pos);
+ let name = aData.substring(pos+1);
+ tbSyncAccounts.updateAccountName (id, name);
+ }
+ },
+
+ updateAccountName: function (id, name) {
+ let listItem = document.getElementById("tbSyncAccounts.accounts." + id);
+ if (listItem.childNodes[1].getAttribute("value") != name) {
+ listItem.childNodes[1].setAttribute("value", name);
+ }
+ },
+
+ updateAvailableProvider: function (provider = null) {
+ //either add/remove a specific provider, or rebuild the list from scratch
+ if (provider) {
+ //update single provider entry
+ tbSyncAccounts.updateAddMenuEntry(provider);
+ } else {
+ //add default providers
+ for (let provider in TbSync.providers.defaultProviders) {
+ tbSyncAccounts.updateAddMenuEntry(provider);
+ }
+ //update/add all remaining installed providers
+ for (let provider in TbSync.providers.loadedProviders) {
+ tbSyncAccounts.updateAddMenuEntry(provider);
+ }
+ }
+
+ this.updateAccountsList();
+
+ let selectedAccount = this.getSelectedAccount();
+ if (selectedAccount !== null && TbSync.db.getAccountProperty(selectedAccount, "provider") == provider) {
+ tbSyncAccounts.loadSelectedAccount();
+ }
+ },
+
+ updateAccountsList: function (accountToSelect = null) {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+ let accounts = TbSync.db.getAccounts();
+
+ // try to keep the currently selected account, if accountToSelect is not given
+ if (accountToSelect === null) {
+ let s = accountsList.getItemAtIndex(accountsList.selectedIndex);
+ if (s) {
+ // there is an entry selected, do not change it
+ accountToSelect = s.value;
+ }
+ }
+
+ if (accounts.allIDs.length > null) {
+
+ //get current accounts in list and remove entries of accounts no longer there
+ let listedAccounts = [];
+ for (let i=accountsList.getRowCount()-1; i>=0; i--) {
+ let item = accountsList.getItemAtIndex(i);
+ listedAccounts.push(item.value);
+ if (accounts.allIDs.indexOf(item.value) == -1) {
+ item.remove();
+ }
+ }
+
+ //accounts array is without order, extract keys (ids) and loop over keys
+ for (let i = 0; i < accounts.allIDs.length; i++) {
+
+ if (listedAccounts.indexOf(accounts.allIDs[i]) == -1) {
+ //add all missing accounts (always to the end of the list)
+ let newListItem = document.createXULElement("richlistitem");
+ newListItem.setAttribute("id", "tbSyncAccounts.accounts." + accounts.allIDs[i]);
+ newListItem.setAttribute("value", accounts.allIDs[i]);
+ newListItem.setAttribute("align", "center");
+ newListItem.setAttribute("label", accounts.data[accounts.allIDs[i]].accountname);
+ newListItem.setAttribute("style", "padding: 5px 0px;");
+ newListItem.setAttribute("ondblclick", "tbSyncAccounts.toggleEnableState();");
+
+ //add icon (use "install provider" icon, if provider not installed)
+ let itemType = document.createXULElement("image");
+ itemType.setAttribute("width", "16");
+ itemType.setAttribute("height", "16");
+ itemType.setAttribute("style", "margin: 0px 0px 0px 5px;");
+ newListItem.appendChild(itemType);
+
+ //add account name
+ let itemLabel = document.createXULElement("label");
+ itemLabel.setAttribute("flex", "1");
+ newListItem.appendChild(itemLabel);
+
+ //add account status
+ let itemStatus = document.createXULElement("image");
+ itemStatus.setAttribute("width", "16");
+ itemStatus.setAttribute("height", "16");
+ itemStatus.setAttribute("style", "margin: 0px 5px;");
+ newListItem.appendChild(itemStatus);
+
+ accountsList.appendChild(newListItem);
+ }
+
+ //update/set actual values
+ this.updateAccountName(accounts.allIDs[i], accounts.data[accounts.allIDs[i]].accountname);
+ this.updateAccountStatus(accounts.allIDs[i]);
+ this.updateAccountLogo(accounts.allIDs[i]);
+ }
+
+ //find selected item
+ for (let i=0; i<accountsList.getRowCount(); i++) {
+ if (accountToSelect === null || accountToSelect == accountsList.getItemAtIndex(i).value) {
+ accountsList.selectedIndex = i;
+ accountsList.ensureIndexIsVisible(i);
+ break;
+ }
+ }
+
+ } else {
+ //No defined accounts, empty accounts list and load dummy
+ for (let i=accountsList.getRowCount()-1; i>=0; i--) {
+ accountsList.getItemAtIndex(i).remove();
+ }
+ document.getElementById("tbSyncAccounts.contentFrame").setAttribute("src", "chrome://tbsync/content/manager/noaccounts.xhtml");
+ }
+ },
+
+ updateAddMenuEntry: function (provider) {
+ let isDefault = TbSync.providers.defaultProviders.hasOwnProperty(provider);
+ let isInstalled = TbSync.providers.loadedProviders.hasOwnProperty(provider);
+
+ let entry = document.getElementById("addMenuEntry_" + provider);
+ if (entry === null) {
+ //add basic menu entry
+ let newItem = window.document.createXULElement("menuitem");
+ newItem.setAttribute("id", "addMenuEntry_" + provider);
+ newItem.setAttribute("value", provider);
+ newItem.setAttribute("class", "menuitem-iconic");
+ newItem.addEventListener("click", function () {tbSyncAccounts.addAccountAction(provider)}, false);
+ newItem.setAttribute("hidden", true);
+ entry = window.document.getElementById("accountActionsAddAccount").appendChild(newItem);
+ }
+
+ //Update label, icon and hidden according to isDefault and isInstalled
+ if (isInstalled) {
+ entry.setAttribute("label", TbSync.providers[provider].Base.getProviderName());
+ entry.setAttribute("image", TbSync.providers[provider].Base.getProviderIcon(16));
+ entry.setAttribute("hidden", false);
+ } else if (isDefault) {
+ entry.setAttribute("label", TbSync.providers.defaultProviders[provider].name);
+ entry.setAttribute("image", "chrome://tbsync/content/skin/provider16.png");
+ entry.setAttribute("hidden", false);
+ } else {
+ entry.setAttribute("hidden", true);
+ }
+ },
+
+ getSelectedAccount: function () {
+ let accountsList = document.getElementById("tbSyncAccounts.accounts");
+ if (accountsList.selectedItem !== null && !isNaN(accountsList.selectedItem.value)) {
+ //get id of selected account from value of selectedItem
+ return accountsList.selectedItem.value;
+ }
+ return null;
+ },
+
+ //load the pref page for the currently selected account (triggered by onSelect)
+ loadSelectedAccount: function () {
+ let selectedAccount = this.getSelectedAccount();
+
+ if (selectedAccount !== null) { //account id could be 0, so need to check for null explicitly
+ let provider = TbSync.db.getAccountProperty(selectedAccount, "provider");
+ if (tbSyncAccounts.hasInstalledProvider(selectedAccount)) {
+ document.getElementById("tbSyncAccounts.contentFrame").setAttribute("src", "chrome://tbsync/content/manager/editAccount.xhtml?provider="+provider+"&id=" + selectedAccount);
+ } else {
+ document.getElementById("tbSyncAccounts.contentFrame").setAttribute("src", "chrome://tbsync/content/manager/missingProvider.xhtml?provider="+provider);
+ }
+ }
+ },
+
+
+
+
+ addAccountAction: function (provider) {
+ let isDefault = TbSync.providers.defaultProviders.hasOwnProperty(provider);
+ let isInstalled = TbSync.providers.loadedProviders.hasOwnProperty(provider);
+
+ if (isInstalled) {
+ tbSyncAccounts.addAccount(provider);
+ } else if (isDefault) {
+ tbSyncAccounts.installProvider(provider);
+ }
+ },
+
+ addAccount: function (provider) {
+ TbSync.providers.loadedProviders[provider].createAccountWindow = window.openDialog(TbSync.providers[provider].Base.getCreateAccountWindowUrl(), "TbSyncNewAccountWindow", "centerscreen,resizable=no");
+ TbSync.providers.loadedProviders[provider].createAccountWindow.addEventListener("unload", function () { TbSync.manager.prefWindowObj.focus(); });
+ },
+
+ installProvider: function (provider) {
+ for (let i=0; i<TbSync.AccountManagerTabs.length; i++) {
+ TbSync.manager.prefWindowObj.document.getElementById("tbSyncAccountManager.t" + i).setAttribute("active","false");
+ }
+ TbSync.manager.prefWindowObj.document.getElementById("tbSyncAccountManager.installProvider").hidden=false;
+ TbSync.manager.prefWindowObj.document.getElementById("tbSyncAccountManager.installProvider").setAttribute("active","true");
+ TbSync.manager.prefWindowObj.document.getElementById("tbSyncAccountManager.contentWindow").setAttribute("src", "chrome://tbsync/content/manager/installProvider.xhtml?provider="+provider);
+ },
+
+};
diff --git a/content/manager/accounts.xhtml b/content/manager/accounts.xhtml
new file mode 100644
index 0000000..1059e02
--- /dev/null
+++ b/content/manager/accounts.xhtml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="tbSyncAccounts.onload();"
+ onunload="tbSyncAccounts.onunload();"
+ title="TbSync Account Settings" >
+
+ <popupset>
+ <menupopup id="tbsync.accountmanger.ContextMenu" onpopupshowing="tbSyncAccounts.updateDropdown('contextMenu');">
+ <menuitem id="contextMenuRetryConnectAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/connect16.png"
+ label="__TBSYNCMSG_manager.RetryConnectAccount__"
+ oncommand="tbSyncAccounts.synchronizeAccount();"/>
+ <menuitem id="contextMenuSyncAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/sync16.png"
+ label="__TBSYNCMSG_manager.SynchronizeAccount__"
+ oncommand="tbSyncAccounts.synchronizeAccount();"/>
+ <menuitem id="contextMenuEnableAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/connect16.png"
+ label="__TBSYNCMSG_manager.EnableAccount__"
+ oncommand="tbSyncAccounts.toggleEnableState();"/>
+ <menuitem id="contextMenuDisableAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/disabled16.png"
+ label="__TBSYNCMSG_manager.DisableAccount__"
+ oncommand="tbSyncAccounts.toggleEnableState();"/>
+ <menuitem id="contextMenuDeleteAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/del16.png"
+ label="__TBSYNCMSG_manager.DeleteAccount__"
+ oncommand="tbSyncAccounts.deleteAccount();"/>
+ </menupopup>
+ </popupset>
+
+ <hbox flex="1">
+ <vbox width="200">
+ <richlistbox
+ id="tbSyncAccounts.accounts"
+ flex="1"
+ style="margin: 0 1px"
+ seltype="single"
+ context="tbsync.accountmanger.ContextMenu"
+ onkeypress="if (event.keyCode == 46) {tbSyncAccounts.deleteAccount();}"
+ onselect="tbSyncAccounts.loadSelectedAccount();">
+ <listheader style="border-bottom: 1px solid lightgrey;">
+ <treecol style="font-weight:bold;" label="" width="26" flex="0" />
+ <treecol style="font-weight:bold;" label="__TBSYNCMSG_manager.accounts__" flex="1" />
+ <treecol style="font-weight:bold;text-align:right;" label="__TBSYNCMSG_manager.status__" flex="0" />
+ </listheader>
+ </richlistbox>
+ <hbox style="margin:1ex 0 0 0">
+ <vbox style="margin:0" flex="1">
+ <button
+ id="tbSyncAccounts.btnAccountActions"
+ label="__TBSYNCMSG_manager.AccountActions__"
+ style="margin:0"
+ type="menu">
+ <menupopup id="accountActionsDropdown" onpopupshowing="tbSyncAccounts.updateDropdown('accountActions');">
+ <menu
+ class="menu-iconic"
+ image="chrome://tbsync/content/skin/add16.png"
+ label="__TBSYNCMSG_manager.AddAccount__">
+ <menupopup id="accountActionsAddAccount" />
+ </menu>
+ <menuitem id="accountActionsSyncAllAccounts"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/sync16.png"
+ label="__TBSYNCMSG_manager.SyncAll__"
+ oncommand="TbSync.core.syncAllAccounts();"/>
+ <menuitem id="accountActionsShowEventLog"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/warning16.png"
+ label="__TBSYNCMSG_manager.ShowEventLog__"
+ oncommand="TbSync.eventlog.open()"/>
+ <menuseparator id="accountActionsSeparator"/>
+ <menuitem id="accountActionsDeleteAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/del16.png"
+ label="__TBSYNCMSG_manager.DeleteAccount__"
+ oncommand="tbSyncAccounts.deleteAccount();"/>
+ <menuitem id="accountActionsDisableAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/disabled16.png"
+ label="__TBSYNCMSG_manager.DisableAccount__"
+ oncommand="tbSyncAccounts.toggleEnableState();"/>
+ <menuitem id="accountActionsEnableAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/connect16.png"
+ label="__TBSYNCMSG_manager.EnableAccount__"
+ oncommand="tbSyncAccounts.toggleEnableState();"/>
+ <menuitem id="accountActionsSyncAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/sync16.png"
+ label="__TBSYNCMSG_manager.SynchronizeAccount__"
+ oncommand="tbSyncAccounts.synchronizeAccount();"/>
+ <menuitem id="accountActionsRetryConnectAccount"
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/connect16.png"
+ label="__TBSYNCMSG_manager.RetryConnectAccount__"
+ oncommand="tbSyncAccounts.synchronizeAccount();"/>
+ </menupopup>
+ </button>
+ </vbox>
+ </hbox>
+ </vbox>
+ <browser id="tbSyncAccounts.contentFrame" type="chrome" src="" disablehistory="true" flex="1" style="margin-left:12px;"/>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accounts.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/addonoptions.xhtml b/content/manager/addonoptions.xhtml
new file mode 100644
index 0000000..8ee9c10
--- /dev/null
+++ b/content/manager/addonoptions.xhtml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="__TBSYNCMSG_manager.title__"
+ onload="tbSyncAccountManager.onloadoptions();"
+ onunload="tbSyncAccountManager.onunloadoptions();"
+ width="180" height="80" >
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/catman.xhtml b/content/manager/catman.xhtml
new file mode 100644
index 0000000..ce78783
--- /dev/null
+++ b/content/manager/catman.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Category Manager" >
+
+ <hbox flex="1" id="mainframe">
+ <vbox flex="1">
+ <html:p>
+ __TBSYNCMSG_manager.catman.text__
+ </html:p>
+
+ <html:p onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="TbSync.manager.openLink('https://addons.thunderbird.net/addon/categorymanager/');" style="color:blue;text-decoration: underline;padding-left:1em;">
+ https://addons.thunderbird.net/addon/categorymanager/
+ </html:p>
+ </vbox>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/editAccount.js b/content/manager/editAccount.js
new file mode 100644
index 0000000..5beca01
--- /dev/null
+++ b/content/manager/editAccount.js
@@ -0,0 +1,397 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { OS } =ChromeUtils.import("resource://gre/modules/osfile.jsm");
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncAccountSettings = {
+
+ accountID: null,
+ provider: null,
+ settings: null,
+ updateTimer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
+
+ updateFolderListObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //only run if is request for this account and main frame is visible
+ let accountID = aData;
+ if (accountID == tbSyncAccountSettings.accountID && !document.getElementById('tbsync.accountsettings.frame').hidden) {
+ //make sure, folderlist is visible, otherwise our updates will be discarded (may cause errors)
+ tbSyncAccountSettings.updateFolderList();
+ tbSyncAccountSettings.updateGui();
+ }
+ }
+ },
+
+ reloadAccountSettingObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //only run if is request for this account and main frame is visible
+ let data = JSON.parse(aData);
+ if (data.accountID == tbSyncAccountSettings.accountID && !document.getElementById('tbsync.accountsettings.frame').hidden) {
+ tbSyncAccountSettings.reloadSetting(data.setting);
+ }
+ }
+ },
+
+ updateGuiObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //only run if is request for this account and main frame is visible
+ let accountID = aData;
+ if (accountID == tbSyncAccountSettings.accountID && !document.getElementById('tbsync.accountsettings.frame').hidden) {
+ tbSyncAccountSettings.updateGui();
+ }
+ }
+ },
+
+ updateSyncstateObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //only run if is request for this account and main frame is visible
+ let accountID = aData;
+ if (accountID == tbSyncAccountSettings.accountID && !document.getElementById('tbsync.accountsettings.frame').hidden) {
+ let syncstate = TbSync.core.getSyncDataObject(accountID).getSyncState().state;
+ if (syncstate == "accountdone") {
+ tbSyncAccountSettings.updateGui();
+ } else {
+ tbSyncAccountSettings.updateSyncstate();
+ }
+ }
+ }
+ },
+
+
+ onInject: function (window) {
+ //called when the provider is injecting, call its onload function and pass in his AccountData Obj
+ if (tbSyncEditAccountOverlay && tbSyncEditAccountOverlay.hasOwnProperty("onload")) {
+ tbSyncEditAccountOverlay.onload(window, new TbSync.AccountData(tbSyncAccountSettings.accountID));
+ }
+ },
+
+ onload: function () {
+ //load observers
+ Services.obs.addObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.observer.manager.updateFolderList", false);
+ Services.obs.addObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.observer.manager.updateAccountSettingsGui", false);
+ Services.obs.addObserver(tbSyncAccountSettings.reloadAccountSettingObserver, "tbsync.observer.manager.reloadAccountSetting", false);
+ Services.obs.addObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.observer.manager.updateSyncstate", false);
+ //get the selected account from the loaded URI
+ tbSyncAccountSettings.accountID = window.location.toString().split("id=")[1];
+ tbSyncAccountSettings.accountData = new TbSync.AccountData(tbSyncAccountSettings.accountID);
+
+ //get information for that acount
+ tbSyncAccountSettings.provider = TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, "provider");
+ tbSyncAccountSettings.settings = Object.keys(TbSync.providers.getDefaultAccountEntries(tbSyncAccountSettings.provider)).sort();
+
+ //add header to folderlist
+ let header = TbSync.providers[tbSyncAccountSettings.provider].folderList.getHeader();
+ let folderlistHeader = window.document.getElementById('tbsync.accountsettings.folderlist.header');
+ for (let h=0; h < header.length; h++) {
+ let listheader = window.document.createXULElement("treecol");
+ for (let a in header[h]) {
+ if (header[h].hasOwnProperty(a)) {
+ listheader.setAttribute(a, header[h][a]);
+ }
+ }
+ folderlistHeader.appendChild(listheader);
+ }
+
+ //load overlays from the provider (if any)
+ TbSync.messenger.overlayManager.injectAllOverlays(window, "chrome://tbsync/content/manager/editAccount.xhtml?provider=" + tbSyncAccountSettings.provider);
+ tbSyncAccountSettings.loadSettings();
+
+ //done, folderlist must be updated while visible
+ document.getElementById('tbsync.accountsettings.frame').hidden = false;
+ tbSyncAccountSettings.updateFolderList();
+
+ if (OS.Constants.Sys.Name == "Darwin") { //we might need to find a way to detect MacOS like styling, other themes move the header bar into the tabpanel as well
+ document.getElementById('manager.tabpanels').style["padding-top"] = "3ex";
+ }
+ },
+
+
+ onunload: function () {
+ tbSyncAccountSettings.updateTimer.cancel();
+ if (!document.getElementById('tbsync.accountsettings.frame').hidden) {
+ Services.obs.removeObserver(tbSyncAccountSettings.updateFolderListObserver, "tbsync.observer.manager.updateFolderList");
+ Services.obs.removeObserver(tbSyncAccountSettings.updateGuiObserver, "tbsync.observer.manager.updateAccountSettingsGui");
+ Services.obs.removeObserver(tbSyncAccountSettings.reloadAccountSettingObserver, "tbsync.observer.manager.reloadAccountSetting");
+ Services.obs.removeObserver(tbSyncAccountSettings.updateSyncstateObserver, "tbsync.observer.manager.updateSyncstate");
+ }
+ },
+
+
+ folderListVisible: function () {
+ let box = document.getElementById('tbsync.accountsettings.folderlist').getBoundingClientRect();
+ let visible = box.width && box.height;
+ return visible;
+ },
+
+
+ reloadSetting: function (setting) {
+ let pref = document.getElementById("tbsync.accountsettings.pref." + setting);
+ let label = document.getElementById("tbsync.accountsettings.label." + setting);
+
+ if (pref) {
+ //is this a checkbox?
+ if (pref.tagName == "checkbox") {
+ //BOOL
+ if (TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, setting)) pref.setAttribute("checked", true);
+ else pref.setAttribute("checked", false);
+ } else {
+ //Not BOOL
+ pref.value = TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, setting);
+ }
+ }
+ },
+
+
+ /**
+ * Run through all defined TbSync settings and if there is a corresponding
+ * field in the settings dialog, fill it with the stored value.
+ */
+ loadSettings: function () {
+ for (let i=0; i < tbSyncAccountSettings.settings.length; i++) {
+ let pref = document.getElementById("tbsync.accountsettings.pref." + tbSyncAccountSettings.settings[i]);
+ let label = document.getElementById("tbsync.accountsettings.label." + tbSyncAccountSettings.settings[i]);
+
+ if (pref) {
+ //is this a checkbox?
+ let event = "blur";
+ if (pref.tagName == "checkbox") {
+ //BOOL
+ if (TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, tbSyncAccountSettings.settings[i])) pref.setAttribute("checked", true);
+ else pref.setAttribute("checked", false);
+ event = "command";
+ } else {
+ //Not BOOL
+ if (pref.tagName == "menulist") {
+ pref.value = TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, tbSyncAccountSettings.settings[i]);
+ event = "command";
+ } else {
+ pref.setAttribute("value", TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, tbSyncAccountSettings.settings[i]));
+ }
+ }
+
+ pref.addEventListener(event, function() {tbSyncAccountSettings.instantSaveSetting(this)});
+ }
+ }
+
+ tbSyncAccountSettings.updateGui();
+ },
+
+ updateGui: function () {
+ let status = TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, "status");
+
+ let isConnected = TbSync.core.isConnected(tbSyncAccountSettings.accountID);
+ let isEnabled = TbSync.core.isEnabled(tbSyncAccountSettings.accountID);
+ let isSyncing = TbSync.core.isSyncing(tbSyncAccountSettings.accountID);
+
+ { //disable settings if connected or syncing
+ let items = document.getElementsByClassName("lockIfConnected");
+ for (let i=0; i < items.length; i++) {
+ if (isConnected || isSyncing || items[i].getAttribute("alwaysDisabled") == "true") {
+ items[i].setAttribute("disabled", true);
+ items[i].style["color"] = "darkgrey";
+ } else {
+ items[i].removeAttribute("disabled");
+ items[i].style["color"] = "black";
+ }
+ }
+ }
+
+ document.getElementById('tbsync.accountsettings.connectbtn.container').hidden = !(isEnabled && !isConnected && !isSyncing);
+ //currently we use a fixed button which is hidden during sync
+ //document.getElementById('tbsync.accountsettings.connectbtn').label = TbSync.getString("manager." + (isSyncing ? "connecting" : "tryagain"));
+
+ { //show elements if connected (this also hides/unhides the folderlist)
+ let items = document.getElementsByClassName("showIfConnected");
+ for (let i=0; i < items.length; i++) {
+ items[i].hidden = !isConnected;
+ }
+ }
+
+ { //show elements if enabled
+ let items = document.getElementsByClassName("showIfEnabled");
+ for (let i=0; i < items.length; i++) {
+ items[i].hidden = !isEnabled;
+ }
+ }
+
+ document.getElementById('tbsync.accountsettings.enabled').checked = isEnabled;
+ document.getElementById('tbsync.accountsettings.enabled').disabled = isSyncing;
+ document.getElementById('tbsync.accountsettings.folderlist').disabled = isSyncing;
+ document.getElementById('tbsync.accountsettings.syncbtn').disabled = isSyncing;
+ document.getElementById('tbsync.accountsettings.connectbtn').disabled = isSyncing;
+
+ tbSyncAccountSettings.updateSyncstate();
+
+ //change color of syncstate according to status
+ let showEventLogButton = false;
+ switch (status) {
+ case "success":
+ case "disabled":
+ case "syncing":
+ document.getElementById("syncstate").removeAttribute("style");
+ break;
+
+ case "notsyncronized":
+ document.getElementById("syncstate").setAttribute("style","color: red");
+ break;
+
+ default:
+ document.getElementById("syncstate").setAttribute("style","color: red");
+ showEventLogButton = TbSync.eventlog.get(tbSyncAccountSettings.accountID).length > 0;
+ }
+ document.getElementById('tbsync.accountsettings.eventlogbtn').hidden = !showEventLogButton;
+ },
+
+ updateSyncstate: function () {
+ tbSyncAccountSettings.updateTimer.cancel();
+
+ // if this account is beeing synced, display syncstate, otherwise print status
+ let status = TbSync.db.getAccountProperty(tbSyncAccountSettings.accountID, "status");
+ let isSyncing = TbSync.core.isSyncing(tbSyncAccountSettings.accountID);
+ let isConnected = TbSync.core.isConnected(tbSyncAccountSettings.accountID);
+ let isEnabled = TbSync.core.isEnabled(tbSyncAccountSettings.accountID);
+ let syncdata = TbSync.core.getSyncDataObject(tbSyncAccountSettings.accountID);
+
+ if (isSyncing) {
+ let accounts = TbSync.db.getAccounts().data;
+
+ let s = syncdata.getSyncState();
+ let syncstate = s.state;
+ let synctime = s.timestamp;
+
+ let msg = TbSync.getString("syncstate." + syncstate, tbSyncAccountSettings.provider);
+
+ if (syncstate.split(".")[0] == "send") {
+ // append timeout countdown
+ let diff = Date.now() - synctime;
+ if (diff > 2000) msg = msg + " (" + Math.round((TbSync.providers[tbSyncAccountSettings.provider].Base.getConnectionTimeout(tbSyncAccountSettings.accountData) - diff)/1000) + "s)";
+ // re-schedule update, if this is a waiting syncstate
+ tbSyncAccountSettings.updateTimer.init(tbSyncAccountSettings.updateSyncstate, 1000, 0);
+ }
+ document.getElementById("syncstate").textContent = msg;
+ } else {
+ let localized = TbSync.getString("status." + (isEnabled ? status : "disabled"), tbSyncAccountSettings.provider);
+ document.getElementById("syncstate").textContent = localized;
+ }
+
+
+ if (tbSyncAccountSettings.folderListVisible()) {
+ //update syncstates of folders in folderlist, if visible - remove obsolete entries while we are here
+ let folderData = TbSync.providers[tbSyncAccountSettings.provider].Base.getSortedFolders(tbSyncAccountSettings.accountData);
+ let folderList = document.getElementById("tbsync.accountsettings.folderlist");
+
+ for (let i=folderList.getRowCount()-1; i>=0; i--) {
+ let item = folderList.getItemAtIndex(i);
+ if (folderData.filter(f => f.folderID == item.folderData.folderID).length == 0) {
+ item.remove();
+ } else {
+ TbSync.providers[tbSyncAccountSettings.provider].folderList.updateRow(document, item, item.folderData);
+ }
+ }
+ }
+ },
+
+ updateFolderList: function () {
+ //get updated list of folderIDs
+ let folderData = TbSync.providers[tbSyncAccountSettings.provider].Base.getSortedFolders(tbSyncAccountSettings.accountData);
+
+ //remove entries from folderlist, which no longer exists and build reference array with current elements
+ let folderList = document.getElementById("tbsync.accountsettings.folderlist");
+ folderList.hidden=true;
+
+ let foldersElements = {};
+ for (let i=folderList.getRowCount()-1; i>=0; i--) {
+ if (folderData.filter(f => f.folderID == folderList.getItemAtIndex(i).folderData.folderID).length == 0) {
+ folderList.getItemAtIndex(i).remove();
+ } else {
+ foldersElements[folderList.getItemAtIndex(i).folderData.folderID] = folderList.getItemAtIndex(i);
+ }
+ }
+
+ //update folderlist
+ for (let i=0; i < folderData.length; i++) {
+ let nextItem = null;
+
+ //if this entry does not exist, create it
+ if (foldersElements.hasOwnProperty(folderData[i].folderID)) {
+ //get reference to current element
+ nextItem = foldersElements[folderData[i].folderID];
+ } else {
+ //add new entry, attach FolderData of this folder as folderData
+ nextItem = document.createXULElement("richlistitem");
+ nextItem.folderData = folderData[i];
+
+ //add row
+ nextItem.appendChild(TbSync.providers[tbSyncAccountSettings.provider].folderList.getRow(document, folderData[i]));
+ }
+
+ //add/move row and update its content
+ let addedItem = folderList.appendChild(nextItem);
+ TbSync.providers[tbSyncAccountSettings.provider].folderList.updateRow(document, addedItem, folderData[i]);
+
+ //ensureElementIsVisible also forces internal update of rowCount, which sometimes is not updated automatically upon appendChild
+ folderList.ensureElementIsVisible(addedItem);
+ }
+ folderList.hidden = false;
+ },
+
+
+
+
+
+ instantSaveSetting: function (field) {
+ let setting = field.id.replace("tbsync.accountsettings.pref.","");
+ let value = "";
+
+ if (field.tagName == "checkbox") {
+ if (field.checked) value = true;
+ else value = false;
+ } else {
+ value = field.value;
+ }
+ TbSync.db.setAccountProperty(tbSyncAccountSettings.accountID, setting, value);
+
+ if (setting == "accountname") {
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateAccountName", tbSyncAccountSettings.accountID + ":" + field.value);
+ }
+ TbSync.db.saveAccounts(); //write modified accounts to disk
+ },
+
+ toggleEnableState: function (element) {
+ if (!TbSync.core.isConnected(tbSyncAccountSettings.accountID)) {
+ //if not connected, we can toggle without prompt
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.toggleEnableState", tbSyncAccountSettings.accountID);
+ return;
+ }
+
+ if (window.confirm(TbSync.getString("prompt.Disable"))) {
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.toggleEnableState", tbSyncAccountSettings.accountID);
+ } else {
+ //invalid, toggle checkbox back
+ element.setAttribute("checked", true);
+ }
+ },
+
+
+ onFolderListContextMenuShowing: function () {
+ let folderList = document.getElementById("tbsync.accountsettings.folderlist");
+ let aFolderIsSelected = (!folderList.disabled && folderList.selectedItem !== null && folderList.selectedItem.value !== undefined);
+ let menupopup = document.getElementById("tbsync.accountsettings.FolderListContextMenu");
+
+ if (aFolderIsSelected) {
+ TbSync.providers[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(window, folderList.selectedItem.folderData);
+ } else {
+ TbSync.providers[tbSyncAccountSettings.provider].folderList.onContextMenuShowing(window, null);
+ }
+ },
+
+};
diff --git a/content/manager/editAccount.xhtml b/content/manager/editAccount.xhtml
new file mode 100644
index 0000000..b09761b
--- /dev/null
+++ b/content/manager/editAccount.xhtml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window id="tbsync.accountsettings"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ omscope="tbSyncAccountSettings"
+ onload="tbSyncAccountSettings.onload()"
+ onunload="tbSyncAccountSettings.onunload()"
+ title="" >
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/editAccount.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+
+ <popupset>
+ <menupopup
+ id="tbsync.accountsettings.FolderListContextMenu"
+ folderID=""
+ onpopupshowing="tbSyncAccountSettings.onFolderListContextMenuShowing();">
+ <menuitem
+ class="menuitem-iconic"
+ image="chrome://tbsync/content/skin/warning16.png"
+ label="__TBSYNCMSG_manager.ShowEventLog__"
+ oncommand="TbSync.eventlog.open(tbSyncAccountSettings.accountID, this.parentNode.getAttribute('folderID'));"/>
+ </menupopup>
+ </popupset>
+
+ <tabbox id="tbsync.accountsettings.frame" hidden="true" flex="1">
+
+ <tabs id="manager.tabs" orient="horizontal" value="">
+ <tab id="manager.tabs.status" label="__TBSYNCMSG_manager.tabs.status__" />
+ </tabs>
+
+ <tabpanels flex="1" id="manager.tabpanels" style="margin:0;padding:1ex;">
+ <tabpanel id="manager.tabpanels.status" orient="vertical"><!-- STATUS -->
+ <vbox flex="1">
+ <label class="header" style="margin-left:0; margin-bottom:1ex;" value="__TBSYNCMSG_manager.tabs.status.general__" />
+ <checkbox id="tbsync.accountsettings.enabled" oncommand="tbSyncAccountSettings.toggleEnableState(this);" label="__TBSYNCMSG_manager.tabs.status.enableThisAccount__" />
+
+ <vbox class="showIfEnabled">
+ <hbox flex="1">
+ <vbox flex="1">
+ <label class="header" style="margin-left:0; margin-bottom:1ex; margin-top:2ex;" value="__TBSYNCMSG_manager.status__" />
+ <description flex="1" id="syncstate"></description>
+ </vbox>
+ <vbox flex="0">
+ <label class="header" style="margin-left:0; margin-bottom:1ex; margin-top:1ex; visibility: hidden" value="nix" />
+ <button id="tbsync.accountsettings.eventlogbtn" label="__TBSYNCMSG_manager.ShowEventLog__" oncommand="TbSync.eventlog.open()" />
+ </vbox>
+ </hbox>
+ </vbox>
+
+ <vbox flex="1">
+ <vbox class="showIfConnected" flex="1">
+ <label style="margin-left:0; margin-bottom: 1ex; margin-top: 2ex" class="header" value="__TBSYNCMSG_manager.tabs.status.resources__"/>
+ <description>__TBSYNCMSG_manager.tabs.status.resources.intro__</description>
+ <richlistbox
+ id="tbsync.accountsettings.folderlist"
+ flex="1"
+ style="margin: 0 1px 1px 1ex;padding:0;"
+ context="tbsync.accountsettings.FolderListContextMenu"
+ seltype="single">
+ <listheader id="tbsync.accountsettings.folderlist.header" style="border-bottom: 1px solid lightgrey;">
+ </listheader>
+ </richlistbox>
+ <vbox flex="0" style="margin:1ex 0 0 0;">
+ <hbox flex="1" align="center" pack="end">
+ <description style="text-align:right" flex="1" control="tbsync.accountsettings.pref.autosync" tooltiptext="__TBSYNCMSG_manager.tabs.status.never__">__TBSYNCMSG_manager.tabs.status.autotime__</description>
+ <html:input style="width:50px;margin-bottom:0; margin-top:0" id="tbsync.accountsettings.pref.autosync" tooltiptext="__TBSYNCMSG_manager.tabs.status.never__" />
+ <button id="tbsync.accountsettings.syncbtn" style="margin-right:0; margin-bottom:0; margin-top:0; padding: 0 1ex;" label="__TBSYNCMSG_manager.tabs.status.sync__" oncommand="TbSync.core.syncAccount(tbSyncAccountSettings.accountID)" />
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+
+ <hbox id="tbsync.accountsettings.connectbtn.container" flex="0" style="margin:1ex 0 0 0;" pack="end">
+ <button id="tbsync.accountsettings.connectbtn" style="margin-right:0; margin-bottom:0; margin-top:0; padding: 0 1ex;" label="__TBSYNCMSG_manager.tabs.status.tryagain__" oncommand="TbSync.core.syncAccount(tbSyncAccountSettings.accountID)" />
+ </hbox>
+
+ </vbox>
+ </tabpanel>
+ </tabpanels>
+
+ </tabbox>
+
+</window>
diff --git a/content/manager/eventlog/eventlog.js b/content/manager/eventlog/eventlog.js
new file mode 100644
index 0000000..eadc77e
--- /dev/null
+++ b/content/manager/eventlog/eventlog.js
@@ -0,0 +1,159 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncEventLog = {
+
+ onload: function () {
+ Services.obs.addObserver(tbSyncEventLog.updateEventLog, "tbsync.observer.eventlog.update", false);
+
+ let eventlog = document.getElementById('tbsync.eventlog');
+ eventlog.hidden = true;
+
+ //init list
+ let events = TbSync.eventlog.get();
+ for (let i=0; i < events.length; i++) {
+ let item = tbSyncEventLog.addLogEntry(events[i]);
+ eventlog.appendChild(item);
+ }
+
+ eventlog.hidden = false;
+ eventlog.ensureIndexIsVisible(eventlog.getRowCount()-1);
+ document.documentElement.getButton("extra1").onclick = tbSyncEventLog.onclear;
+ document.documentElement.getButton("extra1").label = TbSync.getString("eventlog.clear");
+ document.documentElement.getButton("cancel").label = TbSync.getString("eventlog.close");
+ },
+
+ onclear: function () {
+ TbSync.eventlog.clear();
+
+ let eventlog = document.getElementById('tbsync.eventlog');
+ eventlog.hidden = true;
+
+ for (let i=eventlog.getRowCount()-1; i>=0; i--) {
+ eventlog.getItemAtIndex(i).remove();
+ }
+
+ eventlog.hidden = false;
+ },
+
+ onunload: function () {
+ Services.obs.removeObserver(tbSyncEventLog.updateEventLog, "tbsync.observer.eventlog.update");
+ },
+
+ updateEventLog: {
+ observe: function (aSubject, aTopic, aData) {
+ let events = TbSync.eventlog.get();
+ if (events.length > 0) {
+ let eventlog = document.getElementById('tbsync.eventlog');
+ eventlog.hidden = true;
+
+ let item = tbSyncEventLog.addLogEntry(events[events.length-1]);
+ eventlog.appendChild(item);
+
+ eventlog.hidden = false;
+ eventlog.ensureIndexIsVisible(eventlog.getRowCount()-1);
+ }
+ }
+ },
+
+
+ addLogEntry: function (entry) {
+
+ //left column
+ let leftColumn = document.createXULElement("vbox");
+ leftColumn.setAttribute("width", "24");
+
+ let image = document.createXULElement("image");
+ let src = entry.type.endsWith("_rerun") ? "sync" : entry.type;
+ image.setAttribute("src", "chrome://tbsync/content/skin/" + src + "16.png");
+ image.setAttribute("style", "margin:4px 4px 4px 4px;");
+ leftColumn.appendChild(image);
+
+ //right column
+ let rightColumn = document.createXULElement("vbox");
+ rightColumn.setAttribute("flex","1");
+
+ let d = new Date(entry.timestamp);
+ let timestamp = document.createXULElement("description");
+ timestamp.setAttribute("flex", "1");
+ timestamp.setAttribute("class", "header");
+ timestamp.textContent = d.toLocaleTimeString();
+ rightColumn.appendChild(timestamp);
+
+ let hBox = document.createXULElement("hbox");
+ hBox.flex = "1";
+ let vBoxLeft = document.createXULElement("vbox");
+ vBoxLeft.flex = "1";
+ let vBoxRight = document.createXULElement("vbox");
+
+ let msg = document.createXULElement("description");
+ msg.setAttribute("flex", "1");
+ msg.setAttribute("class", "header");
+ msg.textContent = entry.message;
+ vBoxLeft.appendChild(msg);
+
+ if (entry.link) {
+ let link = document.createXULElement("button");
+ link.setAttribute("label", TbSync.getString("manager.help"));
+ link.setAttribute("oncommand", "TbSync.manager.openLink('" + entry.link + "')");
+ vBoxRight.appendChild(link);
+ }
+
+ hBox.appendChild(vBoxLeft);
+ hBox.appendChild(vBoxRight);
+ rightColumn.appendChild(hBox);
+
+ if (entry.accountname || entry.provider) {
+ let account = document.createXULElement("label");
+ if (entry.accountname) account.setAttribute("value", "Account: " + entry.accountname + (entry.provider ? " (" + entry.provider.toUpperCase() + ")" : ""));
+ else account.setAttribute("value", "Provider: " + entry.provider.toUpperCase());
+ rightColumn.appendChild(account);
+ }
+
+ if (entry.foldername) {
+ let folder = document.createXULElement("label");
+ folder.setAttribute("value", "Resource: " + entry.foldername);
+ rightColumn.appendChild(folder);
+ }
+
+ if (entry.details) {
+ let lines = entry.details.split("\n");
+ let line = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
+ line.setAttribute("readonly", "true");
+ line.setAttribute("wrap", "off");
+ line.setAttribute("rows", lines.length);
+ line.setAttribute("style", "font-family: monospace; font-size: 10px;");
+ line.setAttribute("class", "plain");
+ line.value = entry.details.trim();
+
+ let container = document.createXULElement("vbox");
+ container.setAttribute("style", "margin-left:1ex;margin-top:1ex;");
+ container.appendChild(line);
+
+ rightColumn.appendChild(container);
+ }
+
+ //columns
+ let columns = document.createXULElement("hbox");
+ columns.setAttribute("flex", "1");
+ columns.appendChild(leftColumn);
+ columns.appendChild(rightColumn);
+
+ //richlistitem
+ let richlistitem = document.createXULElement("richlistitem");
+ richlistitem.setAttribute("style", "padding:4px; border-bottom: 1px solid lightgrey;");
+ richlistitem.appendChild(columns);
+
+ return richlistitem;
+ },
+};
diff --git a/content/manager/eventlog/eventlog.xhtml b/content/manager/eventlog/eventlog.xhtml
new file mode 100644
index 0000000..ae4884a
--- /dev/null
+++ b/content/manager/eventlog/eventlog.xhtml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<dialog
+ width="600"
+ height="400"
+ title="__TBSYNCMSG_eventlog.title__"
+ buttons="cancel, extra1"
+ onload="tbSyncEventLog.onload();"
+ onunload="tbSyncEventLog.onunload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+
+ <script type="application/javascript" src="chrome://tbsync/content/manager/eventlog/eventlog.js"/>
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+
+ <vbox flex="1">
+ <richlistbox id="tbsync.eventlog" flex="1" seltype="single" disabled="true"/>
+ </vbox>
+</dialog>
diff --git a/content/manager/help.xhtml b/content/manager/help.xhtml
new file mode 100644
index 0000000..675bf53
--- /dev/null
+++ b/content/manager/help.xhtml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="tbSyncAccountManager.getLogPref();"
+ title="Help" >
+
+ <hbox flex="1" id="mainframe">
+ <vbox flex="1">
+
+ <html:p>
+ <html:b>__TBSYNCMSG_manager.help.needhelp__</html:b><html:br/><html:br/>
+ __TBSYNCMSG_manager.help.wiki__
+ </html:p>
+
+ <html:p onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="TbSync.manager.openTranslatedLink('https://github.com/jobisoft/TbSync/wiki');" style="color:blue;text-decoration: underline;padding-left:1em;margin:0 0 24px 0">
+ https://github.com/jobisoft/TbSync/wiki
+ </html:p>
+
+ <html:p>
+ <html:b>__TBSYNCMSG_manager.help.foundabug__</html:b><html:br/><html:br/>
+ <html:span>__TBSYNCMSG_manager.help.fixit__</html:span>
+ </html:p>
+
+ <hbox align="center">
+ <label value="__TBSYNCMSG_manager.help.debugmode__" />
+ <menulist flex="0" id="tbSyncAccountManager.logLevel" oncommand="tbSyncAccountManager.toggleLogPref();">
+ <menupopup>
+ <menuitem label="__TBSYNCMSG_manager.help.debuglevel.0__" value="0" />
+ <menuitem label="__TBSYNCMSG_manager.help.debuglevel.1__" value="1" />
+ <menuitem label="__TBSYNCMSG_manager.help.debuglevel.2__" value="2" />
+ <menuitem label="__TBSYNCMSG_manager.help.debuglevel.3__" value="3" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <html:p>
+ __TBSYNCMSG_manager.help.createbugreportinfo__
+ </html:p>
+
+ <hbox>
+ <button style="margin:0 0 0 1em; padding: 0 1ex;" label="__TBSYNCMSG_manager.help.createbugreport__" oncommand="TbSync.manager.openBugReportWizard();" />
+ <button style="margin:0 0 0 1em; padding: 0 1ex;" label="__TBSYNCMSG_manager.help.viewdebuglog__" oncommand="TbSync.manager.viewDebugLog();" />
+ </hbox>
+
+ <hbox flex="1">
+ </hbox>
+
+ </vbox>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/installProvider.xhtml b/content/manager/installProvider.xhtml
new file mode 100644
index 0000000..9ccdd75
--- /dev/null
+++ b/content/manager/installProvider.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="tbSyncManageProvider.prepInstall();"
+ title="Install additional synchronization provider for TbSync" >
+
+ <hbox flex="1" id="mainframe">
+ <vbox flex="1">
+
+ <hbox>
+ <html:p style="font-weight: bold" id="header"></html:p>
+ </hbox>
+
+ <html:p>
+ __TBSYNCMSG_manager.installprovider.link__
+ </html:p>
+
+ <html:p id="link" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="TbSync.manager.prefWindowObj.tbSyncAccountManager.selectTab(0); TbSync.manager.openTBtab(this.getAttribute('link'));" style="color:blue;text-decoration: underline;padding-left:1em;">
+ </html:p>
+
+ <html:p id="warning" style="font-weight: bold">
+ __TBSYNCMSG_manager.installprovider.warning__
+ </html:p>
+
+ </vbox>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/manageProvider.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/manageProvider.js b/content/manager/manageProvider.js
new file mode 100644
index 0000000..01b3be5
--- /dev/null
+++ b/content/manager/manageProvider.js
@@ -0,0 +1,40 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncManageProvider = {
+
+ prepInstall: function () {
+ let url = window.location.toString();
+ let provider = url.split("provider=")[1];
+ window.document.getElementById("header").textContent = TbSync.getString("installProvider.header::" + TbSync.providers.defaultProviders[provider].name);
+
+ window.document.getElementById("link").textContent = TbSync.providers.defaultProviders[provider].homepageUrl;
+ window.document.getElementById("link").setAttribute("link", TbSync.providers.defaultProviders[provider].homepageUrl);
+
+ window.document.getElementById("warning").hidden = TbSync.providers.defaultProviders[provider].homepageUrl.startsWith("https://addons.thunderbird.net");
+ },
+
+ prepMissing: function () {
+ let url = window.location.toString();
+ let provider = url.split("provider=")[1];
+
+ let e = window.document.getElementById("missing");
+ let v = e.textContent;
+ e.textContent = v.replace("##provider##", provider.toUpperCase());
+
+ if (TbSync.providers.defaultProviders.hasOwnProperty(provider)) {
+ window.document.getElementById("link").textContent = TbSync.providers.defaultProviders[provider].homepageUrl;
+ window.document.getElementById("link").setAttribute("link", TbSync.providers.defaultProviders[provider].homepageUrl);
+ }
+
+ },
+};
diff --git a/content/manager/manager.css b/content/manager/manager.css
new file mode 100644
index 0000000..c6359bf
--- /dev/null
+++ b/content/manager/manager.css
@@ -0,0 +1,38 @@
+#manager {
+ margin:8px 12px 12px 12px;
+}
+
+#tbtoolbar {
+ border:1px solid darkgrey;
+ background-color: #ffffff;
+ padding: 0;
+ margin:0 0 12px 0;
+}
+
+#tbtoolbar vbox {
+ margin: 0;
+ padding:6px 6px 2px 6px;
+ background-color: #ffffff;
+}
+
+#tbtoolbar vbox:hover {
+ background-color: #e0e8f6;
+}
+
+#tbtoolbar vbox[active="true"] {
+ background-color: #c1d2ee;
+}
+
+#mainframe {
+ padding:0 12px;
+ margin:0;
+}
+
+.row vbox {
+ width:140px;
+}
+
+iframe {
+ margin:0;
+ padding:0;
+}
diff --git a/content/manager/missingProvider.xhtml b/content/manager/missingProvider.xhtml
new file mode 100644
index 0000000..fbdc26f
--- /dev/null
+++ b/content/manager/missingProvider.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="tbSyncManageProvider.prepMissing();"
+ title="TbSync Account Settings" >
+
+ <hbox flex="1" pack="center">
+ <vbox flex="1" pack="center">
+ <description style="text-align:center" id="missing">__TBSYNCMSG_manager.missingprovider__</description>
+ <html:p id="link" onmouseover="this.style.cursor='pointer'" onmouseout="this.style.cursor='default'" onclick="TbSync.manager.openTBtab(this.getAttribute('link'));" style="color:blue; text-decoration: underline; text-align:center;"></html:p>
+ </vbox>
+
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/manageProvider.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/noaccounts.xhtml b/content/manager/noaccounts.xhtml
new file mode 100644
index 0000000..2437218
--- /dev/null
+++ b/content/manager/noaccounts.xhtml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="TbSync Account Settings" >
+
+ <hbox flex="1" pack="center">
+ <vbox flex="1" pack="center">
+ <description style="text-align:center">__TBSYNCMSG_manager.noaccounts__</description>
+ </vbox>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/manager/support-wizard/support-wizard.xhtml b/content/manager/support-wizard/support-wizard.xhtml
new file mode 100644
index 0000000..ce7ea27
--- /dev/null
+++ b/content/manager/support-wizard/support-wizard.xhtml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window
+ width="500"
+ height="600"
+ onload="tbSyncAccountManager.initSupportWizard();"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <linkset>
+ <html:link rel="localization" href="toolkit/global/wizard.ftl"/>
+ </linkset>
+
+ <wizard
+ id="SupportWizard"
+ title="__TBSYNCMSG_supportwizard.title__">
+
+ <wizardpage onFirstPage="true" label="__TBSYNCMSG_supportwizard.pagetitle__">
+ <label value="__TBSYNCMSG_supportwizard.label.faultycomponent__"/>
+ <menulist id="tbsync.supportwizard.faultycomponent.menulist">
+ <menupopup id="tbsync.supportwizard.faultycomponent">
+ <menuitem hidden="true" selected="true" label="__TBSYNCMSG_supportwizard.label.selectcomponent__" value="" />
+ <menuitem label="__TBSYNCMSG_manager.title__" value="core" />
+ </menupopup>
+ </menulist>
+
+ <label style="margin-top:1em" value="__TBSYNCMSG_supportwizard.label.summary__"/>
+ <html:input id="tbsync.supportwizard.summary" oninput="tbSyncAccountManager.checkSupportWizard()" />
+
+ <label style="margin-top:1em" value="__TBSYNCMSG_supportwizard.label.description__"/>
+ <html:textarea rows="15" style="margin-left: 1ex;" id="tbsync.supportwizard.description" />
+
+ <description style="margin-top:1em">
+ __TBSYNCMSG_supportwizard.footer__
+ </description>
+ </wizardpage>
+
+ </wizard>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+
+</window>
diff --git a/content/manager/supporter.xhtml b/content/manager/supporter.xhtml
new file mode 100644
index 0000000..a9bee40
--- /dev/null
+++ b/content/manager/supporter.xhtml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://tbsync/content/manager/manager.css" type="text/css"?>
+
+<window
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="tbSyncAccountManager.initCommunity();"
+ title="Community" >
+
+ <hbox flex="1" id="mainframe">
+ <vbox flex="1">
+ <html:p>
+ <html:b>__TBSYNCMSG_manager.supporter.contributors__</html:b>
+ <html:div id="listOfContributors" style="margin-top:10px">
+
+ <html:div provider="" style="width:225px; margin: 0 0 10px 0; float:left;">
+ <html:img
+ src=""
+ style="width:48px; height:48px; margin:4px 0 0 10px; float:left"
+ onclick="TbSync.manager.openLink(TbSync.providers.loadedProviders[this.parentNode.getAttribute('provider')].addon.homepageURL);"
+ onmouseover="this.style.cursor='pointer'"
+ onmouseout="this.style.cursor='default'" />
+ <html:div style="padding-left: 65px;">
+ <html:div>__TBSYNCMSG_manager.title__</html:div>
+ <html:div style="font-style:italic">__TBSYNCMSG_manager.provider4tbsync__</html:div>
+ <html:div style="font-style:italic;">[<html:span
+ onclick="TbSync.manager.openLink(TbSync.providers.loadedProviders[this.parentNode.parentNode.parentNode.getAttribute('provider')].addon.contributorsURL);"
+ onmouseover="this.style.cursor='pointer'"
+ onmouseout="this.style.cursor='default'"
+ style="color:blue;text-decoration: underline;">__TBSYNCMSG_manager.supporter.details__</html:span>]</html:div>
+ </html:div>
+ </html:div>
+
+ <html:div provider="" style="width:225px; margin: 0 0 10px 0; float:left;">
+ <html:img
+ src="chrome://tbsync/content/skin/tbsync64.png"
+ style="width:48px; height:48px; margin:4px 0 0 10px; float:left"
+ onclick="TbSync.manager.openLink(TbSync.addon.homepageURL);"
+ onmouseover="this.style.cursor='pointer'"
+ onmouseout="this.style.cursor='default'" />
+ <html:div style="padding-left: 65px;">
+ <html:span></html:span>
+ <html:div>TbSync</html:div>
+ <html:div style="font-style:italic">__TBSYNCMSG_manager.shorttitle__</html:div>
+ <html:div style="font-style:italic;">[<html:span
+ onclick="TbSync.manager.openLink(TbSync.addon.contributorsURL);"
+ onmouseover="this.style.cursor='pointer'"
+ onmouseout="this.style.cursor='default'"
+ style="color:blue;text-decoration: underline;">__TBSYNCMSG_manager.supporter.details__</html:span>]</html:div>
+ </html:div>
+ </html:div>
+
+ </html:div>
+ </html:p>
+
+
+ <html:p>
+ <html:b>__TBSYNCMSG_manager.supporter.sponsors__</html:b>
+ <html:div id="listOfSponsors" style="margin-top:10px">
+ <html:div link="" style="width:225px; margin: 0 0 10px 0; float:left;">
+ <html:img src="chrome://tbsync/content/skin/user48.png" style="width:48px; height:48px; padding:0px; margin:0 0 0 10px; border:1px solid #d3d3d3; float:left" onclick="if (this.parentNode.getAttribute('link') != '') {TbSync.manager.openLink(this.parentNode.getAttribute('link'));}" onmouseover="if (this.parentNode.getAttribute('link') != '') {this.style.cursor='pointer'}" onmouseout="this.style.cursor='default'" />
+ <html:div style="padding-left: 65px;">
+ <html:div >Name</html:div>
+ <html:div style="font-style:italic">Description</html:div>
+ </html:div>
+ </html:div>
+ </html:div>
+ </html:p>
+
+ </vbox>
+ </hbox>
+
+ <script type="text/javascript" src="chrome://tbsync/content/manager/accountManager.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+</window>
diff --git a/content/modules/abAutoComplete.js b/content/modules/abAutoComplete.js
new file mode 100644
index 0000000..c154a40
--- /dev/null
+++ b/content/modules/abAutoComplete.js
@@ -0,0 +1,234 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+//derived from https://dxr.mozilla.org/comm-central/source/mozilla/accessible/tests/mochitest/autocomplete.js
+var abAutoComplete = {
+
+ tbSyncAutoCompleteSearch : null,
+
+ /**
+ * Register 'tbSyncAutoCompleteSearch' AutoCompleteSearch.
+ */
+ load : function () {
+ abAutoComplete.tbSyncAutoCompleteSearch = new abAutoComplete.Search("tbSyncAutoCompleteSearch");
+ abAutoComplete.register(abAutoComplete.tbSyncAutoCompleteSearch, "AutoCompleteSearch");
+ },
+
+ /**
+ * Unregister 'tbSyncAutoCompleteSearch' AutoCompleteSearch.
+ */
+ unload : function () {
+ abAutoComplete.unregister(abAutoComplete.tbSyncAutoCompleteSearch);
+ abAutoComplete.tbSyncAutoCompleteSearch.cid = null;
+ abAutoComplete.tbSyncAutoCompleteSearch = null;
+ },
+
+
+ /**
+ * Register the given AutoCompleteSearch.
+ *
+ * @param aSearch [in] AutoCompleteSearch object
+ * @param aDescription [in] description of the search object
+ */
+ register : function (aSearch, aDescription) {
+ var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name;
+
+ var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].getService(Components.interfaces.nsIUUIDGenerator);
+ var cid = uuidGenerator.generateUUID();
+
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.registerFactory(cid, aDescription, name, aSearch);
+
+ // Keep the id on the object so we can unregister later.
+ aSearch.cid = cid;
+ },
+
+ /**
+ * Unregister the given AutoCompleteSearch.
+ */
+ unregister : function (aSearch) {
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.unregisterFactory(aSearch.cid, aSearch);
+ },
+
+
+ /**
+ * nsIAutoCompleteSearch implementation.
+ *
+ * @param aName [in] the name of autocomplete search
+ */
+ Search : function (aName) {
+ this.name = aName;
+ },
+
+
+
+
+ /**
+ * nsIAutoCompleteResult implementation.
+ */
+ Result : function (aValues, aComments, aStyles, aIcons) {
+ this.values = aValues;
+ this.comments = aComments;
+ this.styles = aStyles;
+ this.icons = aIcons;
+
+ if (this.values.length > 0)
+ this.searchResult = Components.interfaces.nsIAutoCompleteResult.RESULT_SUCCESS;
+ else
+ this.searchResult = Components.interfaces.nsIAutoCompleteResult.NOMATCH;
+ },
+
+
+
+
+ Request: async function(accountData, aSearchString) {
+ let entries = [];
+ if (accountData.isConnected()) {
+ entries = await TbSync.providers[accountData.getAccountProperty("provider")].Base.abAutoComplete(accountData, aSearchString);
+ }
+ return entries.map(entry => ({ ...entry, id: accountData.accountID }));
+ },
+}
+
+
+
+
+
+abAutoComplete.Search.prototype = {
+ constructor: abAutoComplete.Search,
+
+ // nsIAutoCompleteSearch implementation
+ startSearch : async function (aSearchString, aSearchParam, aPreviousResult, aListener) {
+ let result = await this.getAutoCompleteResultFor(aSearchString);
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch() {},
+
+ // nsISupports implementation
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIAutoCompleteSearch"]),
+
+ // nsIFactory implementation
+ createInstance(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // Search name. Used by AutoCompleteController.
+ name: null,
+
+ /**
+ * Return AutoCompleteResult for the given search string.
+ */
+ getAutoCompleteResultFor : async function (aSearchString) {
+ //check each account and init server request
+ let accounts = TbSync.db.getAccounts();
+ let requests = [];
+ let values = [];
+ let comments = [];
+ let styles = [];
+ let icons = [];
+
+ for (let i=0; i<accounts.IDs.length; i++) {
+ let accountID = accounts.IDs[i];
+
+ let accountData = new TbSync.AccountData(accountID);
+ let provider = accountData.getAccountProperty("provider");
+ let status = accountData.getAccountProperty("status");
+
+ if (status == "disabled") continue;
+ //start all requests parallel (do not wait till done here, push the promise)
+ if (TbSync.providers[provider].Base.abAutoComplete) {
+ try {
+ requests.push(TbSync.abAutoComplete.Request(accountData, aSearchString));
+ } catch (e) {}
+ }
+ }
+
+ //wait for all requests to finish (only have to wait for the slowest, all others are done)
+ for (let r=0; r < requests.length; r++) {
+ try {
+ let result = await requests[r];
+ for (let count=0; count < result.length; count++) {
+ values.push(result[count].value);
+ comments.push(result[count].comment);
+ styles.push(result[count].style);
+ icons.push(result[count].icon);
+ }
+ } catch (e) {};
+ }
+
+ return new abAutoComplete.Result(values, comments, styles, icons);
+ }
+}
+
+abAutoComplete.Result.prototype = {
+ constructor: abAutoComplete.Result,
+
+ searchString: "",
+ searchResult: null,
+
+ defaultIndex: 0,
+
+ get matchCount() {
+ return this.values.length;
+ },
+
+ getValueAt(aIndex) {
+ return this.values[aIndex];
+ },
+
+ /**
+ * This returns the string that is displayed in the dropdown
+ */
+ getLabelAt(aIndex) {
+ return " " + this.getValueAt(aIndex);
+ },
+
+ /**
+ * Get the comment of the result at the given index
+ */
+ getCommentAt(aIndex) {
+ return " " + this.comments[aIndex];
+ },
+
+ /**
+ * Get the style hint for the result at the given index
+ */
+ getStyleAt(aIndex) {
+ return this.styles[aIndex] ? this.styles[aIndex] : null;
+ },
+
+ /**
+ * Get the image of the result at the given index
+ */
+ getImageAt(aIndex) {
+ return this.icons[aIndex] ? this.icons[aIndex] : null;
+ },
+
+ /**
+ * Get the final value that should be completed when the user confirms
+ * the match at the given index.
+ */
+ getFinalCompleteValueAt(aIndex) {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt(aRowIndex, aRemoveFromDb) {},
+
+ // nsISupports implementation
+ QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]),
+
+ // Data
+ values: null,
+ comments: null,
+ styles: null,
+ icons: null,
+}
diff --git a/content/modules/addressbook.js b/content/modules/addressbook.js
new file mode 100644
index 0000000..455d0e1
--- /dev/null
+++ b/content/modules/addressbook.js
@@ -0,0 +1,1218 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var addressbook = {
+
+ load : async function () {
+ // Geoffs addrbook-contact-created observer does not fire on moves, so we do not use it
+ // Services.obs.addObserver(this.addressbookObserver, "addrbook-contact-created", false);
+ Services.obs.addObserver(this.addressbookObserver, "addrbook-contact-updated", false);
+ Services.obs.addObserver(this.addressbookObserver, "addrbook-list-member-added", false);
+ Services.obs.addObserver(this.addressbookObserver, "addrbook-list-updated", false);
+ this.addressbookListener.add();
+ },
+
+ unload : async function () {
+ // Geoffs addrbook-contact-created observer does not fire on moves, so we do not use it
+ // Services.obs.removeObserver(this.addressbookObserver, "addrbook-contact-created");
+ Services.obs.removeObserver(this.addressbookObserver, "addrbook-contact-updated");
+ Services.obs.removeObserver(this.addressbookObserver, "addrbook-list-member-added");
+ Services.obs.removeObserver(this.addressbookObserver, "addrbook-list-updated");
+ this.addressbookListener.remove();
+ },
+
+
+ // needed due to sogo issue not implementing this method in their Ab
+ getStringValue : function (ab, value, fallback) {
+ try {
+ return ab.getStringValue(value, fallback);
+ } catch (e) {
+ return fallback;
+ }
+ },
+
+ searchDirectory: function (uri, search) {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ cards : [],
+
+ onSearchFinished(aResult, aErrorMsg) {
+ resolve(this.cards);
+ },
+ onSearchFoundCard(aCard) {
+ this.cards.push(aCard.QueryInterface(Components.interfaces.nsIAbCard));
+ }
+ }
+
+ let result = MailServices.ab.getDirectory(uri).search(search, listener);
+ });
+ },
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * AdvancedTargetData, an extended TargetData implementation, providers
+ // * can use this as their own TargetData by extending it and just
+ // * defining the extra methods
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ AdvancedTargetData : class {
+ constructor(folderData) {
+ this._folderData = folderData;
+ this._targetObj = null;
+ }
+
+
+ // Check, if the target exists and return true/false.
+ hasTarget() {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+ return directory ? true : false;
+ }
+
+ // Returns the target obj, which TbSync should return as the target. It can
+ // be whatever you want and is returned by FolderData.targetData.getTarget().
+ // If the target does not exist, it should be created. Throw a simple Error, if that
+ // failed.
+ async getTarget() {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+
+ if (!directory) {
+ // create a new addressbook and store its UID in folderData
+ directory = await TbSync.addressbook.prepareAndCreateAddressbook(this._folderData);
+ if (!directory)
+ throw new Error("notargets");
+ }
+
+ if (!this._targetObj || this._targetObj.UID != directory.UID)
+ this._targetObj = new TbSync.addressbook.AbDirectory(directory, this._folderData);
+
+ return this._targetObj;
+ }
+
+ /**
+ * Removes the target from the local storage. If it does not exist, return
+ * silently. A call to ``hasTarget()`` should return false, after this has
+ * been executed.
+ *
+ */
+ removeTarget() {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+ try {
+ if (directory) {
+ MailServices.ab.deleteAddressBook(directory.URI);
+ }
+ } catch (e) {}
+
+ TbSync.db.clearChangeLog(target);
+ this._folderData.resetFolderProperty("target");
+ }
+
+ /**
+ * Disconnects the target in the local storage from this TargetData, but
+ * does not delete it, so it becomes a stale "left over" . A call
+ * to ``hasTarget()`` should return false, after this has been executed.
+ *
+ */
+ disconnectTarget() {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+ if (directory) {
+ let changes = TbSync.db.getItemsFromChangeLog(target, 0, "_by_user");
+ if (changes.length > 0) {
+ this.targetName = this.targetName + " (*)";
+ }
+ directory.setStringValue("tbSyncIcon", "orphaned");
+ directory.setStringValue("tbSyncProvider", "orphaned");
+ directory.setStringValue("tbSyncAccountID", "");
+ }
+ TbSync.db.clearChangeLog(target);
+ this._folderData.resetFolderProperty("target");
+ }
+
+ set targetName(newName) {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+ if (directory) {
+ directory.dirName = newName;
+ } else {
+ throw new Error("notargets");
+ }
+ }
+
+ get targetName() {
+ let target = this._folderData.getFolderProperty("target");
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(target);
+ if (directory) {
+ return directory.dirName;
+ } else {
+ throw new Error("notargets");
+ }
+ }
+
+ setReadOnly(value) {
+ }
+
+
+ // * * * * * * * * * * * * * * * * *
+ // * AdvancedTargetData extension *
+ // * * * * * * * * * * * * * * * * *
+
+ get isAdvancedAddressbookTargetData() {
+ return true;
+ }
+
+ get folderData() {
+ return this._folderData;
+ }
+
+ // define a card property, which should be used for the changelog
+ // basically your primary key for the abItem properties
+ // UID will be used, if nothing specified
+ get primaryKeyField() {
+ return "UID";
+ }
+
+ generatePrimaryKey() {
+ return TbSync.generateUUID();
+ }
+
+ // enable or disable changelog
+ get logUserChanges() {
+ return true;
+ }
+
+ directoryObserver(aTopic) {
+ switch (aTopic) {
+ case "addrbook-removed":
+ case "addrbook-updated":
+ //Services.console.logStringMessage("["+ aTopic + "] " + folderData.getFolderProperty("foldername"));
+ break;
+ }
+ }
+
+ cardObserver(aTopic, abCardItem) {
+ switch (aTopic) {
+ case "addrbook-contact-updated":
+ case "addrbook-contact-removed":
+ case "addrbook-contact-created":
+ //Services.console.logStringMessage("["+ aTopic + "] " + abCardItem.getProperty("DisplayName"));
+ break;
+ }
+ }
+
+ listObserver(aTopic, abListItem, abListMember) {
+ switch (aTopic) {
+ case "addrbook-list-member-added":
+ case "addrbook-list-member-removed":
+ //Services.console.logStringMessage("["+ aTopic + "] MemberName: " + abListMember.getProperty("DisplayName"));
+ break;
+
+ case "addrbook-list-removed":
+ case "addrbook-list-updated":
+ //Services.console.logStringMessage("["+ aTopic + "] ListName: " + abListItem.getProperty("ListName"));
+ break;
+
+ case "addrbook-list-created":
+ //Services.console.logStringMessage("["+ aTopic + "] Created new X-DAV-UID for List <"+abListItem.getProperty("ListName")+">");
+ break;
+ }
+ }
+
+ // replace this with your own implementation to create the actual addressbook,
+ // when this class is extended
+ async createAddressbook(newname) {
+ // https://searchfox.org/comm-central/source/mailnews/addrbook/src/nsDirPrefs.h
+ let dirPrefId = MailServices.ab.newAddressBook(newname, "", 101);
+ let directory = MailServices.ab.getDirectoryFromId(dirPrefId);
+ return directory;
+ }
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * AbItem and AbDirectory Classes
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ AbItem : class {
+ constructor(abDirectory, item) {
+ if (!abDirectory)
+ throw new Error("AbItem::constructor is missing its first parameter!");
+
+ if (!item)
+ throw new Error("AbItem::constructor is missing its second parameter!");
+
+ this._abDirectory = abDirectory;
+ this._card = null;
+ this._tempListDirectory = null;
+ this._tempProperties = null;
+ this._isMailList = false;
+
+ if (item instanceof Components.interfaces.nsIAbDirectory) {
+ this._tempListDirectory = item;
+ this._isMailList = true;
+ this._tempProperties = {};
+ } else {
+ this._card = item;
+ this._isMailList = item.isMailList;
+ }
+ }
+
+ get abDirectory() {
+ return this._abDirectory;
+ }
+
+ get isMailList() {
+ return this._isMailList;
+ }
+
+
+
+
+
+ get nativeItem() {
+ return this._card;
+ }
+
+ get UID() {
+ if (this._tempListDirectory) return this._tempListDirectory.UID;
+ return this._card.UID;
+ }
+
+ get primaryKey() {
+ //use UID as fallback
+ let key = this._abDirectory.primaryKeyField;
+ return key ? this.getProperty(key) : this.UID;
+ }
+
+ set primaryKey(value) {
+ //use UID as fallback
+ let key = this._abDirectory.primaryKeyField;
+ if (key) this.setProperty(key, value)
+ else throw ("TbSync.addressbook.AbItem.set primaryKey: UID is used as primaryKeyField but changing the UID of an item is currently not supported. Please use a custom primaryKeyField.");
+ }
+
+ clone() { //no real clone ... this is just here to match the calendar target
+ return new TbSync.addressbook.AbItem(this._abDirectory, this._card);
+ }
+
+ toString() {
+ return this._card.displayName + " (" + this._card.firstName + ", " + this._card.lastName + ") <"+this._card.primaryEmail+">";
+ }
+
+ // mailinglist aware method to get properties of cards
+ // mailinglist properties cannot be stored in mailinglists themselves, so we store them in changelog
+ getProperty(property, fallback = "") {
+ if (property == "UID")
+ return this.UID;
+
+ if (this._isMailList) {
+ const directListProperties = {
+ ListName: "dirName",
+ ListNickName: "listNickName",
+ ListDescription: "description"
+ };
+
+ let value;
+ if (directListProperties.hasOwnProperty(property)) {
+ try {
+ let mailListDirectory = this._tempListDirectory || MailServices.ab.getDirectory(this._card.mailListURI);
+ value = mailListDirectory[directListProperties[property]];
+ } catch (e) {
+ // list does not exists
+ }
+ } else {
+ value = this._tempProperties ? this._tempProperties[property] : TbSync.db.getItemStatusFromChangeLog(this._abDirectory.UID + "#" + this.UID, property);
+ }
+ return value || fallback;
+ } else {
+ return this._card.getProperty(property, fallback);
+ }
+ }
+
+ // mailinglist aware method to set properties of cards
+ // mailinglist properties cannot be stored in mailinglists themselves, so we store them in changelog
+ // while the list has not been added, wekeep all props in an object (UID changes on adding)
+ setProperty(property, value) {
+ // UID cannot be changed (currently)
+ if (property == "UID") {
+ throw ("TbSync.addressbook.AbItem.setProperty: UID cannot be changed currently.");
+ return;
+ }
+
+ if (this._isMailList) {
+ const directListProperties = {
+ ListName: "dirName",
+ ListNickName: "listNickName",
+ ListDescription: "description"
+ };
+
+ if (directListProperties.hasOwnProperty(property)) {
+ try {
+ let mailListDirectory = this._tempListDirectory || MailServices.ab.getDirectory(this._card.mailListURI);
+ mailListDirectory[directListProperties[property]] = value;
+ } catch (e) {
+ // list does not exists
+ }
+ } else {
+ if (this._tempProperties) {
+ this._tempProperties[property] = value;
+ } else {
+ TbSync.db.addItemToChangeLog(this._abDirectory.UID + "#" + this.UID, property, value);
+ }
+ }
+ } else {
+ this._card.setProperty(property, value);
+ }
+ }
+
+ deleteProperty(property) {
+ if (this._isMailList) {
+ if (this._tempProperties) {
+ delete this._tempProperties[property];
+ } else {
+ TbSync.db.removeItemFromChangeLog(this._abDirectory.UID + "#" + this.UID, property);
+ }
+ } else {
+ this._card.deleteProperty(property);
+ }
+ }
+
+ get changelogData() {
+ return TbSync.db.getItemDataFromChangeLog(this._abDirectory.UID, this.primaryKey);
+ }
+
+ get changelogStatus() {
+ return TbSync.db.getItemStatusFromChangeLog(this._abDirectory.UID, this.primaryKey);
+ }
+
+ set changelogStatus(status) {
+ let value = this.primaryKey;
+
+ if (value) {
+ if (!status) {
+ TbSync.db.removeItemFromChangeLog(this._abDirectory.UID, value);
+ return;
+ }
+
+ if (this._abDirectory.logUserChanges || status.endsWith("_by_server")) {
+ TbSync.db.addItemToChangeLog(this._abDirectory.UID, value, status);
+ }
+ }
+ }
+
+
+
+
+
+ // get the property given from all members and return it as an array (that property better be uniqe)
+ getMembersPropertyList(property) {
+ let members = [];
+ if (this._card && this._card.isMailList) {
+ // get mailListDirectory
+ let mailListDirectory = MailServices.ab.getDirectory(this._card.mailListURI);
+ let cards = mailListDirectory.childCards;
+ while (cards.hasMoreElements()) {
+ let member = cards.getNext().QueryInterface(Components.interfaces.nsIAbCard)
+ let prop = member.getProperty(property, "");
+ if (prop) members.push(prop);
+ }
+ }
+ return members;
+ }
+
+ addListMembers(property, candidates) {
+ if (this._card && this._card.isMailList) {
+ let members = this.getMembersPropertyList(property);
+ let mailListDirectory = MailServices.ab.getDirectory(this._card.mailListURI);
+
+ for (let candidate of candidates) {
+ if (members.includes(candidate))
+ continue;
+
+ let card = this._abDirectory._directory.getCardFromProperty(property, candidate, true);
+ if (card) mailListDirectory.addCard(card);
+ }
+ }
+ }
+
+ removeListMembers(property, candidates) {
+ if (this._card && this._card.isMailList) {
+ let members = this.getMembersPropertyList(property);
+ let mailListDirectory = MailServices.ab.getDirectory(this._card.mailListURI);
+
+ let cardsToRemove = [];
+ for (let candidate of candidates) {
+ if (!members.includes(candidate))
+ continue;
+
+ let card = this._abDirectory._directory.getCardFromProperty(property, candidate, true);
+ if (card) cardsToRemove.push(card);
+ }
+ if (cardsToRemove.length > 0) mailListDirectory.deleteCards(cardsToRemove);
+ }
+ }
+
+ addPhoto(photo, data, extension = "jpg", url = "") {
+ let dest = [];
+ let card = this._card;
+ let bookUID = this.abDirectory.UID;
+
+ // TbSync storage must be set as last
+ let book64 = btoa(bookUID);
+ let photo64 = btoa(photo);
+ let photoName64 = book64 + "_" + photo64 + "." + extension;
+
+ dest.push(["Photos", photoName64]);
+ // I no longer see a reason for this
+ // dest.push(["TbSync","Photos", book64, photo64]);
+
+ let filePath = "";
+ for (let i=0; i < dest.length; i++) {
+ let file = FileUtils.getFile("ProfD", dest[i]);
+
+ let foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+ foStream.init(file, 0x02 | 0x08 | 0x20, 0x180, 0); // write, create, truncate
+ let binary = "";
+ try {
+ binary = atob(data.split(" ").join(""));
+ } catch (e) {
+ console.log("Failed to decode base64 string:", data);
+ }
+ foStream.write(binary, binary.length);
+ foStream.close();
+
+ filePath = 'file:///' + file.path.replace(/\\/g, '\/').replace(/^\s*\/?/, '').replace(/\ /g, '%20');
+ }
+ card.setProperty("PhotoName", photoName64);
+ card.setProperty("PhotoType", url ? "web" : "file");
+ card.setProperty("PhotoURI", url ? url : filePath);
+ return filePath;
+ }
+
+ getPhoto() {
+ let card = this._card;
+ let photo = card.getProperty("PhotoName", "");
+ let data = "";
+
+ if (photo) {
+ try {
+ let file = FileUtils.getFile("ProfD", ["Photos", photo]);
+
+ let fiStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
+ fiStream.init(file, -1, -1, false);
+
+ let bstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
+ bstream.setInputStream(fiStream);
+
+ data = btoa(bstream.readBytes(bstream.available()));
+ fiStream.close();
+ } catch (e) {}
+ }
+ return data;
+ }
+ },
+
+ AbDirectory : class {
+ constructor(directory, folderData) {
+ this._directory = directory;
+ this._folderData = folderData;
+ }
+
+ get directory() {
+ return this._directory;
+ }
+
+ get logUserChanges() {
+ return this._folderData.targetData.logUserChanges;
+ }
+
+ get primaryKeyField() {
+ return this._folderData.targetData.primaryKeyField;
+ }
+
+ get UID() {
+ return this._directory.UID;
+ }
+
+ get URI() {
+ return this._directory.URI;
+ }
+
+ createNewCard() {
+ let card = Components.classes["@mozilla.org/addressbook/cardproperty;1"].createInstance(Components.interfaces.nsIAbCard);
+ return new TbSync.addressbook.AbItem(this, card);
+ }
+
+ createNewList() {
+ let listDirectory = Components.classes["@mozilla.org/addressbook/directoryproperty;1"].createInstance(Components.interfaces.nsIAbDirectory);
+ listDirectory.isMailList = true;
+ return new TbSync.addressbook.AbItem(this, listDirectory);
+ }
+
+ async addItem(abItem, pretagChangelogWithByServerEntry = true) {
+ if (this.primaryKeyField && !abItem.getProperty(this.primaryKeyField)) {
+ abItem.setProperty(this.primaryKeyField, this._folderData.targetData.generatePrimaryKey());
+ //Services.console.logStringMessage("[AbDirectory::addItem] Generated primary key!");
+ }
+
+ if (pretagChangelogWithByServerEntry) {
+ abItem.changelogStatus = "added_by_server";
+ }
+
+ if (abItem.isMailList && abItem._tempListDirectory) {
+ let list = this._directory.addMailList(abItem._tempListDirectory);
+ // the list has been added and we can now get the corresponding card via its UID
+ let found = await this.getItemFromProperty("UID", list.UID);
+
+ // clone and clear temporary properties
+ let props = {...abItem._tempProperties};
+ abItem._tempListDirectory = null;
+ abItem._tempProperties = null;
+
+ // store temporary properties
+ for (const [property, value] of Object.entries(props)) {
+ found.setProperty(property, value);
+ }
+
+ abItem._card = found._card;
+ } else if (!abItem.isMailList) {
+ this._directory.addCard(abItem._card);
+
+ } else {
+ throw new Error("Cannot re-add a list to a directory.");
+ }
+ }
+
+ modifyItem(abItem, pretagChangelogWithByServerEntry = true) {
+ // only add entry if the current entry does not start with _by_user
+ let status = abItem.changelogStatus ? abItem.changelogStatus : "";
+ if (pretagChangelogWithByServerEntry && !status.endsWith("_by_user")) {
+ abItem.changelogStatus = "modified_by_server";
+ }
+
+ if (abItem.isMailList) {
+ // get mailListDirectory
+ let mailListDirectory = MailServices.ab.getDirectory(abItem._card.mailListURI);
+
+ // store
+ mailListDirectory.editMailListToDatabase(abItem._card);
+ } else {
+ this._directory.modifyCard(abItem._card);
+ }
+ }
+
+ deleteItem(abItem, pretagChangelogWithByServerEntry = true) {
+ if (pretagChangelogWithByServerEntry) {
+ abItem.changelogStatus = "deleted_by_server";
+ }
+ this._directory.deleteCards([abItem._card]);
+ }
+
+ async getItem(searchId) {
+ //use UID as fallback
+ let key = this.primaryKeyField ? this.primaryKeyField : "UID";
+ return await this.getItemFromProperty(key, searchId);
+ }
+
+ async getItemFromProperty(property, value) {
+ // try to use the standard card method first
+ let card = this._directory.getCardFromProperty(property, value, true);
+ if (card) {
+ return new TbSync.addressbook.AbItem(this, card);
+ }
+
+ // search for list cards
+ // we cannot search for the prop directly, because for mailinglists
+ // they are not part of the card (expect UID) but stored in a custom storage
+ let searchList = "(IsMailList,=,TRUE)";
+ let foundCards = await TbSync.addressbook.searchDirectory(this._directory.URI, "(or" + searchList+")");
+ for (let aCard of foundCards) {
+ let card = new TbSync.addressbook.AbItem(this, aCard);
+ //does this list card have the req prop?
+ if (card.getProperty(property) == value) {
+ return card;
+ }
+ }
+ return null;
+ }
+
+ getAllItems () {
+ let rv = [];
+ let cards = this._directory.childCards;
+ while (true) {
+ let more = false;
+ try { more = cards.hasMoreElements() } catch (e) { Components.utils.reportError(e); }
+ if (!more) break;
+
+ let card = new TbSync.addressbook.AbItem( this._directory, cards.getNext().QueryInterface(Components.interfaces.nsIAbCard));
+ rv.push(card);
+ }
+ return rv;
+ }
+
+
+
+
+
+ getAddedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this._directory.UID, maxitems, "added_by_user").map(item => item.itemId);
+ }
+
+ getModifiedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this._directory.UID, maxitems, "modified_by_user").map(item => item.itemId);
+ }
+
+ getDeletedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this._directory.UID, maxitems, "deleted_by_user").map(item => item.itemId);
+ }
+
+ getItemsFromChangeLog(maxitems = 0) { // Document what this returns
+ return TbSync.db.getItemsFromChangeLog(this._directory.UID, maxitems, "_by_user");
+ }
+
+ removeItemFromChangeLog(id, moveToEndInsteadOfDelete = false) {
+ TbSync.db.removeItemFromChangeLog(this._directory.UID, id, moveToEndInsteadOfDelete);
+ }
+
+ clearChangelog() {
+ TbSync.db.clearChangeLog(this._directory.UID);
+ }
+
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * Internal Functions
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ prepareAndCreateAddressbook: async function (folderData) {
+ let target = folderData.getFolderProperty("target");
+ let provider = folderData.accountData.getAccountProperty("provider");
+
+ // Get cached or new unique name for new address book
+ let cachedName = folderData.getFolderProperty("targetName");
+ let newname = cachedName == "" ? folderData.accountData.getAccountProperty("accountname") + " (" + folderData.getFolderProperty("foldername")+ ")" : cachedName;
+
+ //Create the new book with the unique name
+ let directory = await folderData.targetData.createAddressbook(newname);
+ if (directory && directory instanceof Components.interfaces.nsIAbDirectory) {
+ directory.setStringValue("tbSyncProvider", provider);
+ directory.setStringValue("tbSyncAccountID", folderData.accountData.accountID);
+
+ // Prevent gContactSync to inject its stuff into New/EditCard dialogs
+ // https://github.com/jdgeenen/gcontactsync/pull/127
+ directory.setStringValue("gContactSyncSkipped", "true");
+
+ folderData.setFolderProperty("target", directory.UID);
+ folderData.setFolderProperty("targetName", directory.dirName);
+ //notify about new created address book
+ Services.obs.notifyObservers(null, 'tbsync.observer.addressbook.created', null)
+ return directory;
+ }
+
+ return null;
+ },
+
+ getFolderFromDirectoryUID: function(bookUID) {
+ let folders = TbSync.db.findFolders({"target": bookUID});
+ if (folders.length == 1) {
+ let accountData = new TbSync.AccountData(folders[0].accountID);
+ return new TbSync.FolderData(accountData, folders[0].folderID);
+ }
+ return null;
+ },
+
+ getDirectoryFromDirectoryUID: function(UID) {
+ let directories = MailServices.ab.directories;
+ while (UID && directories.hasMoreElements()) {
+ let directory = directories.getNext();
+ if (directory instanceof Components.interfaces.nsIAbDirectory) {
+ if (directory.UID == UID) return directory;
+ }
+ }
+ return null;
+ },
+
+ getListInfoFromListUID: async function(UID) {
+ let directories = MailServices.ab.directories;
+ while (directories.hasMoreElements()) {
+ let directory = directories.getNext();
+ if (directory instanceof Components.interfaces.nsIAbDirectory && !directory.isRemote) {
+ let searchList = "(IsMailList,=,TRUE)";
+ let foundCards = await TbSync.addressbook.searchDirectory(directory.URI, "(and" + searchList+")");
+ for (let listCard of foundCards) {
+ //return after first found card
+ if (listCard.UID == UID) return {directory, listCard};
+ }
+ }
+ }
+ throw new Error("List with UID <" + UID + "> does not exists");
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * Addressbook Observer and Listener
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ addressbookObserver: {
+ observe: async function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ // we do not need addrbook-created
+ case "addrbook-updated":
+ case "addrbook-removed":
+ {
+ //aSubject: nsIAbDirectory (we can get URI and UID directly from the object, but the directory no longer exists)
+ aSubject.QueryInterface(Components.interfaces.nsIAbDirectory);
+ let bookUID = aSubject.UID;
+
+ let folderData = TbSync.addressbook.getFolderFromDirectoryUID(bookUID);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedAddressbookTargetData) {
+
+ switch(aTopic) {
+ case "addrbook-updated":
+ {
+ //update name of target (if changed)
+ folderData.setFolderProperty("targetName", aSubject.dirName);
+ //update settings window, if open
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ }
+ break;
+
+ case "addrbook-removed":
+ {
+ //delete any pending changelog of the deleted book
+ TbSync.db.clearChangeLog(bookUID);
+
+ //unselect book if deleted by user and update settings window, if open
+ if (folderData.getFolderProperty("selected")) {
+ folderData.setFolderProperty("selected", false);
+ //update settings window, if open
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ }
+
+ folderData.resetFolderProperty("target");
+ }
+ break;
+ }
+
+ folderData.targetData.directoryObserver(aTopic);
+ }
+ }
+ break;
+
+ case "addrbook-contact-created":
+ case "addrbook-contact-updated":
+ case "addrbook-contact-removed":
+ {
+ //aSubject: nsIAbCard
+ aSubject.QueryInterface(Components.interfaces.nsIAbCard);
+ //aData: 128-bit unique identifier for the parent directory
+ let bookUID = aData;
+
+ let folderData = TbSync.addressbook.getFolderFromDirectoryUID(bookUID);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedAddressbookTargetData) {
+
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(bookUID);
+ let abDirectory = new TbSync.addressbook.AbDirectory(directory, folderData);
+ let abItem = new TbSync.addressbook.AbItem(abDirectory, aSubject);
+ let itemStatus = abItem.changelogStatus || "";
+
+ // during create the following can happen
+ // card has no primary key
+ // another process could try to mod
+ // -> we need to identify this card with an always available ID and block any other MODS until we free it again
+ // -> store creation type
+
+ if (aTopic == "addrbook-contact-created" && itemStatus == "") {
+ // add this new card to changelog to keep track of it
+ TbSync.db.addItemToChangeLog(bookUID, aSubject.UID + "#DelayedUserCreation", Date.now());
+ // new cards must get a NEW(!) primaryKey first
+ if (abDirectory.primaryKeyField) {
+ console.log("New primary Key generated!");
+ abItem.setProperty(abDirectory.primaryKeyField, folderData.targetData.generatePrimaryKey());
+ }
+ // special case: do not add "modified_by_server"
+ abDirectory.modifyItem(abItem, /*pretagChangelogWithByServerEntry */ false);
+ // We will see this card again as updated but delayed created
+ return;
+ }
+
+ // during follow up MODs we can identify this card via
+ let delayedUserCreation = TbSync.db.getItemStatusFromChangeLog(bookUID, aSubject.UID + "#DelayedUserCreation");
+
+ // if we reach this point and if we have adelayedUserCreation,
+ // we can remove the delayedUserCreation marker and can
+ // continue to process this event as an addrbook-contact-created
+ let bTopic = aTopic;
+ if (delayedUserCreation) {
+ let age = Date.now() - delayedUserCreation;
+ if (age < 1500) {
+ bTopic = "addrbook-contact-created";
+ } else {
+ TbSync.db.removeItemFromChangeLog(bookUID, aSubject.UID + "#DelayedUserCreation");
+ }
+ }
+
+ // if this card was created by us, it will be in the log
+ // we want to ignore any MOD for a freeze time, because
+ // gContactSync modifies our(!) contacts (GoogleID) after we added them, so they get
+ // turned into "modified_by_user" and will be send back to the server.
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ let age = Date.now() - abItem.changelogData.timestamp;
+ if (age < 1500) {
+ // during freeze, local modifications are not possible
+ return;
+ } else {
+ // remove blocking entry from changelog after freeze time is over (1.5s),
+ // and continue evaluating this event
+ abItem.changelogStatus = "";
+ }
+ }
+
+ // From here on, we only process user changes as server changes are self freezed
+ // update changelog based on old status
+ switch (bTopic) {
+ case "addrbook-contact-created":
+ {
+ switch (itemStatus) {
+ case "added_by_user":
+ // late create notification
+ break;
+
+ case "modified_by_user":
+ // late create notification
+ abItem.changelogStatus = "added_by_user";
+ break;
+
+ case "deleted_by_user":
+ // unprocessed delete for this card, undo the delete (moved out and back in)
+ abItem.changelogStatus = "modified_by_user";
+ break;
+
+ default:
+ // new card
+ abItem.changelogStatus = "added_by_user";
+ }
+ }
+ break;
+
+ case "addrbook-contact-updated":
+ {
+ switch (itemStatus) {
+ case "added_by_user":
+ // unprocessed add for this card, keep status
+ break;
+
+ case "modified_by_user":
+ // double notification, keep status
+ break;
+
+ case "deleted_by_user":
+ // race? unprocessed delete for this card, moved out and back in and modified
+ default:
+ abItem.changelogStatus = "modified_by_user";
+ break;
+ }
+ }
+ break;
+
+ case "addrbook-contact-removed":
+ {
+ switch (itemStatus) {
+ case "added_by_user":
+ // unprocessed add for this card, revert
+ abItem.changelogStatus = "";
+ return;
+
+ case "deleted_by_user":
+ // double notification
+ break;
+
+ case "modified_by_user":
+ // unprocessed mod for this card
+ default:
+ abItem.changelogStatus = "deleted_by_user";
+ break;
+ }
+ }
+ break;
+ }
+
+ if (abDirectory.logUserChanges) TbSync.core.setTargetModified(folderData);
+
+ // notify observers only if status changed
+ if (itemStatus != abItem.changelogStatus) {
+ folderData.targetData.cardObserver(bTopic, abItem);
+ }
+ return;
+ }
+ }
+ break;
+
+ case "addrbook-list-created":
+ case "addrbook-list-removed":
+ {
+ //aSubject: nsIAbCard (ListCard)
+ aSubject.QueryInterface(Components.interfaces.nsIAbCard);
+ //aData: 128-bit unique identifier for the parent directory
+ let bookUID = aData;
+
+ let folderData = TbSync.addressbook.getFolderFromDirectoryUID(bookUID);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedAddressbookTargetData) {
+
+ let directory = TbSync.addressbook.getDirectoryFromDirectoryUID(bookUID);
+ let abDirectory = new TbSync.addressbook.AbDirectory(directory, folderData);
+ let abItem = new TbSync.addressbook.AbItem(abDirectory, aSubject);
+
+ let itemStatus = abItem.changelogStatus;
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ //we caused this, ignore
+ abItem.changelogStatus = "";
+ return;
+ }
+
+ // update changelog based on old status
+ switch (aTopic) {
+ case "addrbook-list-created":
+ {
+ if (abDirectory.primaryKeyField) {
+ // Since we do not need to update a list, to make custom properties persistent, we do not need to use delayedUserCreation as with contacts.
+ abItem.setProperty(abDirectory.primaryKeyField, folderData.targetData.generatePrimaryKey());
+ }
+
+ switch (itemStatus) {
+ case "added_by_user":
+ // double notification, which is probably impossible, keep status
+ break;
+
+ case "modified_by_user":
+ // late create notification
+ abItem.changelogStatus = "added_by_user";
+ break;
+
+ case "deleted_by_user":
+ // unprocessed delete for this card, undo the delete (moved out and back in)
+ abItem.changelogStatus = "modified_by_user";
+ break;
+
+ default:
+ // new list
+ abItem.changelogStatus = "added_by_user";
+ break;
+ }
+ }
+ break;
+
+ case "addrbook-list-removed":
+ {
+ switch (itemStatus) {
+ case "added_by_user":
+ // unprocessed add for this card, revert
+ abItem.changelogStatus = "";
+ return;
+
+ case "modified_by_user":
+ // unprocessed mod for this card
+ case "deleted_by_user":
+ // double notification
+ default:
+ abItem.changelogStatus = "deleted_by_user";
+ break;
+ }
+ //remove properties of this ML stored in changelog
+ TbSync.db.clearChangeLog(abDirectory.UID + "#" + abItem.UID);
+ }
+ break;
+ }
+
+ if (abDirectory.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.listObserver(aTopic, abItem, null);
+ }
+ }
+ break;
+
+ case "addrbook-list-updated":
+ {
+ // aSubject: nsIAbDirectory
+ aSubject.QueryInterface(Components.interfaces.nsIAbDirectory);
+ // get the card representation of this list, including its parent directory
+ let listInfo = await TbSync.addressbook.getListInfoFromListUID(aSubject.UID);
+ let bookUID = listInfo.directory.UID;
+
+ let folderData = TbSync.addressbook.getFolderFromDirectoryUID(bookUID);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedAddressbookTargetData) {
+
+ let abDirectory = new TbSync.addressbook.AbDirectory(listInfo.directory, folderData);
+ let abItem = new TbSync.addressbook.AbItem(abDirectory, listInfo.listCard);
+
+ let itemStatus = abItem.changelogStatus;
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ //we caused this, ignore
+ abItem.changelogStatus = "";
+ return;
+ }
+
+ // update changelog based on old status
+ switch (aTopic) {
+ case "addrbook-list-updated":
+ {
+ switch (itemStatus) {
+ case "added_by_user":
+ // unprocessed add for this card, keep status
+ break;
+
+ case "modified_by_user":
+ // double notification, keep status
+ break;
+
+ case "deleted_by_user":
+ // race? unprocessed delete for this card, moved out and back in and modified
+ default:
+ abItem.changelogStatus = "modified_by_user";
+ break;
+ }
+ }
+ break;
+ }
+
+ if (abDirectory.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.listObserver(aTopic, abItem, null);
+ }
+ }
+ break;
+
+ // unknow, if called for programatically added members as well, probably not
+ case "addrbook-list-member-added": //exclude contact without Email - notification is wrongly send
+ case "addrbook-list-member-removed":
+ {
+ //aSubject: nsIAbCard of Member
+ aSubject.QueryInterface(Components.interfaces.nsIAbCard);
+ //aData: 128-bit unique identifier for the list
+ let listInfo = await TbSync.addressbook.getListInfoFromListUID(aData);
+ let bookUID = listInfo.directory.UID;
+
+ let folderData = TbSync.addressbook.getFolderFromDirectoryUID(bookUID);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedAddressbookTargetData) {
+
+ let abDirectory = new TbSync.addressbook.AbDirectory(listInfo.directory, folderData);
+ let abItem = new TbSync.addressbook.AbItem(abDirectory, listInfo.listCard);
+ let abMember = new TbSync.addressbook.AbItem(abDirectory, aSubject);
+
+ if (abDirectory.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.listObserver(aTopic, abItem, abMember);
+
+ // removed, added members cause the list to be changed
+ let mailListDirectory = MailServices.ab.getDirectory(listInfo.listCard.mailListURI);
+ TbSync.addressbook.addressbookObserver.observe(mailListDirectory, "addrbook-list-updated", null);
+ return;
+ }
+ }
+ break;
+
+ }
+ }
+ },
+
+
+ // Geoff added new observers but these observers cannot catch everything
+ // Use the listeners to make up for that
+ addressbookListener: {
+
+ onItemPropertyChanged: function addressbookListener_onItemPropertyChanged(aItem, aProperty, aOldValue, aNewValue) {
+ //redirect to addrbook-updated observers
+ if (aItem instanceof Components.interfaces.nsIAbDirectory
+ && !aItem.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-updated", null);
+ }
+ },
+
+ onItemRemoved: function addressbookListener_onItemRemoved (aParentDir, aItem) {
+ // redirect to addrbook-list-member-removed observers
+ // unsafe and buggy - see bug 1555294 - can be removed after that landed
+ if (aItem instanceof Components.interfaces.nsIAbCard
+ && aParentDir instanceof Components.interfaces.nsIAbDirectory
+ && !aItem.isMailList
+ && aParentDir.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-list-member-removed", aParentDir.UID)
+ }
+
+ //redirect to addrbook-contact-removed observers
+ if (aItem instanceof Components.interfaces.nsIAbCard
+ && aParentDir instanceof Components.interfaces.nsIAbDirectory
+ && !aItem.isMailList
+ && !aParentDir.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-contact-removed", aParentDir.UID)
+ }
+
+ //redirect to addrbook-list-removed observers
+ if (aItem instanceof Components.interfaces.nsIAbCard
+ && aParentDir instanceof Components.interfaces.nsIAbDirectory
+ && aItem.isMailList
+ && !aParentDir.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-list-removed", aParentDir.UID)
+ }
+
+ //redirect to addrbook-removed observers
+ if (aItem instanceof Components.interfaces.nsIAbDirectory
+ && !aItem.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-removed", null)
+ }
+ },
+
+ onItemAdded: function addressbookListener_onItemAdded (aParentDir, aItem) {
+ //redirect to addrbook-list-created observers
+ if (aItem instanceof Components.interfaces.nsIAbCard
+ && aParentDir instanceof Components.interfaces.nsIAbDirectory
+ && aItem.isMailList
+ && !aParentDir.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-list-created", aParentDir.UID)
+ }
+
+ //redirect to addrbook-contact-created observers
+ if (aItem instanceof Components.interfaces.nsIAbCard
+ && aParentDir instanceof Components.interfaces.nsIAbDirectory
+ //&& aItem.getProperty("UID","") == "" //detect the only case where the original addrbook-contact-created observer fails to notify
+ && !aItem.isMailList
+ && !aParentDir.isMailList) {
+ TbSync.addressbook.addressbookObserver.observe(aItem, "addrbook-contact-created", aParentDir.UID)
+ }
+ },
+
+ add: function addressbookListener_add () {
+ let flags = Components.interfaces.nsIAbListener;
+ MailServices.ab.addAddressBookListener(this, flags.all);
+ },
+
+ remove: function addressbookListener_remove () {
+ MailServices.ab.removeAddressBookListener(this);
+ }
+ },
+}
diff --git a/content/modules/cardbook.js b/content/modules/cardbook.js
new file mode 100644
index 0000000..a9e02c2
--- /dev/null
+++ b/content/modules/cardbook.js
@@ -0,0 +1,20 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+var cardbook = {
+
+ load: async function () {
+ //check for cardbook
+ this.cardbook = await AddonManager.getAddonByID("cardbook@vigneau.philippe") ;
+ },
+
+ unload: async function () {
+ },
+
+}
diff --git a/content/modules/core.js b/content/modules/core.js
new file mode 100644
index 0000000..6a82af3
--- /dev/null
+++ b/content/modules/core.js
@@ -0,0 +1,332 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var core = {
+
+ syncDataObj : null,
+
+ load: async function () {
+ this.syncDataObj = {};
+ },
+
+ unload: async function () {
+ },
+
+ isSyncing: function (accountID) {
+ let status = TbSync.db.getAccountProperty(accountID, "status"); //global status of the account
+ return (status == "syncing");
+ },
+
+ isEnabled: function (accountID) {
+ let status = TbSync.db.getAccountProperty(accountID, "status");
+ return (status != "disabled");
+ },
+
+ isConnected: function (accountID) {
+ let status = TbSync.db.getAccountProperty(accountID, "status");
+ let validFolders = TbSync.db.findFolders({"cached": false}, {"accountID": accountID});
+ return (status != "disabled" && validFolders.length > 0);
+ },
+
+ resetSyncDataObj: function (accountID) {
+ this.syncDataObj[accountID] = new TbSync.SyncData(accountID);
+ },
+
+ getSyncDataObject: function (accountID) {
+ if (!this.syncDataObj.hasOwnProperty(accountID)) {
+ this.resetSyncDataObj(accountID);
+ }
+ return this.syncDataObj[accountID];
+ },
+
+ getNextPendingFolder: function (syncData) {
+ let sortedFolders = TbSync.providers[syncData.accountData.getAccountProperty("provider")].Base.getSortedFolders(syncData.accountData);
+ for (let i=0; i < sortedFolders.length; i++) {
+ if (sortedFolders[i].getFolderProperty("status") != "pending") continue;
+ syncData._setCurrentFolderData(sortedFolders[i]);
+ return true;
+ }
+ syncData._clearCurrentFolderData();
+ return false;
+ },
+
+
+ syncAllAccounts: function () {
+ //get info of all accounts
+ let accounts = TbSync.db.getAccounts();
+
+ for (let i=0; i < accounts.IDs.length; i++) {
+ // core async sync function, but we do not wait until it has finished,
+ // but return right away and initiate sync of all accounts parallel
+ this.syncAccount(accounts.IDs[i]);
+ }
+ },
+
+ syncAccount: async function (accountID, aSyncDescription = {}) {
+ let syncDescription = {};
+ Object.assign(syncDescription, aSyncDescription);
+
+ if (!syncDescription.hasOwnProperty("maxAccountReruns")) syncDescription.maxAccountReruns = 2;
+ if (!syncDescription.hasOwnProperty("maxFolderReruns")) syncDescription.maxFolderReruns = 2;
+ if (!syncDescription.hasOwnProperty("syncList")) syncDescription.syncList = true;
+ if (!syncDescription.hasOwnProperty("syncFolders")) syncDescription.syncFolders = null; // null ( = default = sync selected folders) or (empty) Array with folderData obj to be synced
+ if (!syncDescription.hasOwnProperty("syncJob")) syncDescription.syncJob = "sync";
+
+ //do not init sync if there is a sync running or account is not enabled
+ if (!this.isEnabled(accountID) || this.isSyncing(accountID)) return;
+
+ //create syncData object for each account (to be able to have parallel XHR)
+ this.resetSyncDataObj(accountID);
+ let syncData = this.getSyncDataObject(accountID);
+
+ //send GUI into lock mode (status == syncing)
+ TbSync.db.setAccountProperty(accountID, "status", "syncing");
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateAccountSettingsGui", accountID);
+
+ let overallStatusData = new TbSync.StatusData();
+ let accountRerun;
+ let accountRuns = 0;
+
+ do {
+ accountRerun = false;
+
+ if (accountRuns > syncDescription.maxAccountReruns) {
+ overallStatusData = new TbSync.StatusData(TbSync.StatusData.ERROR, "resync-loop");
+ break;
+ }
+ accountRuns++;
+
+ if (syncDescription.syncList) {
+ let listStatusData;
+ try {
+ listStatusData = await TbSync.providers[syncData.accountData.getAccountProperty("provider")].Base.syncFolderList(syncData, syncDescription.syncJob, accountRuns);
+ } catch (e) {
+ listStatusData = new TbSync.StatusData(TbSync.StatusData.WARNING, "JavaScriptError", e.message + "\n\n" + e.stack);
+ }
+
+ if (!(listStatusData instanceof TbSync.StatusData)) {
+ overallStatusData = new TbSync.StatusData(TbSync.StatusData.ERROR, "apiError", "TbSync/"+syncData.accountData.getAccountProperty("provider")+": Base.syncFolderList() must return a StatusData object");
+ break;
+ }
+
+ //if we have an error during folderList sync, there is no need to go on
+ if (listStatusData.type != TbSync.StatusData.SUCCESS) {
+ overallStatusData = listStatusData;
+ accountRerun = (listStatusData.type == TbSync.StatusData.ACCOUNT_RERUN)
+ TbSync.eventlog.add(listStatusData.type, syncData.eventLogInfo, listStatusData.message, listStatusData.details);
+ continue; //jumps to the while condition check
+ }
+
+ // Removes all leftover cached folders and sets all other folders to a well defined cached = "0"
+ // which will set this account as connected (if at least one non-cached folder is present).
+ this.removeCachedFolders(syncData);
+
+ // update folder list in GUI
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateFolderList", syncData.accountData.accountID);
+ }
+
+ // syncDescription.syncFolders is either null ( = default = sync selected folders) or an Array.
+ // Skip folder sync if Array is empty.
+ if (!Array.isArray(syncDescription.syncFolders) || syncDescription.syncFolders.length > 0) {
+ this.prepareFoldersForSync(syncData, syncDescription);
+
+ // update folder list in GUI
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateFolderList", syncData.accountData.accountID);
+
+ // if any folder was found, sync
+ if (syncData.accountData.isConnected()) {
+ let folderRuns = 1;
+ do {
+ if (folderRuns > syncDescription.maxFolderReruns) {
+ overallStatusData = new TbSync.StatusData(TbSync.StatusData.ERROR, "resync-loop");
+ break;
+ }
+
+ // getNextPendingFolder will set or clear currentFolderData of syncData
+ if (!this.getNextPendingFolder(syncData)) {
+ break;
+ }
+
+ let folderStatusData;
+ try {
+ folderStatusData = await TbSync.providers[syncData.accountData.getAccountProperty("provider")].Base.syncFolder(syncData, syncDescription.syncJob, folderRuns);
+ } catch (e) {
+ folderStatusData = new TbSync.StatusData(TbSync.StatusData.WARNING, "JavaScriptError", e.message + "\n\n" + e.stack);
+ }
+
+ if (!(folderStatusData instanceof TbSync.StatusData)) {
+ folderStatusData = new TbSync.StatusData(TbSync.StatusData.ERROR, "apiError", "TbSync/"+syncData.accountData.getAccountProperty("provider")+": Base.syncFolder() must return a StatusData object");
+ }
+
+ // if one of the folders indicated a FOLDER_RERUN, do not finish this
+ // folder but do it again
+ if (folderStatusData.type == TbSync.StatusData.FOLDER_RERUN) {
+ TbSync.eventlog.add(folderStatusData.type, syncData.eventLogInfo, folderStatusData.message, folderStatusData.details);
+ folderRuns++;
+ continue;
+ } else {
+ folderRuns = 1;
+ }
+
+ this.finishFolderSync(syncData, folderStatusData);
+
+ //if one of the folders indicated an ERROR, abort sync
+ if (folderStatusData.type == TbSync.StatusData.ERROR) {
+ break;
+ }
+
+ //if the folder has send an ACCOUNT_RERUN, abort sync and rerun the entire account
+ if (folderStatusData.type == TbSync.StatusData.ACCOUNT_RERUN) {
+ syncDescription.syncList = true;
+ accountRerun = true;
+ break;
+ }
+
+ } while (true);
+ } else {
+ overallStatusData = new TbSync.StatusData(TbSync.StatusData.ERROR, "no-folders-found-on-server");
+ }
+ }
+
+ } while (accountRerun);
+
+ this.finishAccountSync(syncData, overallStatusData);
+ },
+
+ // this could be added to AccountData, but I do not want that in public
+ setTargetModified: function (folderData) {
+ if (!folderData.accountData.isSyncing() && folderData.accountData.isEnabled()) {
+ folderData.accountData.setAccountProperty("status", "notsyncronized");
+ folderData.setFolderProperty("status", "modified");
+ //notify settings gui to update status
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ }
+ },
+
+ enableAccount: function(accountID) {
+ let accountData = new TbSync.AccountData(accountID);
+ TbSync.providers[accountData.getAccountProperty("provider")].Base.onEnableAccount(accountData);
+ accountData.setAccountProperty("status", "notsyncronized");
+ accountData.resetAccountProperty("lastsynctime");
+ },
+
+ disableAccount: function(accountID) {
+ let accountData = new TbSync.AccountData(accountID);
+ TbSync.providers[accountData.getAccountProperty("provider")].Base.onDisableAccount(accountData);
+ accountData.setAccountProperty("status", "disabled");
+
+ let folders = accountData.getAllFolders();
+ for (let folder of folders) {
+ if (folder.getFolderProperty("selected")) {
+ folder.targetData.removeTarget();
+ folder.setFolderProperty("selected", false);
+ }
+ folder.setFolderProperty("cached", true);
+ }
+ },
+
+ //removes all leftover cached folders and sets all other folders to a well defined cached = "0"
+ //which will set this account as connected (if at least one non-cached folder is present)
+ removeCachedFolders: function(syncData) {
+ let folders = syncData.accountData.getAllFoldersIncludingCache();
+ for (let folder of folders) {
+ //delete all leftover cached folders
+ if (folder.getFolderProperty("cached")) {
+ TbSync.db.deleteFolder(folder.accountID, folder.folderID);
+ continue;
+ } else {
+ //set well defined cache state
+ folder.setFolderProperty("cached", false);
+ }
+ }
+ },
+
+ //set allrequested folders to "pending", so they are marked for syncing
+ prepareFoldersForSync: function(syncData, syncDescription) {
+ let folders = syncData.accountData.getAllFolders();
+ for (let folder of folders) {
+ let requested = (Array.isArray(syncDescription.syncFolders) && syncDescription.syncFolders.filter(f => f.folderID == folder.folderID).length > 0);
+ let selected = (!Array.isArray(syncDescription.syncFolders) && folder.getFolderProperty("selected"));
+
+ //set folders to pending, so they get synced
+ if (requested || selected) {
+ folder.setFolderProperty("status", "pending");
+ }
+ }
+ },
+
+ finishFolderSync: function(syncData, statusData) {
+ if (statusData.type != TbSync.StatusData.SUCCESS) {
+ //report error
+ TbSync.eventlog.add(statusData.type, syncData.eventLogInfo, statusData.message, statusData.details);
+ }
+
+ //if this is a success, prepend success to the status message,
+ //otherwise just set the message
+ let status;
+ if (statusData.type == TbSync.StatusData.SUCCESS || statusData.message == "") {
+ status = statusData.type;
+ if (statusData.message) status = status + "." + statusData.message;
+ } else {
+ status = statusData.message;
+ }
+
+ if (syncData.currentFolderData) {
+ syncData.currentFolderData.setFolderProperty("status", status);
+ syncData.currentFolderData.setFolderProperty("lastsynctime", Date.now());
+ //clear folderID to fall back to account-only-mode (folder is done!)
+ syncData._clearCurrentFolderData();
+ }
+
+ syncData.setSyncState("done");
+ },
+
+ finishAccountSync: function(syncData, statusData) {
+ // set each folder with PENDING status to ABORTED
+ let folders = TbSync.db.findFolders({"status": "pending"}, {"accountID": syncData.accountData.accountID});
+ for (let i=0; i < folders.length; i++) {
+ TbSync.db.setFolderProperty(folders[i].accountID, folders[i].folderID, "status", "aborted");
+ }
+
+ //if this is a success, prepend success to the status message,
+ //otherwise just set the message
+ let status;
+ if (statusData.type == TbSync.StatusData.SUCCESS || statusData.message == "") {
+ status = statusData.type;
+ if (statusData.message) status = status + "." + statusData.message;
+ } else {
+ status = statusData.message;
+ }
+
+
+ if (statusData.type != TbSync.StatusData.SUCCESS) {
+ //report error
+ TbSync.eventlog.add("warning", syncData.eventLogInfo, statusData.message, statusData.details);
+ } else {
+ //account itself is ok, search for folders with error
+ folders = TbSync.db.findFolders({"selected": true, "cached": false}, {"accountID": syncData.accountData.accountID});
+ for (let i in folders) {
+ let folderstatus = folders[i].data.status.split(".")[0];
+ if (folderstatus != "" && folderstatus != TbSync.StatusData.SUCCESS && folderstatus != "aborted") {
+ status = "foldererror";
+ break;
+ }
+ }
+ }
+
+ //done
+ syncData.accountData.setAccountProperty("lastsynctime", Date.now());
+ syncData.accountData.setAccountProperty("status", status);
+ syncData.setSyncState("accountdone");
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateFolderList", syncData.accountData.accountID);
+ this.resetSyncDataObj(syncData.accountData.accountID);
+ }
+
+}
diff --git a/content/modules/db.js b/content/modules/db.js
new file mode 100644
index 0000000..c3f90c2
--- /dev/null
+++ b/content/modules/db.js
@@ -0,0 +1,465 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { DeferredTask } = ChromeUtils.import("resource://gre/modules/DeferredTask.jsm");
+
+var db = {
+
+ loaded: false,
+
+ files: {
+ accounts: {
+ name: "accounts68.json",
+ default: JSON.stringify({ sequence: 0, data : {} })
+ //data[account] = {row}
+ },
+ folders: {
+ name: "folders68.json",
+ default: JSON.stringify({})
+ //assoziative array of assoziative array : folders[<int>accountID][<string>folderID] = {row}
+ },
+ changelog: {
+ name: "changelog68.json",
+ default: JSON.stringify([]),
+ },
+ },
+
+ load: async function () {
+ //DB Concept:
+ //-- on application start, data is read async from json file into object
+ //-- add-on only works on object
+ //-- each time data is changed, an async write job is initiated <writeDelay>ms in the future and is resceduled, if another request arrives within that time
+
+ for (let f in this.files) {
+ this.files[f].write = new DeferredTask(() => this.writeAsync(f), 6000);
+
+ try {
+ let data = await OS.File.read(TbSync.io.getAbsolutePath(this.files[f].name));
+ this[f] = JSON.parse(TbSync.decoder.decode(data));
+ this.files[f].found = true;
+ } catch (e) {
+ //if there is no file, there is no file...
+ this[f] = JSON.parse(this.files[f].default);
+ this.files[f].found = false;
+ Components.utils.reportError(e);
+ }
+ }
+
+ function getNewDeviceId4Migration() {
+ //taken from https://jsfiddle.net/briguy37/2MVFd/
+ let d = new Date().getTime();
+ let uuid = 'xxxxxxxxxxxxxxxxyxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ let r = (d + Math.random()*16)%16 | 0;
+ d = Math.floor(d/16);
+ return (c=='x' ? r : (r&0x3|0x8)).toString(16);
+ });
+ return "MZTB" + uuid;
+ }
+
+ // try to migrate old accounts file from TB60
+ if (!this.files["accounts"].found) {
+ try {
+ let data = await OS.File.read(TbSync.io.getAbsolutePath("accounts.json"));
+ let accounts = JSON.parse(TbSync.decoder.decode(data));
+ for (let d of Object.values(accounts.data)) {
+ console.log("Migrating: " + JSON.stringify(d));
+
+ let settings = {};
+ settings.status = "disabled";
+ settings.provider = d.provider;
+ settings.https = (d.https == "1");
+
+ switch (d.provider) {
+ case "dav":
+ settings.calDavHost = d.host ? d.host : "";
+ settings.cardDavHost = d.host2 ? d.host2 : "";
+ settings.serviceprovider = d.serviceprovider;
+ settings.user = d.user;
+ settings.syncGroups = (d.syncGroups == "1");
+ settings.useCalendarCache = (d.useCache == "1");
+ break;
+
+ case "eas":
+ settings.useragent = d.useragent;
+ settings.devicetype = d.devicetype;
+ settings.deviceId = getNewDeviceId4Migration();
+ settings.asversionselected = d.asversionselected;
+ settings.asversion = d.asversion;
+ settings.host = d.host;
+ settings.user = d.user;
+ settings.servertype = d.servertype;
+ settings.seperator = d.seperator;
+ settings.provision = (d.provision == "1");
+ settings.displayoverride = (d.displayoverride == "1");
+ if (d.hasOwnProperty("galautocomplete")) settings.galautocomplete = (d.galautocomplete == "1");
+ break;
+ }
+
+ this.addAccount(d.accountname, settings);
+ }
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ this.loaded = true;
+ },
+
+ unload: async function () {
+ if (this.loaded) {
+ for (let f in this.files) {
+ try{
+ //abort write delay timers and write current file content to disk
+ await this.files[f].write.finalize();
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+ }
+ },
+
+
+ saveFile: function (f) {
+ if (this.loaded) {
+ //cancel any pending write and schedule a new delayed write
+ this.files[f].write.disarm();
+ this.files[f].write.arm();
+ }
+ },
+
+ writeAsync: async function (f) {
+ // if this file was not found/read on load, do not write default content to prevent clearing of data in case of read-errors
+ if (!this.files[f].found && JSON.stringify(this[f]) == this.files[f].default) {
+ return;
+ }
+
+ let filepath = TbSync.io.getAbsolutePath(this.files[f].name);
+ let json = TbSync.encoder.encode(JSON.stringify(this[f]));
+
+ await OS.File.makeDir(TbSync.io.storageDirectory);
+ await OS.File.writeAtomic(filepath, json, {tmpPath: filepath + ".tmp"});
+ },
+
+
+
+ // simple convenience wrapper
+ saveAccounts: function () {
+ this.saveFile("accounts");
+ },
+
+ saveFolders: function () {
+ this.saveFile("folders");
+ },
+
+ saveChangelog: function () {
+ this.saveFile("changelog");
+ },
+
+
+
+ // CHANGELOG FUNCTIONS
+ getItemStatusFromChangeLog: function (parentId, itemId) {
+ for (let i=0; i<this.changelog.length; i++) {
+ if (this.changelog[i].parentId == parentId && this.changelog[i].itemId == itemId) return this.changelog[i].status;
+ }
+ return null;
+ },
+
+ getItemDataFromChangeLog: function (parentId, itemId) {
+ for (let i=0; i<this.changelog.length; i++) {
+ if (this.changelog[i].parentId == parentId && this.changelog[i].itemId == itemId) return this.changelog[i];
+ }
+ return null;
+ },
+
+ addItemToChangeLog: function (parentId, itemId, status) {
+ this.removeItemFromChangeLog(parentId, itemId);
+
+ //ChangelogData object
+ let row = {
+ "parentId" : parentId,
+ "itemId" : itemId,
+ "timestamp": Date.now(),
+ "status" : status};
+
+ this.changelog.push(row);
+ this.saveChangelog();
+ },
+
+ removeItemFromChangeLog: function (parentId, itemId, moveToEnd = false) {
+ for (let i=this.changelog.length-1; i>-1; i-- ) {
+ if (this.changelog[i].parentId == parentId && this.changelog[i].itemId == itemId) {
+ let row = this.changelog.splice(i,1);
+ if (moveToEnd) this.changelog.push(row[0]);
+ this.saveChangelog();
+ return;
+ }
+ }
+ },
+
+ removeAllItemsFromChangeLogWithStatus: function (parentId, status) {
+ for (let i=this.changelog.length-1; i>-1; i-- ) {
+ if (this.changelog[i].parentId == parentId && this.changelog[i].status == status) {
+ let row = this.changelog.splice(i,1);
+ }
+ }
+ this.saveChangelog();
+ },
+
+ // Remove all cards of a parentId from ChangeLog
+ clearChangeLog: function (parentId) {
+ if (parentId) {
+ // we allow extra parameters added to a parentId, but still want to delete all items of that parent
+ // so we check for startsWith instead of equal
+ for (let i=this.changelog.length-1; i>-1; i-- ) {
+ if (this.changelog[i].parentId.startsWith(parentId)) this.changelog.splice(i,1);
+ }
+ this.saveChangelog();
+ }
+ },
+
+ getItemsFromChangeLog: function (parentId, maxnumbertosend, status = null) {
+ //maxnumbertosend = 0 will return all results
+ let log = [];
+ let counts = 0;
+ for (let i=0; i<this.changelog.length && (log.length < maxnumbertosend || maxnumbertosend == 0); i++) {
+ if (this.changelog[i].parentId == parentId && (status === null || (typeof this.changelog[i].status == "string" && this.changelog[i].status.indexOf(status) != -1))) log.push(this.changelog[i]);
+ }
+ return log;
+ },
+
+
+
+
+
+ // ACCOUNT FUNCTIONS
+
+ addAccount: function (accountname, newAccountEntry) {
+ this.accounts.sequence++;
+ let id = this.accounts.sequence.toString();
+ newAccountEntry.accountID = id;
+ newAccountEntry.accountname = accountname;
+
+ this.accounts.data[id] = newAccountEntry;
+ this.saveAccounts();
+ return id;
+ },
+
+ removeAccount: function (accountID) {
+ //check if accountID is known
+ if (this.accounts.data.hasOwnProperty(accountID) == false ) {
+ throw "Unknown accountID!" + "\nThrown by db.removeAccount("+accountID+ ")";
+ } else {
+ delete (this.accounts.data[accountID]);
+ delete (this.folders[accountID]);
+ this.saveAccounts();
+ this.saveFolders();
+ }
+ },
+
+ getAccounts: function () {
+ let accounts = {};
+ accounts.IDs = Object.keys(this.accounts.data).filter(accountID => TbSync.providers.loadedProviders.hasOwnProperty(this.accounts.data[accountID].provider)).sort((a, b) => a - b);
+ accounts.allIDs = Object.keys(this.accounts.data).sort((a, b) => a - b)
+ accounts.data = this.accounts.data;
+ return accounts;
+ },
+
+ getAccount: function (accountID) {
+ //check if accountID is known
+ if (this.accounts.data.hasOwnProperty(accountID) == false ) {
+ throw "Unknown accountID!" + "\nThrown by db.getAccount("+accountID+ ")";
+ } else {
+ return this.accounts.data[accountID];
+ }
+ },
+
+ isValidAccountProperty: function (provider, name) {
+ if (["provider"].includes(name)) //internal properties, do not need to be defined by user/provider
+ return true;
+
+ //check if provider is installed
+ if (!TbSync.providers.loadedProviders.hasOwnProperty(provider)) {
+ TbSync.dump("Error @ isValidAccountProperty", "Unknown provider <"+provider+">!");
+ return false;
+ }
+
+ if (TbSync.providers.getDefaultAccountEntries(provider).hasOwnProperty(name)) {
+ return true;
+ } else {
+ TbSync.dump("Error @ isValidAccountProperty", "Unknown account setting <"+name+">!");
+ return false;
+ }
+ },
+
+ getAccountProperty: function (accountID, name) {
+ // if the requested accountID does not exist, getAccount() will fail
+ let data = this.getAccount(accountID);
+
+ //check if field is allowed and get value or default value if setting is not set
+ if (this.isValidAccountProperty(data.provider, name)) {
+ if (data.hasOwnProperty(name)) return data[name];
+ else return TbSync.providers.getDefaultAccountEntries(data.provider)[name];
+ }
+ },
+
+ setAccountProperty: function (accountID , name, value) {
+ // if the requested accountID does not exist, getAccount() will fail
+ let data = this.getAccount(accountID);
+
+ //check if field is allowed, and set given value
+ if (this.isValidAccountProperty(data.provider, name)) {
+ this.accounts.data[accountID][name] = value;
+ }
+ this.saveAccounts();
+ },
+
+ resetAccountProperty: function (accountID , name) {
+ // if the requested accountID does not exist, getAccount() will fail
+ let data = this.getAccount(accountID);
+ let defaults = TbSync.providers.getDefaultAccountEntries(data.provider);
+
+ //check if field is allowed, and set given value
+ if (this.isValidAccountProperty(data.provider, name)) {
+ this.accounts.data[accountID][name] = defaults[name];
+ }
+ this.saveAccounts();
+ },
+
+
+
+
+ // FOLDER FUNCTIONS
+
+ addFolder: function(accountID) {
+ let folderID = TbSync.generateUUID();
+ let provider = this.getAccountProperty(accountID, "provider");
+
+ if (!this.folders.hasOwnProperty(accountID)) this.folders[accountID] = {};
+
+ //create folder with default settings
+ this.folders[accountID][folderID] = TbSync.providers.getDefaultFolderEntries(accountID);
+ this.saveFolders();
+ return folderID;
+ },
+
+ deleteFolder: function(accountID, folderID) {
+ delete (this.folders[accountID][folderID]);
+ //if there are no more folders, delete entire account entry
+ if (Object.keys(this.folders[accountID]).length === 0) delete (this.folders[accountID]);
+ this.saveFolders();
+ },
+
+ isValidFolderProperty: function (accountID, field) {
+ if (["cached"].includes(field)) //internal properties, do not need to be defined by user/provider
+ return true;
+
+ //check if provider is installed
+ let provider = this.getAccountProperty(accountID, "provider");
+ if (!TbSync.providers.loadedProviders.hasOwnProperty(provider)) {
+ TbSync.dump("Error @ isValidFolderProperty", "Unknown provider <"+provider+"> for accountID <"+accountID+">!");
+ return false;
+ }
+
+ if (TbSync.providers.getDefaultFolderEntries(accountID).hasOwnProperty(field)) {
+ return true;
+ } else {
+ TbSync.dump("Error @ isValidFolderProperty", "Unknown folder setting <"+field+"> for accountID <"+accountID+">!");
+ return false;
+ }
+ },
+
+ getFolderProperty: function(accountID, folderID, field) {
+ //does the field exist?
+ let folder = (this.folders.hasOwnProperty(accountID) && this.folders[accountID].hasOwnProperty(folderID)) ? this.folders[accountID][folderID] : null;
+
+ if (folder === null) {
+ throw "Unknown folder <"+folderID+">!";
+ }
+
+ if (this.isValidFolderProperty(accountID, field)) {
+ if (folder.hasOwnProperty(field)) {
+ return folder[field];
+ } else {
+ let provider = this.getAccountProperty(accountID, "provider");
+ let defaultFolder = TbSync.providers.getDefaultFolderEntries(accountID);
+ //handle internal fields, that do not have a default value (see isValidFolderProperty)
+ return (defaultFolder[field] ? defaultFolder[field] : "");
+ }
+ }
+ },
+
+ setFolderProperty: function (accountID, folderID, field, value) {
+ if (this.isValidFolderProperty(accountID, field)) {
+ this.folders[accountID][folderID][field] = value;
+ this.saveFolders();
+ }
+ },
+
+ resetFolderProperty: function (accountID, folderID, field) {
+ let provider = this.getAccountProperty(accountID, "provider");
+ let defaults = TbSync.providers.getDefaultFolderEntries(accountID);
+ if (this.isValidFolderProperty(accountID, field)) {
+ //handle internal fields, that do not have a default value (see isValidFolderProperty)
+ this.folders[accountID][folderID][field] = defaults[field] ? defaults[field] : "";
+ this.saveFolders();
+ }
+ },
+
+ findFolders: function (folderQuery = {}, accountQuery = {}) {
+ // folderQuery is an object with one or more key:value pairs (logical AND) ::
+ // {key1: value1, key2: value2}
+ // the value itself may be an array (logical OR)
+ let data = [];
+ let folderQueryEntries = Object.entries(folderQuery);
+ let folderFields = folderQueryEntries.map(pair => pair[0]);
+ let folderValues = folderQueryEntries.map(pair => Array.isArray(pair[1]) ? pair[1] : [pair[1]]);
+
+ let accountQueryEntries = Object.entries(accountQuery);
+ let accountFields = accountQueryEntries.map(pair => pair[0]);
+ let accountValues = accountQueryEntries.map(pair => Array.isArray(pair[1]) ? pair[1] : [pair[1]]);
+
+ for (let aID in this.folders) {
+ //is this a leftover folder of an account, which no longer there?
+ if (!this.accounts.data.hasOwnProperty(aID)) {
+ delete (this.folders[aID]);
+ this.saveFolders();
+ continue;
+ }
+
+ //skip this folder, if it belongs to an account currently not supported (provider not loaded)
+ if (!TbSync.providers.loadedProviders.hasOwnProperty(this.getAccountProperty(aID, "provider"))) {
+ continue;
+ }
+
+ //does this account match account search options?
+ let accountmatch = true;
+ for (let a = 0; a < accountFields.length && accountmatch; a++) {
+ accountmatch = accountValues[a].some(item => item === this.getAccountProperty(aID, accountFields[a]));
+ //Services.console.logStringMessage(" " + accountFields[a] + ":" + this.getAccountProperty(aID, accountFields[a]) + " in " + JSON.stringify(accountValues[a]) + " ? " + accountmatch);
+ }
+
+ if (accountmatch) {
+ for (let fID in this.folders[aID]) {
+ //does this folder match folder search options?
+ let foldermatch = true;
+ for (let f = 0; f < folderFields.length && foldermatch; f++) {
+ foldermatch = folderValues[f].some(item => item === this.getFolderProperty(aID, fID, folderFields[f]));
+ //Services.console.logStringMessage(" " + folderFields[f] + ":" + this.getFolderProperty(aID, fID, folderFields[f]) + " in " + JSON.stringify(folderValues[f]) + " ? " + foldermatch);
+ }
+ if (foldermatch) data.push({accountID: aID, folderID: fID, data: this.folders[aID][fID]});
+ }
+ }
+ }
+
+ //still a reference to the original data
+ return data;
+ }
+};
diff --git a/content/modules/eventlog.js b/content/modules/eventlog.js
new file mode 100644
index 0000000..ef8f1e8
--- /dev/null
+++ b/content/modules/eventlog.js
@@ -0,0 +1,153 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+/**
+ *
+ */
+var EventLogInfo = class {
+ /**
+ * An EventLogInfo instance is used when adding entries to the
+ * :ref:`TbSyncEventLog`. The information given here will be added as a
+ * header to the actual event.
+ *
+ * @param {string} provider ``Optional`` A provider ID (also used as
+ * provider namespace).
+ * @param {string} accountname ``Optional`` An account name. Can be
+ * arbitrary but should match the accountID
+ * (if provided).
+ * @param {string} accountID ``Optional`` An account ID. Used to filter
+ * events for a given account.
+ * @param {string} foldername ``Optional`` A folder name.
+ *
+ */
+ constructor(provider, accountname = "", accountID = "", foldername = "") {
+ this._provider = provider;
+ this._accountname = accountname;
+ this._accountID = accountID;
+ this._foldername = foldername;
+ }
+
+ /**
+ * Getter/Setter for the provider ID of this EventLogInfo.
+ */
+ get provider() {return this._provider};
+ /**
+ * Getter/Setter for the account ID of this EventLogInfo.
+ */
+ get accountname() {return this._accountname};
+ /**
+ * Getter/Setter for the account name of this EventLogInfo.
+ */
+ get accountID() {return this._accountID};
+ /**
+ * Getter/Setter for the folder name of this EventLogInfo.
+ */
+ get foldername() {return this._foldername};
+
+ set provider(v) {this._provider = v};
+ set accountname(v) {this._accountname = v};
+ set accountID(v) {this._accountID = v};
+ set foldername(v) {this._foldername = v};
+}
+
+
+
+/**
+ * The TbSync event log
+ */
+var eventlog = {
+ /**
+ * Adds an entry to the TbSync event log
+ *
+ * @param {StatusDataType} type One of the types defined in
+ * :class:`StatusData`
+ * @param {EventLogInfo} eventInfo EventLogInfo for this event.
+ * @param {string} message The event message.
+ * @param {string} details ``Optional`` The event details.
+ *
+ */
+ add: function (type, eventInfo, message, details = null) {
+ let entry = {
+ timestamp: Date.now(),
+ message: message,
+ type: type,
+ link: null,
+ //some details are just true, which is not a useful detail, ignore
+ details: details === true ? null : details,
+ provider: "",
+ accountname: "",
+ foldername: "",
+ };
+
+ if (eventInfo) {
+ if (eventInfo.accountID) entry.accountID = eventInfo.accountID;
+ if (eventInfo.provider) entry.provider = eventInfo.provider;
+ if (eventInfo.accountname) entry.accountname = eventInfo.accountname;
+ if (eventInfo.foldername) entry.foldername = eventInfo.foldername;
+ }
+
+ let localized = "";
+ let link = "";
+ if (entry.provider) {
+ localized = TbSync.getString("status." + message, entry.provider);
+ link = TbSync.getString("helplink." + message, entry.provider);
+ } else {
+ //try to get localized string from message from TbSync
+ localized = TbSync.getString("status." + message);
+ link = TbSync.getString("helplink." + message);
+ }
+
+ //can we provide a localized version of the event msg?
+ if (localized != "status."+message) {
+ entry.message = localized;
+ }
+
+ //is there a help link?
+ if (link != "helplink." + message) {
+ entry.link = link;
+ }
+
+ //dump the non-localized message into debug log
+ TbSync.dump("EventLog", message + (entry.details !== null ? "\n" + entry.details : ""));
+ this.events.push(entry);
+ if (this.events.length > 100) this.events.shift();
+ Services.obs.notifyObservers(null, "tbsync.observer.eventlog.update", null);
+ },
+
+ events: null,
+ eventLogWindow: null,
+
+ load: async function () {
+ this.clear();
+ },
+
+ unload: async function () {
+ if (this.eventLogWindow) {
+ this.eventLogWindow.close();
+ }
+ },
+
+ get: function (accountID = null) {
+ if (accountID) {
+ return this.events.filter(e => e.accountID == accountID);
+ } else {
+ return this.events;
+ }
+ },
+
+ clear: function () {
+ this.events = [];
+ },
+
+
+ open: function (accountID = null, folderID = null) {
+ this.eventLogWindow = TbSync.manager.prefWindowObj.open("chrome://tbsync/content/manager/eventlog/eventlog.xhtml", "TbSyncEventLog", "centerscreen,chrome,resizable");
+ },
+}
diff --git a/content/modules/io.js b/content/modules/io.js
new file mode 100644
index 0000000..960454b
--- /dev/null
+++ b/content/modules/io.js
@@ -0,0 +1,41 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var io = {
+
+ storageDirectory : OS.Path.join(OS.Constants.Path.profileDir, "TbSync"),
+
+ load: async function () {
+ },
+
+ unload: async function () {
+ },
+
+ getAbsolutePath: function(filename) {
+ return OS.Path.join(this.storageDirectory, filename);
+ },
+
+ initFile: function (filename) {
+ let file = FileUtils.getFile("ProfD", ["TbSync",filename]);
+ //create a stream to write to that file
+ let foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+ foStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0666", 8), 0); // write, create, truncate
+ foStream.close();
+ },
+
+ appendToFile: function (filename, data) {
+ let file = FileUtils.getFile("ProfD", ["TbSync",filename]);
+ //create a strem to write to that file
+ let foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+ foStream.init(file, 0x02 | 0x08 | 0x10, parseInt("0666", 8), 0); // write, create, append
+ foStream.write(data, data.length);
+ foStream.close();
+ },
+}
diff --git a/content/modules/lightning.js b/content/modules/lightning.js
new file mode 100644
index 0000000..61b7ef0
--- /dev/null
+++ b/content/modules/lightning.js
@@ -0,0 +1,766 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var lightning = {
+
+ cal: null,
+ ICAL: null,
+
+ load: async function () {
+ try {
+ TbSync.lightning.cal = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm").cal;
+ TbSync.lightning.ICAL = ChromeUtils.import("resource:///modules/calendar/Ical.jsm").ICAL;
+ TbSync.lightning.cal.getCalendarManager().addCalendarObserver(this.calendarObserver);
+ TbSync.lightning.cal.getCalendarManager().addObserver(this.calendarManagerObserver);
+ } catch (e) {
+ TbSync.dump("Check4Lightning","Error during lightning module import: " + e.toString() + "\n" + e.stack);
+ Components.utils.reportError(e);
+ }
+ },
+
+ unload: async function () {
+ //removing global observer
+ TbSync.lightning.cal.getCalendarManager().removeCalendarObserver(this.calendarObserver);
+ TbSync.lightning.cal.getCalendarManager().removeObserver(this.calendarManagerObserver);
+
+ //remove listeners on global sync buttons
+ if (TbSync.window.document.getElementById("calendar-synchronize-button")) {
+ TbSync.window.document.getElementById("calendar-synchronize-button").removeEventListener("click", function(event){Services.obs.notifyObservers(null, 'tbsync.observer.sync', null);}, false);
+ }
+ if (TbSync.window.document.getElementById("task-synchronize-button")) {
+ TbSync.window.document.getElementById("task-synchronize-button").removeEventListener("click", function(event){Services.obs.notifyObservers(null, 'tbsync.observer.sync', null);}, false);
+ }
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * AdvancedTargetData, an extended TargetData implementation, providers
+ // * can use this as their own TargetData by extending it and just
+ // * defining the extra methods
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ AdvancedTargetData : class {
+ constructor(folderData) {
+ this._folderData = folderData;
+ this._targetObj = null;
+ }
+
+ // Check, if the target exists and return true/false.
+ hasTarget() {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ return calendar ? true : false;
+ }
+
+ // Returns the target obj, which TbSync should return as the target. It can
+ // be whatever you want and is returned by FolderData.targetData.getTarget().
+ // If the target does not exist, it should be created. Throw a simple Error, if that
+ // failed.
+ async getTarget() {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ if (!calendar) {
+ calendar = await TbSync.lightning.prepareAndCreateCalendar(this._folderData);
+ if (!calendar)
+ throw new Error("notargets");
+ }
+
+ if (!this._targetObj || this._targetObj.id != calendar.id)
+ this._targetObj = new TbSync.lightning.TbCalendar(calendar, this._folderData);
+
+ return this._targetObj;
+ }
+
+ /**
+ * Removes the target from the local storage. If it does not exist, return
+ * silently. A call to ``hasTarget()`` should return false, after this has
+ * been executed.
+ *
+ */
+ removeTarget() {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ try {
+ if (calendar) {
+ TbSync.lightning.cal.getCalendarManager().removeCalendar(calendar);
+ }
+ } catch (e) {}
+ TbSync.db.clearChangeLog(target);
+ this._folderData.resetFolderProperty("target");
+ }
+
+
+ /**
+ * Disconnects the target in the local storage from this TargetData, but
+ * does not delete it, so it becomes a stale "left over" . A call
+ * to ``hasTarget()`` should return false, after this has been executed.
+ *
+ */
+ disconnectTarget() {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ if (calendar) {
+ let changes = TbSync.db.getItemsFromChangeLog(target, 0, "_by_user");
+ if (changes.length > 0) {
+ this.targetName = this.targetName + " (*)";
+ }
+ calendar.setProperty("disabled", true);
+ calendar.setProperty("tbSyncProvider", "orphaned");
+ calendar.setProperty("tbSyncAccountID", "");
+ }
+ TbSync.db.clearChangeLog(target);
+ this._folderData.resetFolderProperty("target");
+ }
+
+ set targetName(newName) {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ if (calendar) {
+ calendar.name = newName;
+ } else {
+ throw new Error("notargets");
+ }
+ }
+
+ get targetName() {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let target = this._folderData.getFolderProperty("target");
+ let calendar = calManager.getCalendarById(target);
+
+ if (calendar) {
+ return calendar.name;
+ } else {
+ throw new Error("notargets");
+ }
+ }
+
+ setReadOnly(value) {
+ // hasTarget() can throw an error, ignore that here
+ try {
+ if (this.hasTarget()) {
+ this.getTarget().then(target => target.calendar.setProperty("readOnly", value));
+ }
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+
+ // * * * * * * * * * * * * * * * * *
+ // * AdvancedTargetData extension *
+ // * * * * * * * * * * * * * * * * *
+
+ get isAdvancedCalendarTargetData() {
+ return true;
+ }
+
+ get folderData() {
+ return this._folderData;
+ }
+
+ // The calendar target does not support a custom primaryKeyField, because
+ // the lightning implementation only allows to search for items via UID.
+ // Like the addressbook target, the calendar target item element has a
+ // primaryKey getter/setter which - however - only works on the UID.
+
+ // enable or disable changelog
+ get logUserChanges(){
+ return true;
+ }
+
+ calendarObserver(aTopic, tbCalendar, aPropertyName, aPropertyValue, aOldPropertyValue) {
+ switch (aTopic) {
+ case "onCalendarPropertyChanged":
+ //Services.console.logStringMessage("["+ aTopic + "] " + tbCalendar.calendar.name + " : " + aPropertyName);
+ break;
+
+ case "onCalendarDeleted":
+ case "onCalendarPropertyDeleted":
+ //Services.console.logStringMessage("["+ aTopic + "] " +tbCalendar.calendar.name);
+ break;
+ }
+ }
+
+ itemObserver(aTopic, tbItem, tbOldItem) {
+ switch (aTopic) {
+ case "onAddItem":
+ case "onModifyItem":
+ case "onDeleteItem":
+ //Services.console.logStringMessage("["+ aTopic + "] " + tbItem.nativeItem.title);
+ break;
+ }
+ }
+
+ // replace this with your own implementation to create the actual addressbook,
+ // when this class is extended
+ async createCalendar(newname) {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let newCalendar = calManager.createCalendar("storage", Services.io.newURI("moz-storage-calendar://"));
+ newCalendar.id = TbSync.lightning.cal.getUUID();
+ newCalendar.name = newname;
+ return newCalendar
+ }
+
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * TbItem and TbCalendar Classes
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ TbItem : class {
+ constructor(TbCalendar, item) {
+ if (!TbCalendar)
+ throw new Error("TbItem::constructor is missing its first parameter!");
+
+ if (!item)
+ throw new Error("TbItem::constructor is missing its second parameter!");
+
+ this._tbCalendar = TbCalendar;
+ this._item = item;
+
+ this._isTodo = (item instanceof Ci.calITodo);
+ this._isEvent = (item instanceof Ci.calIEvent);
+ }
+
+ get tbCalendar() {
+ return this._tbCalendar;
+ }
+
+ get isTodo() {
+ return this._isTodo;
+ }
+
+ get isEvent() {
+ return this._isEvent;
+ }
+
+
+
+
+
+ get nativeItem() {
+ return this._item;
+ }
+
+ get UID() {
+ return this._item.id;
+ }
+
+ get primaryKey() {
+ // no custom key possible with lightning, must use the UID
+ return this._item.id;
+ }
+
+ set primaryKey(value) {
+ // no custom key possible with lightning, must use the UID
+ this._item.id = value;
+ }
+
+ clone() {
+ return new TbSync.lightning.TbItem(this._tbCalendar, this._item.clone());
+ }
+
+ toString() {
+ return this._item.icalString;
+ }
+
+ getProperty(property, fallback = "") {
+ return this._item.hasProperty(property) ? this._item.getProperty(property) : fallback;
+ }
+
+ setProperty(property, value) {
+ this._item.setProperty(property, value);
+ }
+
+ deleteProperty(property) {
+ this._item.deleteProperty(property);
+ }
+
+ get changelogData() {
+ return TbSync.db.getItemDataFromChangeLog(this._tbCalendar.UID, this.primaryKey);
+ }
+
+ get changelogStatus() {
+ return TbSync.db.getItemStatusFromChangeLog(this._tbCalendar.UID, this.primaryKey);
+ }
+
+ set changelogStatus(status) {
+ let value = this.primaryKey;
+
+ if (value) {
+ if (!status) {
+ TbSync.db.removeItemFromChangeLog(this._tbCalendar.UID, value);
+ return;
+ }
+
+ if (this._tbCalendar.logUserChanges || status.endsWith("_by_server")) {
+ TbSync.db.addItemToChangeLog(this._tbCalendar.UID, value, status);
+ }
+ }
+ }
+ },
+
+
+ TbCalendar : class {
+ constructor(calendar, folderData) {
+ this._calendar = calendar;
+ this._promisifyCalendar = TbSync.lightning.cal.async.promisifyCalendar(this._calendar.wrappedJSObject);
+ this._folderData = folderData;
+ }
+
+ get calendar() {
+ return this._calendar;
+ }
+
+ get promisifyCalendar() {
+ return this._promisifyCalendar;
+ }
+
+ get logUserChanges() {
+ return this._folderData.targetData.logUserChanges;
+ }
+
+ get primaryKeyField() {
+ // Not supported by lightning. We let the implementation sit here, it may get changed in the future.
+ // In order to support this, lightning needs to implement a proper getItemfromProperty() method.
+ return null;
+ }
+
+ get UID() {
+ return this._calendar.id;
+ }
+
+ createNewEvent() {
+ let event = TbSync.lightning.cal.createEvent();
+ return new TbSync.lightning.TbItem(this, event);
+ }
+
+ createNewTodo() {
+ let todo = TbSync.lightning.cal.createTodo();
+ return new TbSync.lightning.TbItem(this, todo);
+ }
+
+
+
+
+ async addItem(tbItem, pretagChangelogWithByServerEntry = true) {
+ if (this.primaryKeyField && !tbItem.getProperty(this.primaryKeyField)) {
+ tbItem.setProperty(this.primaryKeyField, this._folderData.targetData.generatePrimaryKey());
+ //Services.console.logStringMessage("[TbCalendar::addItem] Generated primary key!");
+ }
+
+ if (pretagChangelogWithByServerEntry) {
+ tbItem.changelogStatus = "added_by_server";
+ }
+ return await this._promisifyCalendar.adoptItem(tbItem._item);
+ }
+
+ async modifyItem(tbNewItem, tbOldItem, pretagChangelogWithByServerEntry = true) {
+ // only add entry if the current entry does not start with _by_user
+ let status = tbNewItem.changelogStatus ? tbNewItem.changelogStatus : "";
+ if (pretagChangelogWithByServerEntry && !status.endsWith("_by_user")) {
+ tbNewItem.changelogStatus = "modified_by_server";
+ }
+
+ return await this._promisifyCalendar.modifyItem(tbNewItem._item, tbOldItem._item);
+ }
+
+ async deleteItem(tbItem, pretagChangelogWithByServerEntry = true) {
+ if (pretagChangelogWithByServerEntry) {
+ tbItem.changelogStatus = "deleted_by_server";
+ }
+ return await this._promisifyCalendar.deleteItem(tbItem._item);
+ }
+
+ // searchId is interpreted as the primaryKeyField, which is the UID for this target
+ async getItem (searchId) {
+ let item = await this._promisifyCalendar.getItem(searchId);
+ if (item.length == 1) return new TbSync.lightning.TbItem(this, item[0]);
+ if (item.length > 1) throw "Oops: getItem returned <"+item.length+"> elements!";
+ return null;
+ }
+
+ async getItemFromProperty(property, value) {
+ if (property == "UID") return await this.getItem(value);
+ else throw ("TbSync.lightning.getItemFromProperty: Currently onle the UID property can be used to search for items.");
+ }
+
+ async getAllItems () {
+ return await this._promisifyCalendar.getAllItems();
+ }
+
+
+
+
+
+ getAddedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this.calendar.id, maxitems, "added_by_user").map(item => item.itemId);
+ }
+
+ getModifiedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this.calendar.id, maxitems, "modified_by_user").map(item => item.itemId);
+ }
+
+ getDeletedItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this.calendar.id, maxitems, "deleted_by_user").map(item => item.itemId);
+ }
+
+ getItemsFromChangeLog(maxitems = 0) {
+ return TbSync.db.getItemsFromChangeLog(this.calendar.id, maxitems, "_by_user");
+ }
+
+ removeItemFromChangeLog(id, moveToEndInsteadOfDelete = false) {
+ TbSync.db.removeItemFromChangeLog(this.calendar.id, id, moveToEndInsteadOfDelete);
+ }
+
+ clearChangelog() {
+ TbSync.db.clearChangeLog(this.calendar.id);
+ }
+ },
+
+
+
+
+
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ // * Internal Functions
+ // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+ getFolderFromCalendarUID: function(calUID) {
+ let folders = TbSync.db.findFolders({"target": calUID});
+ if (folders.length == 1) {
+ let accountData = new TbSync.AccountData(folders[0].accountID);
+ return new TbSync.FolderData(accountData, folders[0].folderID);
+ }
+ return null;
+ },
+
+ getFolderFromCalendarURL: function(calURL) {
+ let folders = TbSync.db.findFolders({"url": calURL});
+ if (folders.length == 1) {
+ let accountData = new TbSync.AccountData(folders[0].accountID);
+ return new TbSync.FolderData(accountData, folders[0].folderID);
+ }
+ return null;
+ },
+
+ calendarObserver : {
+ onStartBatch : function () {},
+ onEndBatch : function () {},
+ onLoad : function (aCalendar) {},
+ onError : function (aCalendar, aErrNo, aMessage) {},
+
+ onAddItem : function (aAddedItem) {
+ if (!(aAddedItem && aAddedItem.calendar))
+ return;
+
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aAddedItem.calendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aAddedItem.calendar, folderData);
+ let tbItem = new TbSync.lightning.TbItem(tbCalendar, aAddedItem);
+ let itemStatus = tbItem.changelogStatus;
+
+ // if this card was created by us, it will be in the log
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ let age = Date.now() - tbItem.changelogData.timestamp;
+ if (age < 1500) {
+ // during freeze, local modifications are not possible
+ return;
+ } else {
+ // remove blocking entry from changelog after freeze time is over (1.5s),
+ // and continue evaluating this event
+ abItem.changelogStatus = "";
+ }
+ }
+
+ if (itemStatus == "deleted_by_user") {
+ // deleted ? user moved item out and back in -> modified
+ tbItem.changelogStatus = "modified_by_user";
+ } else {
+ tbItem.changelogStatus = "added_by_user";
+ }
+
+ if (tbCalendar.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.itemObserver("onAddItem", tbItem, null);
+ }
+ },
+
+ onModifyItem : function (aNewItem, aOldItem) {
+ //check, if it is a pure modification within the same calendar
+ if (!(aNewItem && aNewItem.calendar && aOldItem && aOldItem.calendar && aNewItem.calendar.id == aOldItem.calendar.id))
+ return;
+
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aNewItem.calendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aNewItem.calendar, folderData);
+ let tbNewItem = new TbSync.lightning.TbItem(tbCalendar, aNewItem);
+ let tbOldItem = new TbSync.lightning.TbItem(tbCalendar, aOldItem);
+ let itemStatus = tbNewItem.changelogStatus;
+
+ // if this card was created by us, it will be in the log
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ let age = Date.now() - tbNewItem.changelogData.timestamp;
+ if (age < 1500) {
+ // during freeze, local modifications are not possible
+ return;
+ } else {
+ // remove blocking entry from changelog after freeze time is over (1.5s),
+ // and continue evaluating this event
+ tbNewItem.changelogStatus = "";
+ }
+ }
+
+ if (itemStatus != "added_by_user") {
+ //added_by_user -> it is a local unprocessed add do not re-add it to changelog
+ tbNewItem.changelogStatus = "modified_by_user";
+ }
+
+ if (tbCalendar.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.itemObserver("onModifyItem", tbNewItem, tbOldItem);
+ }
+ },
+
+ onDeleteItem : function (aDeletedItem) {
+ if (!(aDeletedItem && aDeletedItem.calendar))
+ return;
+
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aDeletedItem.calendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aDeletedItem.calendar, folderData);
+ let tbItem = new TbSync.lightning.TbItem(tbCalendar, aDeletedItem);
+ let itemStatus = tbItem.changelogStatus;
+
+ // if this card was created by us, it will be in the log
+ if (itemStatus && itemStatus.endsWith("_by_server")) {
+ let age = Date.now() - tbItem.changelogData.timestamp;
+ if (age < 1500) {
+ // during freeze, local modifications are not possible
+ return;
+ } else {
+ // remove blocking entry from changelog after freeze time is over (1.5s),
+ // and continue evaluating this event
+ tbItem.changelogStatus = "";
+ }
+ }
+
+ if (itemStatus == "added_by_user") {
+ //a local add, which has not yet been processed (synced) is deleted -> remove all traces
+ tbItem.changelogStatus = "";
+ } else {
+ tbItem.changelogStatus = "deleted_by_user";
+ }
+
+ if (tbCalendar.logUserChanges) TbSync.core.setTargetModified(folderData);
+ folderData.targetData.itemObserver("onDeleteItem", tbItem, null);
+ }
+ },
+
+ //Changed properties of the calendar itself (name, color etc.)
+ onPropertyChanged : function (aCalendar, aName, aValue, aOldValue) {
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aCalendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aCalendar, folderData);
+
+ switch (aName) {
+ case "color":
+ // update stored color to recover after disable
+ folderData.setFolderProperty("targetColor", aValue);
+ break;
+ case "name":
+ // update stored name to recover after disable
+ folderData.setFolderProperty("targetName", aValue);
+ // update settings window, if open
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ break;
+ }
+
+ folderData.targetData.calendarObserver("onCalendarPropertyChanged", tbCalendar, aName, aValue, aOldValue);
+ }
+ },
+
+ //Deleted properties of the calendar itself (name, color etc.)
+ onPropertyDeleting : function (aCalendar, aName) {
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aCalendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aCalendar, folderData);
+
+ switch (aName) {
+ case "color":
+ case "name":
+ //update settings window, if open
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ break;
+ }
+
+ folderData.targetData.calendarObserver("onCalendarPropertyDeleted", tbCalendar, aName);
+ }
+ }
+ },
+
+ calendarManagerObserver : {
+ onCalendarRegistered : function (aCalendar) {
+ },
+
+ onCalendarUnregistering : function (aCalendar) {
+ /*let folderData = TbSync.lightning.getFolderFromCalendarUID(aCalendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ folderData.targetData.calendarObserver("onCalendarUnregistered", aCalendar);
+ }*/
+ },
+
+ onCalendarDeleting : async function (aCalendar) {
+ let folderData = TbSync.lightning.getFolderFromCalendarUID(aCalendar.id);
+ if (folderData
+ && folderData.targetData
+ && folderData.targetData.isAdvancedCalendarTargetData) {
+
+ // If the user switches "offline support", the calendar is deleted and recreated. Thus,
+ // we wait a bit and check, if the calendar is back again and ignore the delete event.
+ if (aCalendar.type == "caldav") {
+ await TbSync.tools.sleep(1500);
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ for (let calendar of calManager.getCalendars({})) {
+ if (calendar.uri.spec == aCalendar.uri.spec) {
+ // update the target
+ folderData.setFolderProperty("target", calendar.id)
+ return;
+ }
+ }
+ }
+
+ //delete any pending changelog of the deleted calendar
+ TbSync.db.clearChangeLog(aCalendar.id);
+
+ let tbCalendar = new TbSync.lightning.TbCalendar(aCalendar, folderData);
+
+ //unselect calendar if deleted by user and update settings window, if open
+ if (folderData.getFolderProperty("selected")) {
+ folderData.setFolderProperty("selected", false);
+ //update settings window, if open
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folderData.accountID);
+ }
+
+ folderData.resetFolderProperty("target");
+ folderData.targetData.calendarObserver("onCalendarDeleted", tbCalendar);
+
+ }
+ },
+ },
+
+
+
+ //this function actually creates a calendar if missing
+ prepareAndCreateCalendar: async function (folderData) {
+ let calManager = TbSync.lightning.cal.getCalendarManager();
+ let provider = folderData.accountData.getAccountProperty("provider");
+
+ //check if there is a known/cached name, and use that as starting point to generate unique name for new calendar
+ let cachedName = folderData.getFolderProperty("targetName");
+ let newname = cachedName == "" ? folderData.accountData.getAccountProperty("accountname") + " (" + folderData.getFolderProperty("foldername") + ")" : cachedName;
+
+ //check if there is a cached or preloaded color - if not, chose one
+ if (!folderData.getFolderProperty("targetColor")) {
+ //define color set
+ let allColors = [
+ "#3366CC",
+ "#DC3912",
+ "#FF9900",
+ "#109618",
+ "#990099",
+ "#3B3EAC",
+ "#0099C6",
+ "#DD4477",
+ "#66AA00",
+ "#B82E2E",
+ "#316395",
+ "#994499",
+ "#22AA99",
+ "#AAAA11",
+ "#6633CC",
+ "#E67300",
+ "#8B0707",
+ "#329262",
+ "#5574A6",
+ "#3B3EAC"];
+
+ //find all used colors
+ let usedColors = [];
+ for (let calendar of calManager.getCalendars({})) {
+ if (calendar && calendar.getProperty("color")) {
+ usedColors.push(calendar.getProperty("color").toUpperCase());
+ }
+ }
+
+ //we do not want to change order of colors, we want to FILTER by counts, so we need to find the least count, filter by that and then take the first one
+ let minCount = null;
+ let statColors = [];
+ for (let i=0; i< allColors.length; i++) {
+ let count = usedColors.filter(item => item == allColors[i]).length;
+ if (minCount === null) minCount = count;
+ else if (count < minCount) minCount = count;
+
+ let obj = {};
+ obj.color = allColors[i];
+ obj.count = count;
+ statColors.push(obj);
+ }
+
+ //filter by minCount
+ let freeColors = statColors.filter(item => (minCount == null || item.count == minCount));
+ folderData.setFolderProperty("targetColor", freeColors[0].color);
+ }
+
+ //create and register new calendar
+ let newCalendar = await folderData.targetData.createCalendar(newname);
+ newCalendar.setProperty("tbSyncProvider", provider);
+ newCalendar.setProperty("tbSyncAccountID", folderData.accountData.accountID);
+
+ //store id of calendar as target in DB
+ folderData.setFolderProperty("target", newCalendar.id);
+ folderData.setFolderProperty("targetName", newCalendar.name);
+ folderData.setFolderProperty("targetColor", newCalendar.getProperty("color"));
+ return newCalendar;
+ }
+}
diff --git a/content/modules/manager.js b/content/modules/manager.js
new file mode 100644
index 0000000..fb30b9a
--- /dev/null
+++ b/content/modules/manager.js
@@ -0,0 +1,387 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var manager = {
+
+ prefWindowObj: null,
+
+ load: async function () {
+ },
+
+ unload: async function () {
+ //close window (if open)
+ if (this.prefWindowObj !== null) this.prefWindowObj.close();
+ },
+
+
+
+
+
+ openManagerWindow: function(event) {
+ if (!event.button) { //catches zero or undefined
+ if (TbSync.enabled) {
+ // check, if a window is already open and just put it in focus
+ if (this.prefWindowObj === null) {
+ this.prefWindowObj = TbSync.window.open("chrome://tbsync/content/manager/accountManager.xhtml", "TbSyncAccountManagerWindow", "chrome,centerscreen");
+ }
+ this.prefWindowObj.focus();
+ } else {
+ //this.popupNotEnabled();
+ }
+ }
+ },
+
+ popupNotEnabled: function () {
+ TbSync.dump("Oops", "Trying to open account manager, but init sequence not yet finished");
+ let msg = TbSync.getString("OopsMessage") + "\n\n";
+ let v = Services.appinfo.platformVersion;
+ if (TbSync.prefs.getIntPref("log.userdatalevel") == 0) {
+ if (TbSync.window.confirm(msg + TbSync.getString("UnableToTraceError"))) {
+ TbSync.prefs.setIntPref("log.userdatalevel", 1);
+ TbSync.window.alert(TbSync.getString("RestartThunderbirdAndTryAgain"));
+ }
+ } else {
+ if (TbSync.window.confirm(msg + TbSync.getString("HelpFixStartupError"))) {
+ this.createBugReport("john.bieling@gmx.de", msg, "");
+ }
+ }
+ },
+
+ openTBtab: function (url) {
+ let tabmail = TbSync.window.document.getElementById("tabmail");
+ if (TbSync.window && tabmail) {
+ TbSync.window.focus();
+ return tabmail.openTab("contentTab", {
+ contentPage: url
+ });
+ }
+ return null;
+ },
+
+ openTranslatedLink: function (url) {
+ let googleCode = TbSync.getString("google.translate.code");
+ if (googleCode != "en" && googleCode != "google.translate.code") {
+ this.openLink("https://translate.google.com/translate?hl=en&sl=en&tl="+TbSync.getString("google.translate.code")+"&u="+url);
+ } else {
+ this.openLink(url);
+ }
+ },
+
+ openLink: function (url) {
+ let ioservice = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
+ let uriToOpen = ioservice.newURI(url, null, null);
+ let extps = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"].getService(Components.interfaces.nsIExternalProtocolService);
+ extps.loadURI(uriToOpen, null);
+ },
+
+ openBugReportWizard: function () {
+ if (!TbSync.debugMode) {
+ this.prefWindowObj.alert(TbSync.getString("NoDebugLog"));
+ } else {
+ this.prefWindowObj.openDialog("chrome://tbsync/content/manager/support-wizard/support-wizard.xhtml", "support-wizard", "dialog,centerscreen,chrome,resizable=no");
+ }
+ },
+
+ createBugReport: function (email, subject, description) {
+ let fields = Components.classes["@mozilla.org/messengercompose/composefields;1"].createInstance(Components.interfaces.nsIMsgCompFields);
+ let params = Components.classes["@mozilla.org/messengercompose/composeparams;1"].createInstance(Components.interfaces.nsIMsgComposeParams);
+
+ fields.to = email;
+ fields.subject = "TbSync " + TbSync.addon.version.toString() + " bug report: " + subject;
+ fields.body = "Hi,\n\n" +
+ "attached you find my debug.log for the following error:\n\n" +
+ description;
+
+ params.composeFields = fields;
+ params.format = Components.interfaces.nsIMsgCompFormat.PlainText;
+
+ let attachment = Components.classes["@mozilla.org/messengercompose/attachment;1"].createInstance(Components.interfaces.nsIMsgAttachment);
+ attachment.contentType = "text/plain";
+ attachment.url = 'file://' + TbSync.io.getAbsolutePath("debug.log");
+ attachment.name = "debug.log";
+ attachment.temporary = false;
+
+ params.composeFields.addAttachment(attachment);
+ MailServices.compose.OpenComposeWindowWithParams (null, params);
+ },
+
+ viewDebugLog: function() {
+
+ if (this.debugLogWindow) {
+ let tabmail = TbSync.window.document.getElementById("tabmail");
+ tabmail.closeTab(this.debugLogWindow.tabNode);
+ this.debugLogWindow = null;
+ }
+ this.debugLogWindow = this.openTBtab('file://' + TbSync.io.getAbsolutePath("debug.log"));
+ },
+}
+
+
+
+/**
+ * Functions used by the folderlist in the main account settings tab
+ */
+manager.FolderList = class {
+ /**
+ * @param {string} provider Identifier for the provider this FolderListView is created for.
+ */
+ constructor(provider) {
+ this.provider = provider
+ }
+
+ /**
+ * Is called before the context menu of the folderlist is shown, allows to
+ * show/hide custom menu options based on selected folder
+ *
+ * @param document [in] document object of the account settings window - element.ownerDocument - menuentry?
+ * @param folderData [in] FolderData of the selected folder
+ */
+ onContextMenuShowing(window, folderData) {
+ return TbSync.providers[this.provider].StandardFolderList.onContextMenuShowing(window, folderData);
+ }
+
+
+ /**
+ * Returns an array of attribute objects, which define the number of columns
+ * and the look of the header
+ */
+ getHeader() {
+ return [
+ {style: "font-weight:bold;", label: "", width: "93"},
+ {style: "font-weight:bold;", label: TbSync.getString("manager.resource"), width:"150"},
+ {style: "font-weight:bold;", label: TbSync.getString("manager.status"), flex :"1"},
+ ]
+ }
+
+
+ /**
+ * Is called to add a row to the folderlist. After this call, updateRow is called as well.
+ *
+ * @param document [in] document object of the account settings window
+ * @param folderData [in] FolderData of the folder in the row
+ */
+ getRow(document, folderData) {
+ //create checkBox for select state
+ let itemSelCheckbox = document.createXULElement("checkbox");
+ itemSelCheckbox.setAttribute("updatefield", "selectbox");
+ itemSelCheckbox.setAttribute("style", "margin: 0px 0px 0px 3px;");
+ itemSelCheckbox.addEventListener("command", this.toggleFolder);
+
+ //icon
+ let itemType = document.createXULElement("image");
+ itemType.setAttribute("src", TbSync.providers[this.provider].StandardFolderList.getTypeImage(folderData));
+ itemType.setAttribute("style", "margin: 0px 9px 0px 3px;");
+
+ //ACL
+ let roAttributes = TbSync.providers[this.provider].StandardFolderList.getAttributesRoAcl(folderData);
+ let rwAttributes = TbSync.providers[this.provider].StandardFolderList.getAttributesRwAcl(folderData);
+ let itemACL = document.createXULElement("button");
+ itemACL.setAttribute("image", "chrome://tbsync/content/skin/acl_" + (folderData.getFolderProperty("downloadonly") ? "ro" : "rw") + ".png");
+ itemACL.setAttribute("class", "plain");
+ itemACL.setAttribute("style", "width: 35px; min-width: 35px; margin: 0; height:26px");
+ itemACL.setAttribute("updatefield", "acl");
+ if (roAttributes && rwAttributes) {
+ itemACL.setAttribute("type", "menu");
+ let menupopup = document.createXULElement("menupopup");
+ {
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.downloadonly = false;
+ menuitem.setAttribute("class", "menuitem-iconic");
+ menuitem.setAttribute("image", "chrome://tbsync/content/skin/acl_rw2.png");
+ menuitem.addEventListener("command", this.updateReadOnly);
+ for (const [attr, value] of Object.entries(rwAttributes)) {
+ menuitem.setAttribute(attr, value);
+ }
+ menupopup.appendChild(menuitem);
+ }
+
+ {
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.downloadonly = true;
+ menuitem.setAttribute("class", "menuitem-iconic");
+ menuitem.setAttribute("image", "chrome://tbsync/content/skin/acl_ro2.png");
+ menuitem.addEventListener("command", this.updateReadOnly);
+ for (const [attr, value] of Object.entries(roAttributes)) {
+ menuitem.setAttribute(attr, value);
+ }
+ menupopup.appendChild(menuitem);
+ }
+ itemACL.appendChild(menupopup);
+ }
+
+ //folder name
+ let itemLabel = document.createXULElement("description");
+ itemLabel.setAttribute("updatefield", "foldername");
+
+ //status
+ let itemStatus = document.createXULElement("description");
+ itemStatus.setAttribute("updatefield", "status");
+
+ //group1
+ let itemHGroup1 = document.createXULElement("hbox");
+ itemHGroup1.setAttribute("align", "center");
+ itemHGroup1.appendChild(itemSelCheckbox);
+ itemHGroup1.appendChild(itemType);
+ if (itemACL) itemHGroup1.appendChild(itemACL);
+
+ let itemVGroup1 = document.createXULElement("vbox");
+ itemVGroup1.setAttribute("width", "93");
+ itemVGroup1.appendChild(itemHGroup1);
+
+ //group2
+ let itemHGroup2 = document.createXULElement("hbox");
+ itemHGroup2.setAttribute("align", "center");
+ itemHGroup2.setAttribute("style", "border: 1px center");
+ itemHGroup2.appendChild(itemLabel);
+
+ let itemVGroup2 = document.createXULElement("vbox");
+ itemVGroup2.setAttribute("width", "150");
+ itemVGroup2.setAttribute("style", "padding: 3px");
+ itemVGroup2.appendChild(itemHGroup2);
+
+ //group3
+ let itemHGroup3 = document.createXULElement("hbox");
+ itemHGroup3.setAttribute("align", "center");
+ itemHGroup3.appendChild(itemStatus);
+
+ let itemVGroup3 = document.createXULElement("vbox");
+ itemVGroup3.setAttribute("width", "250");
+ itemVGroup3.setAttribute("style", "padding: 3px");
+ itemVGroup3.appendChild(itemHGroup3);
+
+ //final row
+ let row = document.createXULElement("hbox");
+ row.setAttribute("style", "min-height: 24px;");
+ row.appendChild(itemVGroup1);
+ row.appendChild(itemVGroup2);
+ row.appendChild(itemVGroup3);
+ return row;
+ }
+
+
+ /**
+ * ToggleFolder event
+ */
+ toggleFolder(event) {
+ let element = event.target;
+ let folderList = element.ownerDocument.getElementById("tbsync.accountsettings.folderlist");
+ if (folderList.selectedItem !== null && !folderList.disabled) {
+ // the folderData obj of the selected folder is attached to its row entry
+ let folder = folderList.selectedItem.folderData;
+
+ if (!folder.accountData.isEnabled())
+ return;
+
+ if (folder.getFolderProperty("selected")) {
+ // hasTarget() can throw an error, ignore that here
+ try {
+ if (!folder.targetData.hasTarget() || element.ownerDocument.defaultView.confirm(TbSync.getString("prompt.Unsubscribe"))) {
+ folder.targetData.removeTarget();
+ folder.setFolderProperty("selected", false);
+ } else {
+ if (element) {
+ //undo users action
+ element.setAttribute("checked", true);
+ }
+ }
+ } catch (e) {
+ folder.setFolderProperty("selected", false);
+ Components.utils.reportError(e);
+ }
+ } else {
+ //select and update status
+ folder.setFolderProperty("selected", true);
+ folder.setFolderProperty("status", "aborted");
+ folder.accountData.setAccountProperty("status", "notsyncronized");
+ }
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", folder.accountID);
+ }
+ }
+
+ /**
+ * updateReadOnly event
+ */
+ updateReadOnly(event) {
+ let element = event.target;
+ let folderList = element.ownerDocument.getElementById("tbsync.accountsettings.folderlist");
+ if (folderList.selectedItem !== null && !folderList.disabled) {
+ //the folderData obj of the selected folder is attached to its row entry
+ let folder = folderList.selectedItem.folderData;
+
+ //update value
+ let value = element.downloadonly;
+ folder.setFolderProperty("downloadonly", value);
+
+ //update icon
+ let button = element.parentNode.parentNode;
+ if (value) {
+ button.setAttribute('image','chrome://tbsync/content/skin/acl_ro.png');
+ } else {
+ button.setAttribute('image','chrome://tbsync/content/skin/acl_rw.png');
+ }
+
+ folder.targetData.setReadOnly(value);
+ }
+ }
+
+ /**
+ * Is called to update a row of the folderlist (the first cell is a select checkbox inserted by TbSync)
+ *
+ * @param document [in] document object of the account settings window
+ * @param listItem [in] the listitem of the row, which needs to be updated
+ * @param folderData [in] FolderData for that row
+ */
+ updateRow(document, listItem, folderData) {
+ let foldername = TbSync.providers[this.provider].StandardFolderList.getFolderDisplayName(folderData);
+ let status = folderData.getFolderStatus();
+ let selected = folderData.getFolderProperty("selected");
+
+ // get updatefields
+ let fields = {}
+ for (let f of listItem.querySelectorAll("[updatefield]")) {
+ fields[f.getAttribute("updatefield")] = f;
+ }
+
+ // update fields
+ fields.foldername.setAttribute("disabled", !selected);
+ fields.foldername.setAttribute("style", selected ? "" : "font-style:italic");
+ if (fields.foldername.textContent != foldername) {
+ fields.foldername.textContent = foldername;
+ fields.foldername.flex = "1";
+ }
+
+ fields.status.setAttribute("style", selected ? "" : "font-style:italic");
+ if (fields.status.textContent != status) {
+ fields.status.textContent = status;
+ fields.status.flex = "1";
+ }
+
+ if (fields.hasOwnProperty("acl")) {
+ fields.acl.setAttribute("image", "chrome://tbsync/content/skin/acl_" + (folderData.getFolderProperty("downloadonly") ? "ro" : "rw") + ".png");
+ fields.acl.setAttribute("disabled", folderData.accountData.isSyncing());
+ }
+
+ // update selectbox
+ let selbox = fields.selectbox;
+ if (selbox) {
+ if (folderData.getFolderProperty("selected")) {
+ selbox.setAttribute("checked", true);
+ } else {
+ selbox.removeAttribute("checked");
+ }
+
+ if (folderData.accountData.isSyncing()) {
+ selbox.setAttribute("disabled", true);
+ } else {
+ selbox.removeAttribute("disabled");
+ }
+ }
+ }
+}
diff --git a/content/modules/messenger.js b/content/modules/messenger.js
new file mode 100644
index 0000000..b466a9e
--- /dev/null
+++ b/content/modules/messenger.js
@@ -0,0 +1,108 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+var messenger = {
+
+ overlayManager : null,
+
+ load: async function () {
+ this.overlayManager = new OverlayManager(TbSync.extension, {verbose: 0});
+ await this.overlayManager.registerOverlay("chrome://messenger/content/messenger.xhtml", "chrome://tbsync/content/overlays/messenger.xhtml");
+ await this.overlayManager.registerOverlay("chrome://messenger/content/messengercompose/messengercompose.xhtml", "chrome://tbsync/content/overlays/messengercompose.xhtml");
+ await this.overlayManager.registerOverlay("chrome://calendar/content/calendar-event-dialog-attendees.xhtml", "chrome://tbsync/content/overlays/calendar-event-dialog-attendees.xhtml");
+ await this.overlayManager.registerOverlay("chrome://messenger/content/addressbook/addressbook.xhtml", "chrome://tbsync/content/overlays/addressbookiconsoverlay.xhtml");
+ await this.overlayManager.registerOverlay("chrome://messenger/content/addressbook/abNewCardDialog.xhtml", "chrome://tbsync/content/overlays/abNewCardWindowOverlay.xhtml");
+
+ // The abCSS.xul overlay is just adding a CSS file.
+ await this.overlayManager.registerOverlay("chrome://messenger/content/addressbook/abNewCardDialog.xhtml", "chrome://tbsync/content/overlays/abCSS.xhtml");
+ await this.overlayManager.registerOverlay("chrome://messenger/content/addressbook/addressbook.xhtml", "chrome://tbsync/content/overlays/abCSS.xhtml");
+
+ Services.obs.addObserver(this.initSyncObserver, "tbsync.observer.sync", false);
+ Services.obs.addObserver(this.syncstateObserver, "tbsync.observer.manager.updateSyncstate", false);
+
+ //inject overlays
+ this.overlayManager.startObserving();
+
+ },
+
+ unload: async function () {
+ //unload overlays
+ this.overlayManager.stopObserving();
+
+ Services.obs.removeObserver(this.initSyncObserver, "tbsync.observer.sync");
+ Services.obs.removeObserver(this.syncstateObserver, "tbsync.observer.manager.updateSyncstate");
+ },
+
+ // observer to catch changing syncstate and to update the status bar.
+ syncstateObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ //update status bar in all main windows
+ let windows = Services.wm.getEnumerator("mail:3pane");
+ while (windows.hasMoreElements()) {
+ let domWindow = windows.getNext();
+ if (TbSync) {
+ let status = domWindow.document.getElementById("tbsync.status");
+ if (status) {
+ let label = "TbSync: ";
+
+ if (TbSync.enabled) {
+
+ //check if any account is syncing, if not switch to idle
+ let accounts = TbSync.db.getAccounts();
+ let idle = true;
+ let err = false;
+
+ for (let i=0; i<accounts.allIDs.length && idle; i++) {
+ if (!accounts.IDs.includes(accounts.allIDs[i])) {
+ err = true;
+ continue;
+ }
+
+ //set idle to false, if at least one account is syncing
+ if (TbSync.core.isSyncing(accounts.allIDs[i])) idle = false;
+
+ //check for errors
+ switch (TbSync.db.getAccountProperty(accounts.allIDs[i], "status")) {
+ case "success":
+ case "disabled":
+ case "notsyncronized":
+ case "syncing":
+ break;
+ default:
+ err = true;
+ }
+ }
+
+ if (idle) {
+ if (err) label += TbSync.getString("info.error");
+ else label += TbSync.getString("info.idle");
+ } else {
+ label += TbSync.getString("status.syncing");
+ }
+ } else {
+ label += "Loading";
+ }
+ status.value = label;
+ }
+ }
+ }
+ }
+ },
+
+ // observer to init sync
+ initSyncObserver: {
+ observe: function (aSubject, aTopic, aData) {
+ if (TbSync.enabled) {
+ TbSync.core.syncAllAccounts();
+ } else {
+ //TbSync.manager.popupNotEnabled();
+ }
+ }
+ },
+}
diff --git a/content/modules/network.js b/content/modules/network.js
new file mode 100644
index 0000000..f5e81d3
--- /dev/null
+++ b/content/modules/network.js
@@ -0,0 +1,114 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var network = {
+
+ load: async function () {
+ },
+
+ unload: async function () {
+ },
+
+ getContainerIdForUser: function(username) {
+ //define the allowed range of container ids to be used
+ let min = 10000;
+ let max = 19999;
+
+ //we need to store the container map in the main window, so it is persistent and survives a restart of this bootstrapped addon
+ //TODO: is there a better way to store this container map globally? Can there be TWO main windows?
+ let mainWindow = Services.wm.getMostRecentWindow("mail:3pane");
+
+ //init
+ if (!(mainWindow._containers)) {
+ mainWindow._containers = [];
+ }
+
+ //reset if adding an entry will exceed allowed range
+ if (mainWindow._containers.length > (max-min) && mainWindow._containers.indexOf(username) == -1) {
+ for (let i=0; i < mainWindow._containers.length; i++) {
+ //Services.clearData.deleteDataFromOriginAttributesPattern({ userContextId: i + min });
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", JSON.stringify({ userContextId: i + min }));
+ }
+ mainWindow._containers = [];
+ }
+
+ let idx = mainWindow._containers.indexOf(username);
+ return (idx == -1) ? mainWindow._containers.push(username) - 1 + min : (idx + min);
+ },
+
+ resetContainerForUser: function(username) {
+ let id = this.getContainerIdForUser(username);
+ Services.obs.notifyObservers(null, "clear-origin-attributes-data", JSON.stringify({ userContextId: id }));
+ },
+
+ createTCPErrorFromFailedXHR: function (xhr) {
+ return this.createTCPErrorFromFailedRequest(xhr.channel.QueryInterface(Components.interfaces.nsIRequest));
+ },
+
+ createTCPErrorFromFailedRequest: function (request) {
+ //adapted from :
+ //https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL
+ //codes: https://developer.mozilla.org/en-US/docs/Mozilla/Errors
+ let status = request.status;
+
+ if ((status & 0xff0000) === 0x5a0000) { // Security module
+ const nsINSSErrorsService = Components.interfaces.nsINSSErrorsService;
+ let nssErrorsService = Components.classes['@mozilla.org/nss_errors_service;1'].getService(nsINSSErrorsService);
+
+ // NSS_SEC errors (happen below the base value because of negative vals)
+ if ((status & 0xffff) < Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
+
+ // The bases are actually negative, so in our positive numeric space, we
+ // need to subtract the base off our value.
+ let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
+ switch (nssErr) {
+ case 11: return 'security::SEC_ERROR_EXPIRED_CERTIFICATE';
+ case 12: return 'security::SEC_ERROR_REVOKED_CERTIFICATE';
+ case 13: return 'security::SEC_ERROR_UNKNOWN_ISSUER';
+ case 20: return 'security::SEC_ERROR_UNTRUSTED_ISSUER';
+ case 21: return 'security::SEC_ERROR_UNTRUSTED_CERT';
+ case 36: return 'security::SEC_ERROR_CA_CERT_INVALID';
+ case 90: return 'security::SEC_ERROR_INADEQUATE_KEY_USAGE';
+ case 176: return 'security::SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED';
+ }
+ return 'security::UNKNOWN_SECURITY_ERROR';
+
+ } else {
+
+ // Calculating the difference
+ let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
+ switch (sslErr) {
+ case 3: return 'security::SSL_ERROR_NO_CERTIFICATE';
+ case 4: return 'security::SSL_ERROR_BAD_CERTIFICATE';
+ case 8: return 'security::SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE';
+ case 9: return 'security::SSL_ERROR_UNSUPPORTED_VERSION';
+ case 12: return 'security::SSL_ERROR_BAD_CERT_DOMAIN';
+ }
+ return 'security::UNKOWN_SSL_ERROR';
+
+ }
+
+ } else { //not the security module
+
+ switch (status) {
+ case 0x804B000C: return 'network::NS_ERROR_CONNECTION_REFUSED';
+ case 0x804B000E: return 'network::NS_ERROR_NET_TIMEOUT';
+ case 0x804B001E: return 'network::NS_ERROR_UNKNOWN_HOST';
+ case 0x804B0047: return 'network::NS_ERROR_NET_INTERRUPT';
+ case 0x805303F4: return 'network::NS_ERROR_DOM_BAD_URI';
+ // Custom error
+ case 0x804B002F: return 'network::REJECTED_REDIRECT_FROM_HTTPS_TO_HTTP';
+ }
+ return 'network::UNKNOWN_NETWORK_ERROR';
+
+ }
+ return null;
+ },
+}
diff --git a/content/modules/passwordManager.js b/content/modules/passwordManager.js
new file mode 100644
index 0000000..0145cd1
--- /dev/null
+++ b/content/modules/passwordManager.js
@@ -0,0 +1,78 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var passwordManager = {
+
+ load: async function () {
+ },
+
+ unload: async function () {
+ },
+
+ removeLoginInfos: function(origin, realm, users = null) {
+ let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init");
+
+ let logins = Services.logins.findLogins(origin, null, realm);
+ for (let i = 0; i < logins.length; i++) {
+ if (!users || users.includes(logins[i].username)) {
+ let currentLoginInfo = new nsLoginInfo(origin, null, realm, logins[i].username, logins[i].password, "", "");
+ try {
+ Services.logins.removeLogin(currentLoginInfo);
+ } catch (e) {
+ TbSync.dump("Error removing loginInfo", e);
+ }
+ }
+ }
+ },
+
+ updateLoginInfo: function(origin, realm, oldUser, newUser, newPassword) {
+ let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init");
+
+ this.removeLoginInfos(origin, realm, [oldUser, newUser]);
+
+ let newLoginInfo = new nsLoginInfo(origin, null, realm, newUser, newPassword, "", "");
+ try {
+ Services.logins.addLogin(newLoginInfo);
+ } catch (e) {
+ TbSync.dump("Error adding loginInfo", e);
+ }
+ },
+
+ getLoginInfo: function(origin, realm, user) {
+ let logins = Services.logins.findLogins(origin, null, realm);
+ for (let i = 0; i < logins.length; i++) {
+ if (logins[i].username == user) {
+ return logins[i].password;
+ }
+ }
+ return null;
+ },
+
+
+ /** data obj
+ windowID
+ accountName
+ userName
+ userNameLocked
+
+ reference is an object in which an entry with windowID will be placed to hold a reference to the prompt window (so it can be closed externaly)
+ */
+ asyncPasswordPrompt: async function(data, reference) {
+ if (data.windowID) {
+ let url = "chrome://tbsync/content/passwordPrompt/passwordPrompt.xhtml";
+
+ return await new Promise(function(resolve, reject) {
+ reference[data.windowID] = TbSync.window.openDialog(url, "TbSyncPasswordPrompt:" + data.windowID, "centerscreen,chrome,resizable=no", data, resolve);
+ });
+ }
+
+ return false;
+ }
+}
diff --git a/content/modules/providers.js b/content/modules/providers.js
new file mode 100644
index 0000000..5503a86
--- /dev/null
+++ b/content/modules/providers.js
@@ -0,0 +1,182 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var providers = {
+
+ //list of default providers (available in add menu, even if not installed)
+ defaultProviders: {
+ "dav" : {
+ name: "CalDAV & CardDAV",
+ homepageUrl: "https://addons.thunderbird.net/addon/dav-4-tbsync/"},
+ "eas" : {
+ name: "Exchange ActiveSync",
+ homepageUrl: "https://addons.thunderbird.net/addon/eas-4-tbsync/"},
+ },
+
+ loadedProviders: null,
+
+ load: async function () {
+ this.loadedProviders = {};
+ },
+
+ unload: async function () {
+ for (let provider in this.loadedProviders) {
+ await this.unloadProvider(provider);
+ }
+ },
+
+
+
+
+
+ loadProvider: async function (extension, provider, js) {
+ //only load, if not yet loaded and if the provider name does not shadow a fuction inside provider.js
+ if (!this.loadedProviders.hasOwnProperty(provider) && !this.hasOwnProperty(provider) && js.startsWith("chrome://")) {
+ try {
+ let addon = await AddonManager.getAddonByID(extension.id);
+
+ //load provider subscripts into TbSync
+ this[provider] = {};
+ Services.scriptloader.loadSubScript(js, this[provider], "UTF-8");
+ if (TbSync.apiVersion != this[provider].Base.getApiVersion()) {
+ throw new Error("API version mismatch, TbSync@"+TbSync.apiVersion+" vs " + provider + "@" + this[provider].Base.getApiVersion());
+ }
+
+ this.loadedProviders[provider] = {};
+ this.loadedProviders[provider].addon = addon;
+ this.loadedProviders[provider].extension = extension;
+ this.loadedProviders[provider].addonId = extension.id;
+ this.loadedProviders[provider].version = addon.version.toString();
+ this.loadedProviders[provider].createAccountWindow = null;
+
+ addon.contributorsURL = this[provider].Base.getContributorsUrl();
+
+ // check if provider has its own implementation of folderList
+ if (!this[provider].hasOwnProperty("folderList")) this[provider].folderList = new TbSync.manager.FolderList(provider);
+
+ //load provider
+ await this[provider].Base.load();
+
+ await TbSync.messenger.overlayManager.registerOverlay("chrome://tbsync/content/manager/editAccount.xhtml?provider=" + provider, this[provider].Base.getEditAccountOverlayUrl());
+ TbSync.dump("Loaded provider", provider + "::" + this[provider].Base.getProviderName() + " ("+this.loadedProviders[provider].version+")");
+
+ // reset all accounts of this provider
+ let providerData = new TbSync.ProviderData(provider);
+ let accounts = providerData.getAllAccounts();
+ for (let accountData of accounts) {
+ // reset sync objects
+ TbSync.core.resetSyncDataObj(accountData.accountID);
+
+ // set all accounts which are syncing to notsyncronized
+ if (accountData.getAccountProperty("status") == "syncing") accountData.setAccountProperty("status", "notsyncronized");
+
+ // set each folder with PENDING status to ABORTED
+ let folders = TbSync.db.findFolders({"status": "pending"}, {"accountID": accountData.accountID});
+
+ for (let f=0; f < folders.length; f++) {
+ TbSync.db.setFolderProperty(folders[f].accountID, folders[f].folderID, "status", "aborted");
+ }
+ }
+
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateProviderList", provider);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", null);
+
+ // TB60 -> TB68 migration - remove icon and rename target if stale
+ let allAddressBooks = MailServices.ab.directories;
+ while (allAddressBooks.hasMoreElements()) {
+ let addressBook = allAddressBooks.getNext();
+ if (addressBook instanceof Components.interfaces.nsIAbDirectory) {
+ let storedProvider = TbSync.addressbook.getStringValue(addressBook, "tbSyncProvider", "");
+ if (provider == storedProvider && providerData.getFolders({"target": addressBook.UID}).length == 0) {
+ let name = addressBook.dirName;
+ addressBook.dirName = TbSync.getString("target.orphaned") + ": " + name;
+ addressBook.setStringValue("tbSyncIcon", "orphaned");
+ addressBook.setStringValue("tbSyncProvider", "orphaned");
+ addressBook.setStringValue("tbSyncAccountID", "");
+ }
+ }
+ }
+
+ for (let calendar of TbSync.lightning.cal.getCalendarManager().getCalendars({})) {
+ let storedProvider = calendar.getProperty("tbSyncProvider");
+ if (provider == storedProvider && calendar.type == "storage" && providerData.getFolders({"target": calendar.id}).length == 0) {
+ let name = calendar.name;
+ calendar.name = TbSync.getString("target.orphaned") + ": " + name;
+ calendar.setProperty("disabled", true);
+ calendar.setProperty("tbSyncProvider", "orphaned");
+ calendar.setProperty("tbSyncAccountID", "");
+ }
+ }
+
+ } catch (e) {
+ delete this.loadedProviders[provider];
+ delete this[provider];
+ let info = new EventLogInfo(provider);
+ TbSync.eventlog.add("error", info, "FAILED to load provider <"+provider+">", e.message);
+ Components.utils.reportError(e);
+ }
+
+ }
+ },
+
+ unloadProvider: async function (provider) {
+ if (this.loadedProviders.hasOwnProperty(provider)) {
+ TbSync.dump("Unloading provider", provider);
+
+ if (this.loadedProviders[provider].createAccountWindow) {
+ this.loadedProviders[provider].createAccountWindow.close();
+ }
+
+ await this[provider].Base.unload();
+ delete this.loadedProviders[provider];
+ delete this[provider];
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateProviderList", provider);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", null);
+ }
+ },
+
+ getDefaultAccountEntries: function (provider) {
+ let defaults = TbSync.providers[provider].Base.getDefaultAccountEntries();
+
+ // List of default system account properties.
+ // Do not remove search marker for doc.
+ // DefaultAccountPropsStart
+ defaults.provider = provider;
+ defaults.accountID = "";
+ defaults.lastsynctime = 0;
+ defaults.status = "disabled";
+ defaults.autosync = 0;
+ defaults.noAutosyncUntil = 0;
+ defaults.accountname = "";
+ // DefaultAccountPropsEnd
+
+ return defaults;
+ },
+
+ getDefaultFolderEntries: function (accountID) {
+ let provider = TbSync.db.getAccountProperty(accountID, "provider");
+ let defaults = TbSync.providers[provider].Base.getDefaultFolderEntries();
+
+ // List of default system folder properties.
+ // Do not remove search marker for doc.
+ // DefaultFolderPropsStart
+ defaults.accountID = accountID;
+ defaults.targetType = "";
+ defaults.cached = false;
+ defaults.selected = false;
+ defaults.lastsynctime = 0;
+ defaults.status = "";
+ defaults.foldername = "";
+ defaults.downloadonly = false;
+ // DefaultFolderPropsEnd
+
+ return defaults;
+ },
+}
diff --git a/content/modules/public.js b/content/modules/public.js
new file mode 100644
index 0000000..5b1e5cf
--- /dev/null
+++ b/content/modules/public.js
@@ -0,0 +1,753 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+/**
+ *
+ */
+ var StatusData = class {
+ /**
+ * A StatusData instance must be used as return value by
+ * :class:`Base.syncFolderList` and :class:`Base.syncFolder`.
+ *
+ * StatusData also defines the possible StatusDataTypes used by the
+ * :ref:`TbSyncEventLog`.
+ *
+ * @param {StatusDataType} type Status type (see const definitions below)
+ * @param {string} message ``Optional`` A message, which will be used as
+ * sync status. If this is not a success, it will be
+ * used also in the :ref:`TbSyncEventLog` as well.
+ * @param {string} details ``Optional`` If this is not a success, it will
+ * be used as description in the
+ * :ref:`TbSyncEventLog`.
+ *
+ */
+ constructor(type = "success", message = "", details = "") {
+ this.type = type; //success, info, warning, error
+ this.message = message;
+ this.details = details;
+ }
+ /**
+ * Successfull sync.
+ */
+ static get SUCCESS() {return "success"};
+ /**
+ * Sync of the entire account will be aborted.
+ */
+ static get ERROR() {return "error"};
+ /**
+ * Sync of this resource will be aborted and continued with next resource.
+ */
+ static get WARNING() {return "warning"};
+ /**
+ * Successfull sync, but message and details
+ * provided will be added to the event log.
+ */
+ static get INFO() {return "info"};
+ /**
+ * Sync of the entire account will be aborted and restarted completely.
+ */
+ static get ACCOUNT_RERUN() {return "account_rerun"};
+ /**
+ * Sync of the current folder/resource will be restarted.
+ */
+ static get FOLDER_RERUN() {return "folder_rerun"};
+}
+
+
+
+/**
+ * ProgressData to manage a ``done`` and a ``todo`` counter.
+ *
+ * Each :class:`SyncData` instance has an associated ProgressData instance. See
+ * :class:`SyncData.progressData`. The information of that ProgressData
+ * instance is used, when the current syncstate is prefixed by ``send.``,
+ * ``eval.`` or ``prepare.``. See :class:`SyncData.setSyncState`.
+ *
+ */
+var ProgressData = class {
+ /**
+ *
+ */
+ constructor() {
+ this._todo = 0;
+ this._done = 0;
+ }
+
+ /**
+ * Reset ``done`` and ``todo`` counter.
+ *
+ * @param {integer} done ``Optional`` Set a value for the ``done`` counter.
+ * @param {integer} todo ``Optional`` Set a value for the ``todo`` counter.
+ *
+ */
+ reset(done = 0, todo = 0) {
+ this._todo = todo;
+ this._done = done;
+ }
+
+ /**
+ * Increment the ``done`` counter.
+ *
+ * @param {integer} value ``Optional`` Set incrementation value.
+ *
+ */
+ inc(value = 1) {
+ this._done += value;
+ }
+
+ /**
+ * Getter for the ``todo`` counter.
+ *
+ */
+ get todo() {
+ return this._todo;
+ }
+
+ /**
+ * Getter for the ``done`` counter.
+ *
+ */
+ get done() {
+ return this._done;
+ }
+}
+
+
+
+/**
+ * ProviderData
+ *
+ */
+var ProviderData = class {
+ /**
+ * Constructor
+ *
+ * @param {FolderData} folderData FolderData of the folder for which the
+ * display name is requested.
+ *
+ */
+ constructor(provider) {
+ if (!TbSync.providers.hasOwnProperty(provider)) {
+ throw new Error("Provider <" + provider + "> has not been loaded. Failed to create ProviderData.");
+ }
+ this.provider = provider;
+ }
+
+ /**
+ * Getter for an :class:`EventLogInfo` instance with all the information
+ * regarding this ProviderData instance.
+ *
+ */
+ get eventLogInfo() {
+ return new EventLogInfo(
+ this.getAccountProperty("provider"));
+ }
+
+ getVersion() {
+ return TbSync.providers.loadedProviders[this.provider].version;
+ }
+
+ get extension() {
+ return TbSync.providers.loadedProviders[this.provider].extension;
+ }
+
+ getAllAccounts() {
+ let accounts = TbSync.db.getAccounts();
+ let allAccounts = [];
+ for (let i=0; i<accounts.IDs.length; i++) {
+ let accountID = accounts.IDs[i];
+ if (accounts.data[accountID].provider == this.provider) {
+ allAccounts.push(new TbSync.AccountData(accountID));
+ }
+ }
+ return allAccounts;
+ }
+
+ getFolders(aFolderSearchCriteria = {}) {
+ let allFolders = [];
+ let folderSearchCriteria = {};
+ Object.assign(folderSearchCriteria, aFolderSearchCriteria);
+ folderSearchCriteria.cached = false;
+
+ let folders = TbSync.db.findFolders(folderSearchCriteria, {"provider": this.provider});
+ for (let i=0; i < folders.length; i++) {
+ allFolders.push(new TbSync.FolderData(new TbSync.AccountData(folders[i].accountID), folders[i].folderID));
+ }
+ return allFolders;
+ }
+
+ getDefaultAccountEntries() {
+ return TbSync.providers.getDefaultAccountEntries(this.provider)
+ }
+
+ addAccount(accountName, accountOptions) {
+ let newAccountID = TbSync.db.addAccount(accountName, accountOptions);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateAccountsList", newAccountID);
+ return new TbSync.AccountData(newAccountID);
+ }
+}
+
+
+
+/**
+ * AccountData
+ *
+ */
+var AccountData = class {
+ /**
+ *
+ */
+ constructor(accountID) {
+ this._accountID = accountID;
+
+ if (!TbSync.db.accounts.data.hasOwnProperty(accountID)) {
+ throw new Error("An account with ID <" + accountID + "> does not exist. Failed to create AccountData.");
+ }
+ }
+
+ /**
+ * Getter for an :class:`EventLogInfo` instance with all the information
+ * regarding this AccountData instance.
+ *
+ */
+ get eventLogInfo() {
+ return new EventLogInfo(
+ this.getAccountProperty("provider"),
+ this.getAccountProperty("accountname"),
+ this.accountID);
+ }
+
+ get accountID() {
+ return this._accountID;
+ }
+
+ getAllFolders() {
+ let allFolders = [];
+ let folders = TbSync.db.findFolders({"cached": false}, {"accountID": this.accountID});
+ for (let i=0; i < folders.length; i++) {
+ allFolders.push(new TbSync.FolderData(this, folders[i].folderID));
+ }
+ return allFolders;
+ }
+
+ getAllFoldersIncludingCache() {
+ let allFolders = [];
+ let folders = TbSync.db.findFolders({}, {"accountID": this.accountID});
+ for (let i=0; i < folders.length; i++) {
+ allFolders.push(new TbSync.FolderData(this, folders[i].folderID));
+ }
+ return allFolders;
+ }
+
+ getFolder(setting, value) {
+ // ES6 supports variable keys by putting it into brackets
+ let folders = TbSync.db.findFolders({[setting]: value, "cached": false}, {"accountID": this.accountID});
+ if (folders.length > 0) return new TbSync.FolderData(this, folders[0].folderID);
+ return null;
+ }
+
+ getFolderFromCache(setting, value) {
+ // ES6 supports variable keys by putting it into brackets
+ let folders = TbSync.db.findFolders({[setting]: value, "cached": true}, {"accountID": this.accountID});
+ if (folders.length > 0) return new TbSync.FolderData(this, folders[0].folderID);
+ return null;
+ }
+
+ createNewFolder() {
+ return new TbSync.FolderData(this, TbSync.db.addFolder(this.accountID));
+ }
+
+ // get data objects
+ get providerData() {
+ return new TbSync.ProviderData(
+ this.getAccountProperty("provider"),
+ );
+ }
+
+ get syncData() {
+ return TbSync.core.getSyncDataObject(this.accountID);
+ }
+
+
+ /**
+ * Initiate a sync of this entire account by calling
+ * :class:`Base.syncFolderList`. If that succeeded, :class:`Base.syncFolder`
+ * will be called for each available folder / resource found on the server.
+ *
+ * @param {Object} syncDescription ``Optional``
+ */
+ sync(syncDescription = {}) {
+ TbSync.core.syncAccount(this.accountID, syncDescription);
+ }
+
+ isSyncing() {
+ return TbSync.core.isSyncing(this.accountID);
+ }
+
+ isEnabled() {
+ return TbSync.core.isEnabled(this.accountID);
+ }
+
+ isConnected() {
+ return TbSync.core.isConnected(this.accountID);
+ }
+
+
+ getAccountProperty(field) {
+ return TbSync.db.getAccountProperty(this.accountID, field);
+ }
+
+ setAccountProperty(field, value) {
+ TbSync.db.setAccountProperty(this.accountID, field, value);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.reloadAccountSetting", JSON.stringify({accountID: this.accountID, setting: field}));
+ }
+
+ resetAccountProperty(field) {
+ TbSync.db.resetAccountProperty(this.accountID, field);
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.reloadAccountSetting", JSON.stringify({accountID: this.accountID, setting: field}));
+ }
+}
+
+
+
+/**
+ * FolderData
+ *
+ */
+var FolderData = class {
+ /**
+ *
+ */
+ constructor(accountData, folderID) {
+ this._accountData = accountData;
+ this._folderID = folderID;
+ this._target = null;
+
+ if (!TbSync.db.folders[accountData.accountID].hasOwnProperty(folderID)) {
+ throw new Error("A folder with ID <" + folderID + "> does not exist for the given account. Failed to create FolderData.");
+ }
+ }
+
+ /**
+ * Getter for an :class:`EventLogInfo` instance with all the information
+ * regarding this FolderData instance.
+ *
+ */
+ get eventLogInfo() {
+ return new EventLogInfo(
+ this.accountData.getAccountProperty("provider"),
+ this.accountData.getAccountProperty("accountname"),
+ this.accountData.accountID,
+ this.getFolderProperty("foldername"),
+ );
+ }
+
+ get folderID() {
+ return this._folderID;
+ }
+
+ get accountID() {
+ return this._accountData.accountID;
+ }
+
+ getDefaultFolderEntries() { // remove
+ return TbSync.providers.getDefaultFolderEntries(this.accountID);
+ }
+
+ getFolderProperty(field) {
+ return TbSync.db.getFolderProperty(this.accountID, this.folderID, field);
+ }
+
+ setFolderProperty(field, value) {
+ TbSync.db.setFolderProperty(this.accountID, this.folderID, field, value);
+ }
+
+ resetFolderProperty(field) {
+ TbSync.db.resetFolderProperty(this.accountID, this.folderID, field);
+ }
+
+ /**
+ * Initiate a sync of this folder only by calling
+ * :class:`Base.syncFolderList` and than :class:`Base.syncFolder` for this
+ * folder / resource only.
+ *
+ * @param {Object} syncDescription ``Optional``
+ */
+ sync(aSyncDescription = {}) {
+ let syncDescription = {};
+ Object.assign(syncDescription, aSyncDescription);
+
+ syncDescription.syncFolders = [this];
+ this.accountData.sync(syncDescription);
+ }
+
+ isSyncing() {
+ let syncdata = this.accountData.syncData;
+ return (syncdata.currentFolderData && syncdata.currentFolderData.folderID == this.folderID);
+ }
+
+ getFolderStatus() {
+ let status = "";
+
+ if (this.getFolderProperty("selected")) {
+ //default
+ status = TbSync.getString("status." + this.getFolderProperty("status"), this.accountData.getAccountProperty("provider")).split("||")[0];
+
+ switch (this.getFolderProperty("status").split(".")[0]) { //the status may have a sub-decleration
+ case "success":
+ case "modified":
+ try {
+ status = status + ": " + this.targetData.targetName;
+ } catch (e) {
+ this.resetFolderProperty("target");
+ this.setFolderProperty("status","notsyncronized");
+ return TbSync.getString("status.notsyncronized");
+ }
+ break;
+
+ case "pending":
+ //add extra info if this folder is beeing synced
+ if (this.isSyncing()) {
+ let syncdata = this.accountData.syncData;
+ status = TbSync.getString("status.syncing", this.accountData.getAccountProperty("provider"));
+ if (["send","eval","prepare"].includes(syncdata.getSyncState().state.split(".")[0]) && (syncdata.progressData.todo + syncdata.progressData.done) > 0) {
+ //add progress information
+ status = status + " (" + syncdata.progressData.done + (syncdata.progressData.todo > 0 ? "/" + syncdata.progressData.todo : "") + ")";
+ }
+ }
+ break;
+ }
+ } else {
+ //remain empty if not selected
+ }
+ return status;
+ }
+
+ // get data objects
+ get accountData() {
+ return this._accountData;
+ }
+
+ /**
+ * Getter for the :class:`TargetData` instance associated with this
+ * FolderData. See :ref:`TbSyncTargets` for more details.
+ *
+ * @returns {TargetData}
+ *
+ */
+ get targetData() {
+ // targetData is created on demand
+ if (!this._target) {
+ let provider = this.accountData.getAccountProperty("provider");
+ let targetType = this.getFolderProperty("targetType");
+
+ if (!targetType)
+ throw new Error("Provider <"+provider+"> has not set a proper target type for this folder.");
+
+ if (!TbSync.providers[provider].hasOwnProperty("TargetData_" + targetType))
+ throw new Error("Provider <"+provider+"> is missing a TargetData implementation for <"+targetType+">.");
+
+ this._target = new TbSync.providers[provider]["TargetData_" + targetType](this);
+
+ if (!this._target)
+ throw new Error("notargets");
+ }
+
+ return this._target;
+ }
+
+ // Removes the folder and its target. If the target should be
+ // kept as a stale/unconnected item, provide a suffix, which
+ // will be added to its name, to indicate, that it is no longer
+ // managed by TbSync.
+ remove(keepStaleTargetSuffix = "") {
+ // hasTarget() can throw an error, ignore that here
+ try {
+ if (this.targetData.hasTarget()) {
+ if (keepStaleTargetSuffix) {
+ let oldName = this.targetData.targetName;
+ this.targetData.targetName = TbSync.getString("target.orphaned") + ": " + oldName + " " + keepStaleTargetSuffix;
+ this.targetData.disconnectTarget();
+ } else {
+ this.targetData.removeTarget();
+ }
+ }
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+ this.setFolderProperty("cached", true);
+ }
+}
+
+
+
+/**
+ * There is only one SyncData instance per account which contains all
+ * relevant information regarding an ongoing sync.
+ *
+ */
+var SyncData = class {
+ /**
+ *
+ */
+ constructor(accountID) {
+
+ //internal (private, not to be touched by provider)
+ this._syncstate = {
+ state: "accountdone",
+ timestamp: Date.now(),
+ }
+ this._accountData = new TbSync.AccountData(accountID);
+ this._progressData = new TbSync.ProgressData();
+ this._currentFolderData = null;
+ }
+
+ //all functions provider should use should be in here
+ //providers should not modify properties directly
+ //when getSyncDataObj is used never change the folder id as a sync may be going on!
+
+ _setCurrentFolderData(folderData) {
+ this._currentFolderData = folderData;
+ }
+ _clearCurrentFolderData() {
+ this._currentFolderData = null;
+ }
+
+ /**
+ * Getter for an :class:`EventLogInfo` instance with all the information
+ * regarding this SyncData instance.
+ *
+ */
+ get eventLogInfo() {
+ return new EventLogInfo(
+ this.accountData.getAccountProperty("provider"),
+ this.accountData.getAccountProperty("accountname"),
+ this.accountData.accountID,
+ this.currentFolderData ? this.currentFolderData.getFolderProperty("foldername") : "",
+ );
+ }
+
+ /**
+ * Getter for the :class:`FolderData` instance of the folder being currently
+ * synced. Can be ``null`` if no folder is being synced.
+ *
+ */
+ get currentFolderData() {
+ return this._currentFolderData;
+ }
+
+ /**
+ * Getter for the :class:`AccountData` instance of the account being
+ * currently synced.
+ *
+ */
+ get accountData() {
+ return this._accountData;
+ }
+
+ /**
+ * Getter for the :class:`ProgressData` instance of the ongoing sync.
+ *
+ */
+ get progressData() {
+ return this._progressData;
+ }
+
+ /**
+ * Sets the syncstate of the ongoing sync, to provide feedback to the user.
+ * The selected state can trigger special UI features, if it starts with one
+ * of the following prefixes:
+ *
+ * * ``send.``, ``eval.``, ``prepare.`` :
+ * The status message in the UI will be appended with the current progress
+ * stored in the :class:`ProgressData` associated with this SyncData
+ * instance. See :class:`SyncData.progressData`.
+ *
+ * * ``send.`` :
+ * The status message in the UI will be appended by a timeout countdown
+ * with the timeout being defined by :class:`Base.getConnectionTimeout`.
+ *
+ * @param {string} state A short syncstate identifier. The actual
+ * message to be displayed in the UI will be
+ * looked up in the locales of the provider
+ * by looking for ``syncstate.<state>``.
+ * The lookup is done via :func:`getString`,
+ * so the same fallback rules apply.
+ *
+ */
+ setSyncState(state) {
+ //set new syncstate
+ let msg = "State: " + state + ", Account: " + this.accountData.getAccountProperty("accountname");
+ if (this.currentFolderData) msg += ", Folder: " + this.currentFolderData.getFolderProperty("foldername");
+
+ let syncstate = {};
+ syncstate.state = state;
+ syncstate.timestamp = Date.now();
+
+ this._syncstate = syncstate;
+ TbSync.dump("setSyncState", msg);
+
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", this.accountData.accountID);
+ }
+
+ /**
+ * Gets the current syncstate and its timestamp of the ongoing sync. The
+ * returned Object has the following attributes:
+ *
+ * * ``state`` : the current syncstate
+ * * ``timestamp`` : its timestamp
+ *
+ * @returns {Object} The syncstate and its timestamp.
+ *
+ */
+ getSyncState() {
+ return this._syncstate;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+// Simple dumper, who can dump to file or console
+// It is suggested to use the event log instead of dumping directly.
+var dump = function (what, aMessage) {
+ if (TbSync.prefs.getBoolPref("log.toconsole")) {
+ Services.console.logStringMessage("[TbSync] " + what + " : " + aMessage);
+ }
+
+ if (TbSync.prefs.getIntPref("log.userdatalevel") > 0) {
+ let now = new Date();
+ TbSync.io.appendToFile("debug.log", "** " + now.toString() + " **\n[" + what + "] : " + aMessage + "\n\n");
+ }
+}
+
+
+
+/**
+ * Get a localized string.
+ *
+ * TODO: Explain placeholder and :: notation.
+ *
+ * @param {string} key The key of the message to look up
+ * @param {string} provider ``Optional`` The provider the key belongs to.
+ *
+ * @returns {string} The message belonging to the key of the specified provider.
+ * If that key is not found in the in the specified provider
+ * or if no provider has been specified, the messages of
+ * TbSync itself we be used as fallback. If the key could not
+ * be found there as well, the key itself is returned.
+ *
+ */
+var getString = function (key, provider) {
+ let localized = null;
+
+ //spezial treatment of strings with :: like status.httperror::403
+ let parts = key.split("::");
+
+ // if a provider is given, try to get the string from the provider
+ if (provider && TbSync.providers.loadedProviders.hasOwnProperty(provider)) {
+ let localeData = TbSync.providers.loadedProviders[provider].extension.localeData;
+ if (localeData.messages.get(localeData.selectedLocale).has(parts[0].toLowerCase())) {
+ localized = TbSync.providers.loadedProviders[provider].extension.localeData.localizeMessage(parts[0]);
+ }
+ }
+
+ // if we did not yet succeed, check the locales of tbsync itself
+ if (!localized) {
+ localized = TbSync.extension.localeData.localizeMessage(parts[0]);
+ }
+
+ if (!localized) {
+ localized = key;
+ } else {
+ //replace placeholders in returned string
+ for (let i = 0; i<parts.length; i++) {
+ let regex = new RegExp( "##replace\."+i+"##", "g");
+ localized = localized.replace(regex, parts[i]);
+ }
+ }
+
+ return localized;
+}
+
+
+var localizeNow = function (window, provider) {
+ let document = window.document;
+ let keyPrefix = "__" + (provider ? provider.toUpperCase() + "4" : "") + "TBSYNCMSG_";
+
+ let localization = {
+ i18n: null,
+
+ updateString(string) {
+ let re = new RegExp(keyPrefix + "(.+?)__", "g");
+ return string.replace(re, matched => {
+ const key = matched.slice(keyPrefix.length, -2);
+ return TbSync.getString(key, provider) || matched;
+ });
+ },
+
+ updateDocument(node) {
+ const texts = document.evaluate(
+ 'descendant::text()[contains(self::text(), "' + keyPrefix + '")]',
+ node,
+ null,
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
+ null
+ );
+ for (let i = 0, maxi = texts.snapshotLength; i < maxi; i++) {
+ const text = texts.snapshotItem(i);
+ if (text.nodeValue.includes(keyPrefix)) text.nodeValue = this.updateString(text.nodeValue);
+ }
+
+ const attributes = document.evaluate(
+ 'descendant::*/attribute::*[contains(., "' + keyPrefix + '")]',
+ node,
+ null,
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
+ null
+ );
+ for (let i = 0, maxi = attributes.snapshotLength; i < maxi; i++) {
+ const attribute = attributes.snapshotItem(i);
+ if (attribute.value.includes(keyPrefix)) attribute.value = this.updateString(attribute.value);
+ }
+ }
+ };
+
+ localization.updateDocument(document);
+}
+
+var localizeOnLoad = function (window, provider) {
+ // standard event if loaded by a standard window
+ window.document.addEventListener('DOMContentLoaded', () => {
+ this.localizeNow(window, provider);
+ }, { once: true });
+
+ // custom event, fired by the overlay loader after it has finished loading
+ // the editAccount dialog is never called as a provider, but from tbsync itself
+ let eventId = "DOMOverlayLoaded_"
+ + (!provider || window.location.href.startsWith("chrome://tbsync/content/manager/editAccount.") ? "" : provider + "4")
+ + "tbsync@jobisoft.de";
+ window.document.addEventListener(eventId, () => {
+ TbSync.localizeNow(window, provider);
+ }, { once: true });
+}
+
+
+
+var generateUUID = function () {
+ const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+ return uuidGenerator.generateUUID().toString().replace(/[{}]/g, '');
+}
diff --git a/content/modules/tools.js b/content/modules/tools.js
new file mode 100644
index 0000000..fe406b9
--- /dev/null
+++ b/content/modules/tools.js
@@ -0,0 +1,80 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var tools = {
+
+ load: async function () {
+ },
+
+ unload: async function () {
+ },
+
+ // async sleep function using Promise to postpone actions to keep UI responsive
+ sleep : function (_delay, useRequestIdleCallback = false) {
+ let useIdleCallback = false;
+ let delay = 5;//_delay;
+ if (TbSync.window.requestIdleCallback && useRequestIdleCallback) {
+ useIdleCallback = true;
+ delay= 2;
+ }
+ let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+
+ return new Promise(function(resolve, reject) {
+ let event = {
+ notify: function(timer) {
+ if (useIdleCallback) {
+ TbSync.window.requestIdleCallback(resolve);
+ } else {
+ resolve();
+ }
+ }
+ }
+ timer.initWithCallback(event, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ });
+ },
+
+ // this is derived from: http://jonisalonen.com/2012/from-utf-16-to-utf-8-in-javascript/
+ // javascript strings are utf16, btoa needs utf8 , so we need to encode
+ toUTF8: function (str) {
+ var utf8 = "";
+ for (var i=0; i < str.length; i++) {
+ var charcode = str.charCodeAt(i);
+ if (charcode < 0x80) utf8 += String.fromCharCode(charcode);
+ else if (charcode < 0x800) {
+ utf8 += String.fromCharCode(0xc0 | (charcode >> 6),
+ 0x80 | (charcode & 0x3f));
+ }
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
+ utf8 += String.fromCharCode(0xe0 | (charcode >> 12),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+
+ // surrogate pair
+ else {
+ i++;
+ // UTF-16 encodes 0x10000-0x10FFFF by
+ // subtracting 0x10000 and splitting the
+ // 20 bits of 0x0-0xFFFFF into two halves
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
+ | (str.charCodeAt(i) & 0x3ff))
+ utf8 += String.fromCharCode(0xf0 | (charcode >>18),
+ 0x80 | ((charcode>>12) & 0x3f),
+ 0x80 | ((charcode>>6) & 0x3f),
+ 0x80 | (charcode & 0x3f));
+ }
+ }
+ return utf8;
+ },
+
+ b64encode: function (str) {
+ return btoa(this.toUTF8(str));
+ }
+}
diff --git a/content/overlays/abCSS.xhtml b/content/overlays/abCSS.xhtml
new file mode 100644
index 0000000..073d5cb
--- /dev/null
+++ b/content/overlays/abCSS.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://tbsync/content/skin/ab.css" type="text/css"?>
+
+<overlay
+ id="TbSyncAbCssOverlay"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+</overlay>
diff --git a/content/overlays/abNewCardWindowOverlay.js b/content/overlays/abNewCardWindowOverlay.js
new file mode 100644
index 0000000..06bdbca
--- /dev/null
+++ b/content/overlays/abNewCardWindowOverlay.js
@@ -0,0 +1,51 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncNewCardWindow = {
+
+ onInject: function (window) {
+ window.document.getElementById("abPopup").parentNode.addEventListener("select", tbSyncNewCardWindow.onAbSelectChangeNewCard, false);
+
+ let items = window.document.getElementsByClassName("abMenuItem");
+ for (let i=0; i < items.length; i++) {
+ let icon = "";
+ let abURI = items[i].value;
+
+ if (abURI) {
+ let ab = MailServices.ab.getDirectory(abURI);
+ if (!ab.isMailList && !ab.isRemote) {
+ try {
+ icon = TbSync.addressbook.getStringValue(ab, "tbSyncIcon", "");
+ } catch (e) {}
+ }
+ }
+
+ if (icon) {
+ items[i].setAttribute("TbSyncIcon", icon);
+ }
+ }
+ },
+
+ onRemove: function (window) {
+ window.document.getElementById("abPopup").parentNode.removeEventListener("select", tbSyncNewCardWindow.onAbSelectChangeNewCard, false);
+ let items = window.document.getElementsByClassName("abMenuItem");
+ for (let i=0; i < items.length; i++) {
+ if (items[i].getAttribute("TbSyncIcon")) {
+ items[i].removeAttribute("TbSyncIcon");
+ }
+ }
+ },
+
+ onAbSelectChangeNewCard: function () {
+ window.sizeToContent();
+ },
+}
diff --git a/content/overlays/abNewCardWindowOverlay.xhtml b/content/overlays/abNewCardWindowOverlay.xhtml
new file mode 100644
index 0000000..72b7ab4
--- /dev/null
+++ b/content/overlays/abNewCardWindowOverlay.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<overlay
+ id="TbSyncNewCardWindow"
+ omscope="tbSyncNewCardWindow"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script type="application/javascript" src="chrome://tbsync/content/overlays/abNewCardWindowOverlay.js" />
+
+</overlay>
diff --git a/content/overlays/addressbookiconsoverlay.js b/content/overlays/addressbookiconsoverlay.js
new file mode 100644
index 0000000..152341c
--- /dev/null
+++ b/content/overlays/addressbookiconsoverlay.js
@@ -0,0 +1,40 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncAddressBook = {
+
+ onInject: function (window) {
+ //hook into getProperties of abDirTreeItem to inject our own icons for the address books
+ if (!window.abDirTreeItem.prototype.hasOwnProperty("_origBeforeTbSyncGetProperties")) {
+ window.abDirTreeItem.prototype._origBeforeTbSyncGetProperties = window.abDirTreeItem.prototype.getProperties;
+ window.abDirTreeItem.prototype.getProperties = function () {
+ //get original properties
+ let properties = this._origBeforeTbSyncGetProperties().split(" ");
+
+ let type = "";
+ if (!this._directory.isMailList && !this._directory.isRemote) {
+ try {
+ type = TbSync.addressbook.getStringValue(this._directory, "tbSyncIcon", "");
+ } catch (e) {}
+ }
+
+ if (type) properties.push(type);
+ return properties.join(" ");
+ }
+ }
+ },
+
+ onRemove: function (window) {
+ window.abDirTreeItem.prototype.getProperties = window.abDirTreeItem.prototype._origBeforeTbSyncGetProperties;
+ delete window.abDirTreeItem.prototype._origBeforeTbSyncGetProperties;
+ }
+}
diff --git a/content/overlays/addressbookiconsoverlay.xhtml b/content/overlays/addressbookiconsoverlay.xhtml
new file mode 100644
index 0000000..28cf816
--- /dev/null
+++ b/content/overlays/addressbookiconsoverlay.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<overlay
+ id="TbSyncAbIconOverlay"
+ omscope="tbSyncAddressBook"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script type="application/javascript" src="chrome://tbsync/content/overlays/addressbookiconsoverlay.js" />
+
+</overlay>
diff --git a/content/overlays/calendar-event-dialog-attendees.js b/content/overlays/calendar-event-dialog-attendees.js
new file mode 100644
index 0000000..33177e9
--- /dev/null
+++ b/content/overlays/calendar-event-dialog-attendees.js
@@ -0,0 +1,35 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncAttendeeEventDialog = {
+
+ onInject: function (window) {
+ // Add autoComplete for TbSync - find all fields with autocompletesearch attribute
+ let fields = window.document.querySelectorAll('[autocompletesearch]');
+ for (let field of fields) {
+ let autocompletesearch = field.getAttribute("autocompletesearch");
+ if (autocompletesearch.indexOf("tbSyncAutoCompleteSearch") == -1) {
+ field.setAttribute("autocompletesearch", autocompletesearch + " tbSyncAutoCompleteSearch");
+ }
+ }
+ },
+
+ onRemove: function (window) {
+ // Remove autoComplete for TbSync
+ let fields = window.document.querySelectorAll('[autocompletesearch]');
+ for (let field of fields) {
+ let autocompletesearch = field.getAttribute("autocompletesearch").replace("tbSyncAutoCompleteSearch", "");
+ field.setAttribute("autocompletesearch", autocompletesearch.trim());
+ }
+ }
+
+}
diff --git a/content/overlays/calendar-event-dialog-attendees.xhtml b/content/overlays/calendar-event-dialog-attendees.xhtml
new file mode 100644
index 0000000..289ab32
--- /dev/null
+++ b/content/overlays/calendar-event-dialog-attendees.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<overlay
+ id="TbSyncAttendeeEventDialogOverlay"
+ omscope="tbSyncAttendeeEventDialog"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script type="application/javascript" src="chrome://tbsync/content/overlays/calendar-event-dialog-attendees.js" />
+
+</overlay>
diff --git a/content/overlays/messenger.js b/content/overlays/messenger.js
new file mode 100644
index 0000000..b6d0a97
--- /dev/null
+++ b/content/overlays/messenger.js
@@ -0,0 +1,22 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+var tbSyncMessenger = {
+
+ onInject: function (window) {
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", null);
+ },
+
+ onRemove: function (window) {
+ },
+
+};
diff --git a/content/overlays/messenger.xhtml b/content/overlays/messenger.xhtml
new file mode 100644
index 0000000..37d07ca
--- /dev/null
+++ b/content/overlays/messenger.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<overlay
+ id="TbSyncMessengerOverlay"
+ omscope="tbSyncMessenger"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script type="application/javascript" src="chrome://tbsync/content/overlays/messenger.js" />
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+
+ <menupopup id="tbsync.statusmenu">
+ <menuitem label="__TBSYNCMSG_popup.opensettings__" oncommand="TbSync.manager.openManagerWindow(event);" />
+ </menupopup>
+
+ <label id="tbsync.status" class="statusbarpanel" value="TbSync" onclick="TbSync.manager.openManagerWindow(event);" appendto="status-bar"/>
+
+ <!--menuitem id="tbsync.taskPopupEntry" label="__TBSYNCMSG_menu.settingslabel__" appendto="taskPopup" onclick="TbSync.manager.openManagerWindow(event);" / -->
+ <menuitem id="tbsync.accountmgrEntry" label="__TBSYNCMSG_menu.settingslabel__" insertbefore="menu_accountmgr" oncommand="TbSync.manager.openManagerWindow(event);" />
+
+</overlay>
diff --git a/content/overlays/messengercompose.js b/content/overlays/messengercompose.js
new file mode 100644
index 0000000..4533410
--- /dev/null
+++ b/content/overlays/messengercompose.js
@@ -0,0 +1,58 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+/*
+ The contact sidebar is loaded inside a browser element. That load is not seen by the windowlistener and thus overlays are not injected.
+ The load is triggered/indicated by setting the src attribute of the browser -> mutation observer and delayed inject
+*/
+
+var tbSyncMessengerCompose = {
+
+ onInject: function (window) {
+ // Create the MutationObserver: try to inject after the src attribute of the sidebar browser has been changed, thus the URL has been loaded
+ tbSyncMessengerCompose.mObserver = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ tbSyncMessengerCompose.timer = window.setInterval(function(){
+ let targetWindow = window.document.getElementById("sidebar").contentWindow.wrappedJSObject;
+ if (targetWindow) {
+ window.clearInterval(tbSyncMessengerCompose.timer);
+ TbSync.messenger.overlayManager.injectAllOverlays(targetWindow);
+ }
+ }, 1000);
+ });
+ });
+
+ tbSyncMessengerCompose.mObserver.observe(window.document.getElementById("sidebar"), { attributes: true, childList: false, characterData: false });
+
+ // Add autoComplete for TbSync - find all fields with autocompletesearch attribute
+ let fields = window.document.querySelectorAll('[autocompletesearch]');
+ for (let field of fields) {
+ let autocompletesearch = field.getAttribute("autocompletesearch");
+ if (autocompletesearch.indexOf("tbSyncAutoCompleteSearch") == -1) {
+ field.setAttribute("autocompletesearch", autocompletesearch + " tbSyncAutoCompleteSearch");
+ }
+ }
+ },
+
+ onRemove: function (window) {
+ let targetWindow = window.document.getElementById("sidebar").contentWindow.wrappedJSObject;
+ TbSync.messenger.overlayManager.removeAllOverlays(targetWindow);
+ tbSyncMessengerCompose.mObserver.disconnect();
+
+ // Remove autoComplete for TbSync
+ let fields = window.document.querySelectorAll('[autocompletesearch]');
+ for (let field of fields) {
+ let autocompletesearch = field.getAttribute("autocompletesearch").replace("tbSyncAutoCompleteSearch", "");
+ field.setAttribute("autocompletesearch", autocompletesearch.trim());
+ }
+ }
+}
diff --git a/content/overlays/messengercompose.xhtml b/content/overlays/messengercompose.xhtml
new file mode 100644
index 0000000..b372d18
--- /dev/null
+++ b/content/overlays/messengercompose.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<overlay
+ id="TbSyncMessengerComposeOverlay"
+ omscope="tbSyncMessengerCompose"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script type="application/javascript" src="chrome://tbsync/content/overlays/messengercompose.js" />
+
+</overlay>
diff --git a/content/passwordPrompt/passwordPrompt.js b/content/passwordPrompt/passwordPrompt.js
new file mode 100644
index 0000000..15d5ccb
--- /dev/null
+++ b/content/passwordPrompt/passwordPrompt.js
@@ -0,0 +1,45 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var tbSyncPassword = {
+
+ onload: function () {
+ let data = window.arguments[0];
+ this.resolve = window.arguments[1];
+ this.resolved = false;
+
+ this.namefield = document.getElementById("tbsync.account");
+ this.passfield = document.getElementById("tbsync.password");
+ this.userfield = document.getElementById("tbsync.user");
+
+ this.namefield.value = data.accountname;
+ this.userfield.value = data.username;
+ this.userfield.disabled = data.usernameLocked;
+
+ document.addEventListener("dialogaccept", tbSyncPassword.doOK.bind(this));
+ window.addEventListener("unload", tbSyncPassword.doCANCEL.bind(this));
+ document.getElementById("tbsync.password").focus();
+ },
+
+ doOK: function (event) {
+ if (!this.resolved) {
+ this.resolved = true
+ this.resolve({username: this.userfield.value, password: this.passfield.value});
+ }
+ },
+
+ doCANCEL: function (event) {
+ if (!this.resolved) {
+ this.resolved = true
+ this.resolve(false);
+ }
+ },
+
+};
diff --git a/content/passwordPrompt/passwordPrompt.xhtml b/content/passwordPrompt/passwordPrompt.xhtml
new file mode 100644
index 0000000..ab7f9e7
--- /dev/null
+++ b/content/passwordPrompt/passwordPrompt.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="__TBSYNCMSG_password.title__"
+ buttons="accept,cancel"
+ onload="tbSyncPassword.onload();">
+
+ <script type="application/javascript" src="chrome://tbsync/content/passwordPrompt/passwordPrompt.js"/>
+ <script type="text/javascript" src="chrome://tbsync/content/scripts/locales.js" />
+
+ <vbox>
+ <description style="width: 350px;">__TBSYNCMSG_password.description__</description>
+ <grid style="margin:1ex;">
+ <columns>
+ <column flex="0"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row style="margin-bottom:1ex;">
+ <label value="__TBSYNCMSG_password.account__"/>
+ <label class="header" id="tbsync.account" />
+ </row>
+ <row align="center">
+ <label value="__TBSYNCMSG_password.user__"/>
+ <html:input id="tbsync.user" />
+ </row>
+ <row align="center">
+ <label value="__TBSYNCMSG_password.password__"/>
+ <html:input type="password" id="tbsync.password"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+
+</dialog>
diff --git a/content/scripts/bootstrap.js b/content/scripts/bootstrap.js
new file mode 100644
index 0000000..706946f
--- /dev/null
+++ b/content/scripts/bootstrap.js
@@ -0,0 +1,89 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function startup(data, reason) {
+ // possible reasons: APP_STARTUP, ADDON_ENABLE, ADDON_INSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE.
+
+ // set default prefs
+ let defaults = Services.prefs.getDefaultBranch("extensions.tbsync.");
+ defaults.setBoolPref("debug.testoptions", false);
+ defaults.setBoolPref("log.toconsole", false);
+ defaults.setIntPref("log.userdatalevel", 0); //0 - off 1 - userdata only on errors 2 - including full userdata, 3 - extra infos
+
+ // Check if at least one main window has finished loading
+ let windows = Services.wm.getEnumerator("mail:3pane");
+ if (windows.hasMoreElements()) {
+ let domWindow = windows.getNext();
+ WindowListener.loadIntoWindow(domWindow);
+ }
+
+ // Wait for any new windows to open.
+ Services.wm.addListener(WindowListener);
+
+ //DO NOT ADD ANYTHING HERE!
+}
+
+function shutdown(data, reason) {
+ //possible reasons: APP_SHUTDOWN, ADDON_DISABLE, ADDON_UNINSTALL, ADDON_UPGRADE, or ADDON_DOWNGRADE
+
+ // Stop listening for any new windows to open.
+ Services.wm.removeListener(WindowListener);
+
+ var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+ TbSync.enabled = false;
+ TbSync.unload().then(function() {
+ Cu.unload("chrome://tbsync/content/tbsync.jsm");
+ Cu.unload("chrome://tbsync/content/HttpRequest.jsm");
+ Cu.unload("chrome://tbsync/content/OverlayManager.jsm");
+ // HACK WARNING:
+ // - the Addon Manager does not properly clear all addon related caches on update;
+ // - in order to fully update images and locales, their caches need clearing here
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+ Services.obs.notifyObservers(null, "chrome-flush-caches");
+ });
+}
+
+
+var WindowListener = {
+
+ async loadIntoWindow(window) {
+ if (window.document.readyState != "complete") {
+ // Make sure the window load has completed.
+ await new Promise(resolve => {
+ window.addEventListener("load", resolve, { once: true });
+ });
+ }
+
+ // Check if the opened window is the one we want to modify.
+ if (window.document.documentElement.getAttribute("windowtype") === "mail:3pane") {
+ // the main window has loaded, continue with init
+ var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+ if (!TbSync.enabled) TbSync.load(window, addon, extension);
+ }
+ },
+
+
+ unloadFromWindow(window) {
+ },
+
+ // nsIWindowMediatorListener functions
+ onOpenWindow(xulWindow) {
+ // A new window has opened.
+ let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ // The domWindow.document.documentElement.getAttribute("windowtype") is not set before the load, so we cannot check it here
+ this.loadIntoWindow(domWindow);
+ },
+
+ onCloseWindow(xulWindow) {
+ },
+
+ onWindowTitleChange(xulWindow, newTitle) {
+ },
+};
diff --git a/content/scripts/locales.js b/content/scripts/locales.js
new file mode 100644
index 0000000..806c4f2
--- /dev/null
+++ b/content/scripts/locales.js
@@ -0,0 +1,3 @@
+var { TbSync } = ChromeUtils.import("chrome://tbsync/content/tbsync.jsm");
+
+TbSync.localizeOnLoad(window);
diff --git a/content/skin/ab.css b/content/skin/ab.css
new file mode 100644
index 0000000..5c8169b
--- /dev/null
+++ b/content/skin/ab.css
@@ -0,0 +1,8 @@
+treechildren::-moz-tree-image(DirCol, orphaned) {
+ margin-inline-end: 2px;
+ list-style-image: url("chrome://tbsync/content/skin/error16.png");
+}
+
+.abMenuItem[AddrBook="true"][TbSyncIcon="orphaned"] {
+ list-style-image: url("chrome://tbsync/content/skin/error16.png");
+}
diff --git a/content/skin/acl_ro.png b/content/skin/acl_ro.png
new file mode 100644
index 0000000..ee806fc
--- /dev/null
+++ b/content/skin/acl_ro.png
Binary files differ
diff --git a/content/skin/acl_ro2.png b/content/skin/acl_ro2.png
new file mode 100644
index 0000000..f3f1de9
--- /dev/null
+++ b/content/skin/acl_ro2.png
Binary files differ
diff --git a/content/skin/acl_rw.png b/content/skin/acl_rw.png
new file mode 100644
index 0000000..109fb45
--- /dev/null
+++ b/content/skin/acl_rw.png
Binary files differ
diff --git a/content/skin/acl_rw2.png b/content/skin/acl_rw2.png
new file mode 100644
index 0000000..1b799fd
--- /dev/null
+++ b/content/skin/acl_rw2.png
Binary files differ
diff --git a/content/skin/add16.png b/content/skin/add16.png
new file mode 100644
index 0000000..b366b96
--- /dev/null
+++ b/content/skin/add16.png
Binary files differ
diff --git a/content/skin/browserOverlay.css b/content/skin/browserOverlay.css
new file mode 100644
index 0000000..3c09214
--- /dev/null
+++ b/content/skin/browserOverlay.css
@@ -0,0 +1,10 @@
+/* skin/toolbar-button.css */
+
+#tbsync-toolbarbutton {
+ list-style-image: url("chrome://tbsync/content/skin/sync.png");
+}
+
+toolbar[iconsize="small"] #tbsync-toolbarbutton {
+ list-style-image: url("chrome://tbsync/content/skin/syncsmall.png");
+}
+
diff --git a/content/skin/calendar16.png b/content/skin/calendar16.png
new file mode 100644
index 0000000..2f7f575
--- /dev/null
+++ b/content/skin/calendar16.png
Binary files differ
diff --git a/content/skin/calendar16_shared.png b/content/skin/calendar16_shared.png
new file mode 100644
index 0000000..c496c40
--- /dev/null
+++ b/content/skin/calendar16_shared.png
Binary files differ
diff --git a/content/skin/catman32.png b/content/skin/catman32.png
new file mode 100644
index 0000000..f87b71e
--- /dev/null
+++ b/content/skin/catman32.png
Binary files differ
diff --git a/content/skin/connect16.png b/content/skin/connect16.png
new file mode 100644
index 0000000..7af930e
--- /dev/null
+++ b/content/skin/connect16.png
Binary files differ
diff --git a/content/skin/contacts16.png b/content/skin/contacts16.png
new file mode 100644
index 0000000..101a9d3
--- /dev/null
+++ b/content/skin/contacts16.png
Binary files differ
diff --git a/content/skin/contacts16_shared.png b/content/skin/contacts16_shared.png
new file mode 100644
index 0000000..4a5d049
--- /dev/null
+++ b/content/skin/contacts16_shared.png
Binary files differ
diff --git a/content/skin/del16.png b/content/skin/del16.png
new file mode 100644
index 0000000..8db349f
--- /dev/null
+++ b/content/skin/del16.png
Binary files differ
diff --git a/content/skin/disabled16.png b/content/skin/disabled16.png
new file mode 100644
index 0000000..ee10cd0
--- /dev/null
+++ b/content/skin/disabled16.png
Binary files differ
diff --git a/content/skin/eas16.png b/content/skin/eas16.png
new file mode 100644
index 0000000..072d5f0
--- /dev/null
+++ b/content/skin/eas16.png
Binary files differ
diff --git a/content/skin/error16.png b/content/skin/error16.png
new file mode 100644
index 0000000..5177258
--- /dev/null
+++ b/content/skin/error16.png
Binary files differ
diff --git a/content/skin/fix_dropdown_1534697.css b/content/skin/fix_dropdown_1534697.css
new file mode 100644
index 0000000..c5e7606
--- /dev/null
+++ b/content/skin/fix_dropdown_1534697.css
@@ -0,0 +1,4 @@
+button.plain .button-menu-dropmarker {
+ -moz-appearance: toolbarbutton-dropdown;
+ display: block;
+}
diff --git a/content/skin/group32.png b/content/skin/group32.png
new file mode 100644
index 0000000..a58f920
--- /dev/null
+++ b/content/skin/group32.png
Binary files differ
diff --git a/content/skin/help32.png b/content/skin/help32.png
new file mode 100644
index 0000000..db2141a
--- /dev/null
+++ b/content/skin/help32.png
Binary files differ
diff --git a/content/skin/info16.png b/content/skin/info16.png
new file mode 100644
index 0000000..eabaf75
--- /dev/null
+++ b/content/skin/info16.png
Binary files differ
diff --git a/content/skin/lock24.png b/content/skin/lock24.png
new file mode 100644
index 0000000..d0e07ab
--- /dev/null
+++ b/content/skin/lock24.png
Binary files differ
diff --git a/content/skin/provider16.png b/content/skin/provider16.png
new file mode 100644
index 0000000..d7ddcb8
--- /dev/null
+++ b/content/skin/provider16.png
Binary files differ
diff --git a/content/skin/provider32.png b/content/skin/provider32.png
new file mode 100644
index 0000000..4a1d0a9
--- /dev/null
+++ b/content/skin/provider32.png
Binary files differ
diff --git a/content/skin/report_open.png b/content/skin/report_open.png
new file mode 100644
index 0000000..5c0f4cb
--- /dev/null
+++ b/content/skin/report_open.png
Binary files differ
diff --git a/content/skin/report_send.png b/content/skin/report_send.png
new file mode 100644
index 0000000..8dcc893
--- /dev/null
+++ b/content/skin/report_send.png
Binary files differ
diff --git a/content/skin/settings32.png b/content/skin/settings32.png
new file mode 100644
index 0000000..9c6a889
--- /dev/null
+++ b/content/skin/settings32.png
Binary files differ
diff --git a/content/skin/slider-off.png b/content/skin/slider-off.png
new file mode 100644
index 0000000..8243d81
--- /dev/null
+++ b/content/skin/slider-off.png
Binary files differ
diff --git a/content/skin/slider-on.png b/content/skin/slider-on.png
new file mode 100644
index 0000000..c9dd28e
--- /dev/null
+++ b/content/skin/slider-on.png
Binary files differ
diff --git a/content/skin/spinner.gif b/content/skin/spinner.gif
new file mode 100644
index 0000000..5b33f7e
--- /dev/null
+++ b/content/skin/spinner.gif
Binary files differ
diff --git a/content/skin/src/LICENSE b/content/skin/src/LICENSE
new file mode 100644
index 0000000..f8ad367
--- /dev/null
+++ b/content/skin/src/LICENSE
@@ -0,0 +1,131 @@
+The following files are released into PUBLIC DOMAIN.
+
+slider.xcf
+slider-on.png
+slider-off.png
+
+The png files are based on the xcf file.
+
+
+
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work. \ No newline at end of file
diff --git a/content/skin/src/slider-off.png b/content/skin/src/slider-off.png
new file mode 100644
index 0000000..8243d81
--- /dev/null
+++ b/content/skin/src/slider-off.png
Binary files differ
diff --git a/content/skin/src/slider-on.png b/content/skin/src/slider-on.png
new file mode 100644
index 0000000..c9dd28e
--- /dev/null
+++ b/content/skin/src/slider-on.png
Binary files differ
diff --git a/content/skin/src/slider.xcf b/content/skin/src/slider.xcf
new file mode 100644
index 0000000..cca28cd
--- /dev/null
+++ b/content/skin/src/slider.xcf
Binary files differ
diff --git a/content/skin/sync16.png b/content/skin/sync16.png
new file mode 100644
index 0000000..87e3ee7
--- /dev/null
+++ b/content/skin/sync16.png
Binary files differ
diff --git a/content/skin/sync16_1.png b/content/skin/sync16_1.png
new file mode 100644
index 0000000..679d5d0
--- /dev/null
+++ b/content/skin/sync16_1.png
Binary files differ
diff --git a/content/skin/sync16_2.png b/content/skin/sync16_2.png
new file mode 100644
index 0000000..49fe7c8
--- /dev/null
+++ b/content/skin/sync16_2.png
Binary files differ
diff --git a/content/skin/sync16_3.png b/content/skin/sync16_3.png
new file mode 100644
index 0000000..87e3ee7
--- /dev/null
+++ b/content/skin/sync16_3.png
Binary files differ
diff --git a/content/skin/sync16_4.png b/content/skin/sync16_4.png
new file mode 100644
index 0000000..94cd442
--- /dev/null
+++ b/content/skin/sync16_4.png
Binary files differ
diff --git a/content/skin/tbsync.png b/content/skin/tbsync.png
new file mode 100644
index 0000000..be77a1f
--- /dev/null
+++ b/content/skin/tbsync.png
Binary files differ
diff --git a/content/skin/tbsync64.png b/content/skin/tbsync64.png
new file mode 100644
index 0000000..0877be0
--- /dev/null
+++ b/content/skin/tbsync64.png
Binary files differ
diff --git a/content/skin/tick16.png b/content/skin/tick16.png
new file mode 100644
index 0000000..db93138
--- /dev/null
+++ b/content/skin/tick16.png
Binary files differ
diff --git a/content/skin/todo16.png b/content/skin/todo16.png
new file mode 100644
index 0000000..0023b3a
--- /dev/null
+++ b/content/skin/todo16.png
Binary files differ
diff --git a/content/skin/trash16.png b/content/skin/trash16.png
new file mode 100644
index 0000000..b8c10d3
--- /dev/null
+++ b/content/skin/trash16.png
Binary files differ
diff --git a/content/skin/update32.png b/content/skin/update32.png
new file mode 100644
index 0000000..f9621e8
--- /dev/null
+++ b/content/skin/update32.png
Binary files differ
diff --git a/content/skin/user48.png b/content/skin/user48.png
new file mode 100644
index 0000000..51bf808
--- /dev/null
+++ b/content/skin/user48.png
Binary files differ
diff --git a/content/skin/warning16.png b/content/skin/warning16.png
new file mode 100644
index 0000000..3f04b67
--- /dev/null
+++ b/content/skin/warning16.png
Binary files differ
diff --git a/content/tbsync.jsm b/content/tbsync.jsm
new file mode 100644
index 0000000..d75b25d
--- /dev/null
+++ b/content/tbsync.jsm
@@ -0,0 +1,166 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var EXPORTED_SYMBOLS = ["TbSync"];
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+var { OS } =ChromeUtils.import("resource://gre/modules/osfile.jsm");
+var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
+var { OverlayManager } = ChromeUtils.import("chrome://tbsync/content/OverlayManager.jsm");
+
+var TbSync = {
+
+ enabled: false,
+ shutdown: false,
+
+ window: null,
+ addon: null,
+ version: 0,
+ debugMode: false,
+ apiVersion: "2.4",
+
+ prefs: Services.prefs.getBranch("extensions.tbsync."),
+
+ decoder: new TextDecoder(),
+ encoder: new TextEncoder(),
+
+ modules : [],
+ extension : null,
+
+ // global load
+ load: async function (window, addon, extension) {
+ //public module and IO module needs to be loaded beforehand
+ Services.scriptloader.loadSubScript("chrome://tbsync/content/modules/public.js", this, "UTF-8");
+ Services.scriptloader.loadSubScript("chrome://tbsync/content/modules/io.js", this, "UTF-8");
+
+ //clear debug log on start
+ this.io.initFile("debug.log");
+
+ this.window = window;
+ this.addon = addon;
+ this.addon.contributorsURL = "https://github.com/jobisoft/TbSync/blob/master/CONTRIBUTORS.md";
+ this.extension = extension;
+ this.dump("TbSync init","Start (" + this.addon.version.toString() + ")");
+
+ //print information about Thunderbird version and OS
+ this.dump(Services.appinfo.name, Services.appinfo.version + " on " + OS.Constants.Sys.Name);
+
+ // register modules to be used by TbSync
+ this.modules.push({name: "db", state: 0});
+ this.modules.push({name: "abAutoComplete", state: 0});
+ this.modules.push({name: "addressbook", state: 0});
+ this.modules.push({name: "lightning", state: 0});
+ this.modules.push({name: "cardbook", state: 0});
+ this.modules.push({name: "eventlog", state: 0});
+ this.modules.push({name: "core", state: 0});
+ this.modules.push({name: "passwordManager", state: 0});
+ this.modules.push({name: "network", state: 0});
+ this.modules.push({name: "tools", state: 0});
+ this.modules.push({name: "manager", state: 0});
+ this.modules.push({name: "providers", state: 0});
+ this.modules.push({name: "messenger", state: 0});
+
+ //load modules
+ for (let module of this.modules) {
+ try {
+ Services.scriptloader.loadSubScript("chrome://tbsync/content/modules/" + module.name + ".js", this, "UTF-8");
+ module.state = 1;
+ this.dump("Loading module <" + module.name + ">", "OK");
+ } catch (e) {
+ this.dump("Loading module <" + module.name + ">", "FAILED!");
+ Components.utils.reportError(e);
+ }
+ }
+
+ //call init function of loaded modules
+ for (let module of this.modules) {
+ if (module.state == 1) {
+ try {
+ this.dump("Initializing module", "<" + module.name + ">");
+ await this[module.name].load();
+ module.state = 2;
+ } catch (e) {
+ this.dump("Initialization of module <" + module.name + "> FAILED", e.message + "\n" + e.stack);
+ Components.utils.reportError(e);
+ }
+ }
+ }
+
+ //was debug mode enabled during startup?
+ this.debugMode = (this.prefs.getIntPref("log.userdatalevel") > 0);
+
+ //enable TbSync
+ this.enabled = true;
+
+ //notify about finished init of TbSync
+ Services.obs.notifyObservers(null, "tbsync.observer.manager.updateSyncstate", null);
+ Services.obs.notifyObservers(null, 'tbsync.observer.initialized', null);
+
+ //activate sync timer
+ this.syncTimer.start();
+
+ this.dump("TbSync init","Done");
+ },
+
+ // global unload
+ unload: async function() {
+ //cancel sync timer
+ this.syncTimer.cancel();
+
+ //unload modules in reverse order
+ this.modules.reverse();
+ for (let module of this.modules) {
+ if (module.state == 2) {
+ try {
+ await this[module.name].unload();
+ this.dump("Unloading module <" + module.name + ">", "OK");
+ } catch (e) {
+ this.dump("Unloading module <" + module.name + ">", "FAILED!");
+ Components.utils.reportError(e);
+ }
+ }
+ }
+ },
+
+ // timer for periodic sync
+ syncTimer: {
+ timer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
+
+ start: function () {
+ this.timer.cancel();
+ this.timer.initWithCallback(this.event, 60000, 3); //run timer every 60s
+ },
+
+ cancel: function () {
+ this.timer.cancel();
+ },
+
+ event: {
+ notify: function (timer) {
+ if (TbSync.enabled) {
+ //get all accounts and check, which one needs sync
+ let accounts = TbSync.db.getAccounts();
+ for (let i=0; i<accounts.IDs.length; i++) {
+ let now = Date.now();
+ let syncInterval = accounts.data[accounts.IDs[i]].autosync * 60 * 1000;
+ let lastsynctime = accounts.data[accounts.IDs[i]].lastsynctime;
+ let noAutosyncUntil = accounts.data[accounts.IDs[i]].noAutosyncUntil || 0;
+ if (TbSync.core.isEnabled(accounts.IDs[i]) && (syncInterval > 0) && (now > (lastsynctime + syncInterval)) && (now > noAutosyncUntil)) {
+ TbSync.core.syncAccount(accounts.IDs[i]);
+ }
+ }
+ }
+ }
+ }
+ }
+};
diff --git a/crowdin.yml b/crowdin.yml
new file mode 100644
index 0000000..3a01c46
--- /dev/null
+++ b/crowdin.yml
@@ -0,0 +1,5 @@
+commit_message: https://crowdin.com/project/tbsync
+files:
+ - source: /_locales/en-US/*
+ translation: /_locales/%osx_locale%/%original_file_name%
+ escape_special_characters: 0
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..6fc63fc
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,32 @@
+{
+ "applications": {
+ "gecko": {
+ "id": "tbsync@jobisoft.de",
+ "strict_min_version": "78.0",
+ "strict_max_version": "78.*"
+ }
+ },
+ "manifest_version": 2,
+ "name": "TbSync",
+ "version": "2.19",
+ "author": "John Bieling",
+ "homepage_url": "https://github.com/jobisoft/TbSync",
+ "default_locale": "en-US",
+ "description": "__MSG_extensionDescription__",
+ "icons": {
+ "32": "content/skin/tbsync.png"
+ },
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "experiment_apis": {
+ "BootstrapLoader": {
+ "schema": "content/api/BootstrapLoader/schema.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "paths": [["BootstrapLoader"]],
+ "script": "content/api/BootstrapLoader/implementation.js"
+ }
+ }
+ }
+}
diff --git a/screenshots/AdvancedConfig.png b/screenshots/AdvancedConfig.png
new file mode 100644
index 0000000..3e60f6a
--- /dev/null
+++ b/screenshots/AdvancedConfig.png
Binary files differ
diff --git a/screenshots/CalDAV-UserName.png b/screenshots/CalDAV-UserName.png
new file mode 100644
index 0000000..a35c068
--- /dev/null
+++ b/screenshots/CalDAV-UserName.png
Binary files differ
diff --git a/screenshots/ExportImport_001.PNG b/screenshots/ExportImport_001.PNG
new file mode 100644
index 0000000..4fd06d7
--- /dev/null
+++ b/screenshots/ExportImport_001.PNG
Binary files differ
diff --git a/screenshots/ExportImport_002.PNG b/screenshots/ExportImport_002.PNG
new file mode 100644
index 0000000..ee95077
--- /dev/null
+++ b/screenshots/ExportImport_002.PNG
Binary files differ
diff --git a/screenshots/ExportImport_005.PNG b/screenshots/ExportImport_005.PNG
new file mode 100644
index 0000000..23ffae0
--- /dev/null
+++ b/screenshots/ExportImport_005.PNG
Binary files differ
diff --git a/screenshots/TbSync_000.png b/screenshots/TbSync_000.png
new file mode 100644
index 0000000..9708a39
--- /dev/null
+++ b/screenshots/TbSync_000.png
Binary files differ
diff --git a/screenshots/TbSync_001.png b/screenshots/TbSync_001.png
new file mode 100644
index 0000000..133c31d
--- /dev/null
+++ b/screenshots/TbSync_001.png
Binary files differ
diff --git a/screenshots/TbSync_004.png b/screenshots/TbSync_004.png
new file mode 100644
index 0000000..50a7bdd
--- /dev/null
+++ b/screenshots/TbSync_004.png
Binary files differ
diff --git a/screenshots/TbSync_005.png b/screenshots/TbSync_005.png
new file mode 100644
index 0000000..c4725ee
--- /dev/null
+++ b/screenshots/TbSync_005.png
Binary files differ
diff --git a/screenshots/TbSync_Enable_debug_logging.png b/screenshots/TbSync_Enable_debug_logging.png
new file mode 100644
index 0000000..30fca44
--- /dev/null
+++ b/screenshots/TbSync_Enable_debug_logging.png
Binary files differ
diff --git a/screenshots/TbSync_Send_logs_by_mail.png b/screenshots/TbSync_Send_logs_by_mail.png
new file mode 100644
index 0000000..00a3869
--- /dev/null
+++ b/screenshots/TbSync_Send_logs_by_mail.png
Binary files differ
diff --git a/screenshots/beta-release-channel.PNG b/screenshots/beta-release-channel.PNG
new file mode 100644
index 0000000..50ddb9d
--- /dev/null
+++ b/screenshots/beta-release-channel.PNG
Binary files differ
diff --git a/screenshots/custom_provider.PNG b/screenshots/custom_provider.PNG
new file mode 100644
index 0000000..147abee
--- /dev/null
+++ b/screenshots/custom_provider.PNG
Binary files differ
diff --git a/screenshots/de/TbSync.PNG b/screenshots/de/TbSync.PNG
new file mode 100644
index 0000000..9801755
--- /dev/null
+++ b/screenshots/de/TbSync.PNG
Binary files differ
diff --git a/screenshots/de/acl_sharing.png b/screenshots/de/acl_sharing.png
new file mode 100644
index 0000000..d43d70d
--- /dev/null
+++ b/screenshots/de/acl_sharing.png
Binary files differ
diff --git a/screenshots/de/add_dav_1.png b/screenshots/de/add_dav_1.png
new file mode 100644
index 0000000..85d5f1a
--- /dev/null
+++ b/screenshots/de/add_dav_1.png
Binary files differ
diff --git a/screenshots/de/add_dav_2.png b/screenshots/de/add_dav_2.png
new file mode 100644
index 0000000..e1a2f72
--- /dev/null
+++ b/screenshots/de/add_dav_2.png
Binary files differ
diff --git a/screenshots/de/add_eas_1.png b/screenshots/de/add_eas_1.png
new file mode 100644
index 0000000..74062fc
--- /dev/null
+++ b/screenshots/de/add_eas_1.png
Binary files differ
diff --git a/screenshots/de/add_eas_2.png b/screenshots/de/add_eas_2.png
new file mode 100644
index 0000000..843096e
--- /dev/null
+++ b/screenshots/de/add_eas_2.png
Binary files differ
diff --git a/screenshots/de/custom.png b/screenshots/de/custom.png
new file mode 100644
index 0000000..7b1b092
--- /dev/null
+++ b/screenshots/de/custom.png
Binary files differ
diff --git a/screenshots/de/eas_done.png b/screenshots/de/eas_done.png
new file mode 100644
index 0000000..af1002d
--- /dev/null
+++ b/screenshots/de/eas_done.png
Binary files differ
diff --git a/screenshots/de/emails.PNG b/screenshots/de/emails.PNG
new file mode 100644
index 0000000..739ffdf
--- /dev/null
+++ b/screenshots/de/emails.PNG
Binary files differ
diff --git a/screenshots/install-from-file.PNG b/screenshots/install-from-file.PNG
new file mode 100644
index 0000000..044ad89
--- /dev/null
+++ b/screenshots/install-from-file.PNG
Binary files differ
diff --git a/screenshots/missing_provider.PNG b/screenshots/missing_provider.PNG
new file mode 100644
index 0000000..ee0a6a8
--- /dev/null
+++ b/screenshots/missing_provider.PNG
Binary files differ
diff --git a/screenshots/update-addon.PNG b/screenshots/update-addon.PNG
new file mode 100644
index 0000000..1d65747
--- /dev/null
+++ b/screenshots/update-addon.PNG
Binary files differ
diff --git a/screenshots/update_notification_statusbar.png b/screenshots/update_notification_statusbar.png
new file mode 100644
index 0000000..79f57b8
--- /dev/null
+++ b/screenshots/update_notification_statusbar.png
Binary files differ
diff --git a/unused/abDirectory.js b/unused/abDirectory.js
new file mode 100644
index 0000000..bd8cf96
--- /dev/null
+++ b/unused/abDirectory.js
@@ -0,0 +1,325 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var abDirectory = {
+
+ component : null,
+ cid : null,
+
+ /**
+ * Register the component.
+ */
+ register : function () {
+ var name = "@mozilla.org/addressbook/directory;1?type=tbsync-abdirectory";
+ var description = "TbSync AbDirectory";
+ var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].getService(Components.interfaces.nsIUUIDGenerator);
+
+ this.cid = uuidGenerator.generateUUID();
+ this.component = new this.Directory();
+
+ /*Services.console.logStringMessage("abDirectory.js: register"
+ + "\n id: " + this.cid
+ + "\n description: " + description
+ + "\n name: " + name + "\n");*/
+
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.registerFactory(this.cid, description, name, this.component);
+ },
+
+ /**
+ * Unregister the component
+ */
+ unregister : function () {
+ //Services.console.logStringMessage("abDirectory.js: unregister");
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.unregisterFactory(this.cid, this.component);
+ this.cid = null;
+ this.component = null;
+ },
+
+ /**
+ * Class Constructor
+ */
+ Directory : function () {
+ //Services.console.logStringMessage("abDirectory.js: constructor");
+ this.mValue = "";
+ this.mQuery = "";
+ this.mDirPrefId = "";
+ this.mDescription = "";
+ this.mURI = "";
+ this.mCardCache = {};
+
+ //https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/wrappedJSObject
+ this.wrappedJSObject = this;
+ },
+
+}
+
+
+
+
+
+abDirectory.Directory.prototype = {
+ constructor: abDirectory.Directory,
+
+ wrappedJSObject: null,
+
+ /* nsIAbCollection (parent of nsIAbDirectory) */
+ get readOnly() {
+ return false;
+ },
+
+ get isRemote() {
+ return false;
+ },
+
+ get isSecure() {
+ let url = this.serverURL;
+ return (url && (url.indexOf("https") == 0));
+ },
+
+ cardForEmailAddress: function(emailAddress) {
+ return null;
+ },
+
+ getCardFromProperty: function(aProperty, aValue, aCaseSensitive) {
+ return null;
+ },
+
+ getCardsFromProperty: function(aProperty, aValue, aCaseSensitive) {
+ return null;
+ },
+
+
+ /* nsIAbDirectory */
+ propertiesChromeURI: "chrome://sogo-connector/content/addressbook/preferences.addressbook.groupdav.xul",
+
+ get dirName() {
+ //Services.console.logStringMessage("get dirname: " + this.mDescription);
+ return this.mDescription;
+ },
+ set dirName(val) {
+ //Services.console.logStringMessage("set dirname: " + val);
+ if (this.mDescription != val) {
+ let oldValue = this.mDescription;
+ this.mDescription = String(val);
+
+ let prefName = this.mDirPrefId;
+ //let service = Components.classes["@mozilla.org/preferences-service;1"]
+ // .getService(Components.interfaces.nsIPrefService);
+ try {
+ let branch = Services.prefs.getBranch(prefName + ".");
+ branch.setCharPref("description", this.mDescription);
+ }
+ catch(e) {
+ dump("directory-properties: exception (new directory '" + prefName
+ + "', URI '" + this.mValue + "' ?):" + e + "\n");
+ }
+
+ MailServices.ab.notifyItemPropertyChanged(this, "DirName", oldValue, val);
+ }
+ },
+
+ get dirType() {
+ return 0;
+ },
+
+ get fileName() {
+ //Services.console.logStringMessage("get filename");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get URI() {
+ //Services.console.logStringMessage("get uri: " + this.mValue);
+ return this.mValue;
+ },
+
+ get position() {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get lastModifiedDate() {
+ return 0;
+ },
+ set lastModifiedDate(val) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get isMailList() {
+ return false;
+ },
+
+ /* retrieve the sub-directories */
+ get childNodes() {
+ let resultArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIArray);
+ return resultArray.enumerate();
+ },
+
+ get childCards() {
+ let resultArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIArray);
+ return resultArray.enumerate();
+ },
+
+ get isQuery() {
+ return (this.mQuery && this.mQuery.length > 0);
+ },
+
+ init: function(uri) {
+ //Services.console.logStringMessage("INIT: " + uri);
+ let gABPrefix = "tbsync-abdirectory://";
+ //if (uri.indexOf(gABPrefix) == 0) {
+ let prefName = uri.substr(gABPrefix.length);
+ let quMark = uri.indexOf("?");
+ if (quMark > 1) {
+ this.mQuery = uri.substr(quMark);
+ prefName = prefName.substr(0, quMark - gABPrefix.length);
+ }
+ this.mValue = gABPrefix + prefName;
+ this.mDirPrefId = prefName;
+ //Services.console.logStringMessage("prefName: " + prefName);
+
+ let branch = Services.prefs.getBranch(prefName + ".");
+ this.mDescription = branch.getCharPref("description","");
+ this.mURI = branch.getCharPref("uri","");
+ //} else throw "unknown uri: " + uri;
+ },
+
+ deleteDirectory: function (directory) {
+ //Services.console.logStringMessage("deleteDirectory");
+ ////throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ hasCard: function(cards) {
+ //Services.console.logStringMessage("hasCard");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ hasDirectory: function(dir) {
+ //Services.console.logStringMessage("hasDirectory");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ addCard: function(card) {
+ //Services.console.logStringMessage("addCard");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ modifyCard: function(modifiedCard) {
+ //Services.console.logStringMessage("modifyCard");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ deleteCards: function(cards) {
+ //Services.console.logStringMessage("deleteCards");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ dropCard: function(card, needToCopyCard) {
+ //Services.console.logStringMessage("dropCard");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ useForAutocomplete: function(aIdentityKey) {
+ return false;
+ },
+
+ get supportsMailingLists() {
+ return false;
+ },
+
+ get addressLists() {
+ return this.mAddressLists;
+ },
+ set addressLists(val) {
+ this.mAddressLists = val;
+ },
+
+ addMailList: function(list) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get listNickName() {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get description() {
+ //Services.console.logStringMessage("get description: " + this.mDescription);
+ return this.mDescription;
+ },
+ set description(val) {
+ //Services.console.logStringMessage("set description");
+ this.mDescription = val;
+ },
+
+ editMailListToDatabase: function(listCard) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ copyMailList: function(aSrcList) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ createNewDirectory: function(aDirName, aURI, aType, aPrefName) {
+ //Services.console.logStringMessage("createNewDirectory("+aDirName +", " + aURI+", " + aType+", " + aPrefName + ")");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ createDirectoryByURI: function(displayName, uri) {
+ //Services.console.logStringMessage("createDirectoryByURIy("+displayName+", " + uri +")");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get dirPrefId() {
+ //Services.console.logStringMessage("get dirPref: " + this.mDirPrefId);
+ return this.mDirPrefId;
+ },
+ set dirPrefId(val) {
+ //Services.console.logStringMessage("set dirPref");
+ if (this.mDirPrefId != val) {
+ this.mDirPrefId = val;
+ }
+ },
+
+ getIntValue: function(aName, aDefaultValue) {
+ return 0;
+ },
+
+ getBoolValue: function(aName, aDefaultValue) {
+ return false;
+ },
+
+ getStringValue: function(aName, aDefaultValue) {
+ return "";
+ },
+ getLocalizedStringValue: function(aName, aDefaultValue) {
+ return "";
+ },
+
+ setIntValue: function(aName, aValue) {
+ },
+ setBoolValue: function(aName, aValue) {
+ },
+ setStringValue: function(aName, aValue) {
+ },
+ setLocalizedStringValue: function(aName, aValue) {
+ },
+
+
+ // nsIFactory implementation
+ createInstance: function (outer, iid) {
+ //Services.console.logStringMessage("abDirectory.js: createInstance ("+outer+", " + iid + ")");
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(["nsIFactory", "nsIAbDirectory", "nsIAbCollection"]),
+}
diff --git a/unused/abDirectory2.js b/unused/abDirectory2.js
new file mode 100644
index 0000000..91fd286
--- /dev/null
+++ b/unused/abDirectory2.js
@@ -0,0 +1,339 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var abDirectory = {
+
+ component : null,
+ cid : null,
+
+ /**
+ * Register the component.
+ */
+ register : function () {
+ var name = "@mozilla.org/addressbook/directory;1?type=tbsync-abdirectory";
+ var description = "TbSync AbDirectory";
+ var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].getService(Components.interfaces.nsIUUIDGenerator);
+
+ this.cid = uuidGenerator.generateUUID();
+ this.component = new this.Directory();
+
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.registerFactory(this.cid, description, name, this.component);
+ },
+
+ /**
+ * Unregister the component
+ */
+ unregister : function () {
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.unregisterFactory(this.cid, this.component);
+ this.cid = null;
+ this.component = null;
+ },
+
+ /**
+ * Class Constructor
+ */
+ Directory : function () {
+ Services.console.logStringMessage("CONSTRUCT");
+ this.mValue = "";
+ this.mQuery = "";
+ this.mDirPrefId = "";
+ this.mDescription = "";
+ this.mURI = "";
+ this.mCardCache = {};
+
+ //https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/wrappedJSObject
+ this.wrappedJSObject = this;
+ },
+
+}
+
+
+
+
+
+abDirectory.Directory.prototype = {
+ constructor: abDirectory.Directory,
+
+ wrappedJSObject: null,
+
+ /* nsIAbCollection (parent of nsIAbDirectory) */
+ get readOnly() {
+ return false;
+ },
+
+ get isRemote() {
+ return false;
+ },
+
+ get isSecure() {
+ let url = this.serverURL;
+ return (url && (url.indexOf("https") == 0));
+ },
+
+ cardForEmailAddress: function(emailAddress) {
+ return null;
+ },
+
+ getCardFromProperty: function(aProperty, aValue, aCaseSensitive) {
+ return null;
+ },
+
+ getCardsFromProperty: function(aProperty, aValue, aCaseSensitive) {
+ return null;
+ },
+
+
+ /* nsIAbDirectory */
+ propertiesChromeURI: "chrome://sogo-connector/content/addressbook/preferences.addressbook.groupdav.xul",
+
+ get dirName() {
+ Services.console.logStringMessage("get dirname: " + this.mDescription);
+ return this.mDescription;
+ },
+ set dirName(val) {
+ Services.console.logStringMessage("set dirname: " + val);
+ if (this.mDescription != val) {
+ let oldValue = this.mDescription;
+ this.mDescription = String(val);
+
+ let prefName = this.mDirPrefId;
+ //let service = Components.classes["@mozilla.org/preferences-service;1"]
+ // .getService(Components.interfaces.nsIPrefService);
+ try {
+ let branch = Services.prefs.getBranch(prefName + ".");
+ branch.setCharPref("description", this.mDescription);
+ }
+ catch(e) {
+ dump("directory-properties: exception (new directory '" + prefName
+ + "', URI '" + this.mValue + "' ?):" + e + "\n");
+ }
+
+ MailServices.ab.notifyItemPropertyChanged(this, "DirName", oldValue, val);
+ }
+ },
+
+ get dirType() {
+ return 0;
+ },
+
+ get fileName() {
+ Services.console.logStringMessage("get filename");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get URI() {
+ Services.console.logStringMessage("get uri: " + this.mValue);
+ return this.mValue;
+ },
+
+ get position() {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get lastModifiedDate() {
+ return 0;
+ },
+ set lastModifiedDate(val) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get isMailList() {
+ return false;
+ },
+
+ /* retrieve the sub-directories */
+ get childNodes() {
+ let resultArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIArray);
+ return resultArray.enumerate();
+ },
+
+ get childCards() {
+ let resultArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIArray);
+ return resultArray.enumerate();
+ },
+
+ get isQuery() {
+ return (this.mQuery && this.mQuery.length > 0);
+ },
+
+ init: function(uri) {
+ Services.console.logStringMessage("INIT CALLED for: " + uri);
+ let gABPrefix = "tbsync-abdirectory://";
+ if (uri.indexOf(gABPrefix) == 0) {
+ let prefName = uri.substr(gABPrefix.length);
+ let quMark = uri.indexOf("?");
+ if (quMark > 1) {
+ this.mQuery = uri.substr(quMark);
+ prefName = prefName.substr(0, quMark - gABPrefix.length);
+ }
+ this.mValue = gABPrefix + prefName;
+ this.mDirPrefId = prefName;
+ Services.console.logStringMessage("prefName: " + prefName);
+
+ let branch = Services.prefs.getBranch(prefName + ".");
+ this.mDescription = branch.getCharPref("description","");
+ this.mURI = branch.getCharPref("uri","");
+ }
+ else
+ throw "unknown uri: " + uri;
+ },
+
+ deleteDirectory: function (directory) {
+ ////throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ hasCard: function(cards) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ hasDirectory: function(dir) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ addCard: function(card) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ modifyCard: function(modifiedCard) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ deleteCards: function(cards) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ dropCard: function(card, needToCopyCard) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ useForAutocomplete: function(aIdentityKey) {
+ return false;
+ },
+
+ get supportsMailingLists() {
+ return false;
+ },
+
+ get addressLists() {
+ return this.mAddressLists;
+ },
+ set addressLists(val) {
+ this.mAddressLists = val;
+ },
+
+ addMailList: function(list) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get listNickName() {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get description() {
+ Services.console.logStringMessage("get description: " + this.mDescription);
+ return this.mDescription;
+ },
+ set description(val) {
+ Services.console.logStringMessage("set description");
+ this.mDescription = val;
+ },
+
+ editMailListToDatabase: function(listCard) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ copyMailList: function(aSrcList) {
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ createNewDirectory: function(aDirName, aURI, aType, aPrefName) {
+ Services.console.logStringMessage("createNewDirectory");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ createDirectoryByURI: function(displayName, uri) {
+ Services.console.logStringMessage("createDirectoryByURIy");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get dirPrefId() {
+ Services.console.logStringMessage("get dirPref: " + this.mDirPrefId);
+ return this.mDirPrefId;
+ },
+ set dirPrefId(val) {
+ Services.console.logStringMessage("set dirPref");
+ if (this.mDirPrefId != val) {
+ this.mDirPrefId = val;
+ }
+ },
+
+ getIntValue: function(aName, aDefaultValue) {
+ return 0;
+ },
+
+ getBoolValue: function(aName, aDefaultValue) {
+ return false;
+ },
+
+ getStringValue: function(aName, aDefaultValue) {
+ return "";
+ },
+ getLocalizedStringValue: function(aName, aDefaultValue) {
+ return "";
+ },
+
+ setIntValue: function(aName, aValue) {
+ },
+ setBoolValue: function(aName, aValue) {
+ },
+ setStringValue: function(aName, aValue) {
+ },
+ setLocalizedStringValue: function(aName, aValue) {
+ },
+
+
+ // nsIFactory implementation
+ createInstance: function (outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ /* nsIClassInfo */
+ getInterfaces: function(count) {
+ const ifaces = [
+ Components.interfaces.nsIAbDirectory,
+ Components.interfaces.nsISecurityCheckedComponent,
+ Components.interfaces.nsIClassInfo,
+ Components.interfaces.nsISupports];
+ count.value = ifaces.length;
+ return ifaces;
+ },
+
+ getHelperForLanguage: function(language) {
+ return null;
+ },
+
+
+ /* nsISupports */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIAbDirectory)
+ && !aIID.equals(Components.interfaces.nsIClassInfo)
+ && !aIID.equals(Components.interfaces.nsISupports)) {
+ //Services.console.logStringMessage("CardDAVDirectoryFactory.js: NO INTERFACE: " + aIID + "\n");
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ }
+}
diff --git a/unused/abDirectoryFactory.js b/unused/abDirectoryFactory.js
new file mode 100644
index 0000000..fc0965c
--- /dev/null
+++ b/unused/abDirectoryFactory.js
@@ -0,0 +1,120 @@
+/*
+ * This file is part of TbSync.
+ *
+ * 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/.
+ */
+
+ "use strict";
+
+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var abDirectoryFactory = {
+
+ component : null,
+ cid : null,
+
+ /**
+ * Register the component.
+ */
+ register : function () {
+ var name = "@mozilla.org/addressbook/directory-factory;1?name=tbsync-abdirectory";
+ var description = "TbSync AbDirectory Factory";
+ var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].getService(Components.interfaces.nsIUUIDGenerator);
+
+ this.cid = uuidGenerator.generateUUID();
+ this.component = new this.Factory();
+
+ Services.console.logStringMessage("abDirectoryFactory.js: register"
+ + "\n id: " + this.cid
+ + "\n description: " + description
+ + "\n name: " + name + "\n");
+
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.registerFactory(this.cid, description, name, this.component);
+ },
+
+ /**
+ * Unregister the component
+ */
+ unregister : function () {
+ Services.console.logStringMessage("abDirectoryFactory.js: unregister");
+ var componentManager = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+ componentManager.unregisterFactory(this.cid, this.component);
+ this.cid = null;
+ this.component = null;
+ },
+
+ /**
+ * Class Constructor
+ */
+ Factory : function () {
+ Services.console.logStringMessage("abDirectoryFactory.js: constructor");
+ },
+
+}
+
+
+
+
+
+abDirectoryFactory.Factory.prototype = {
+ constructor: abDirectoryFactory.Factory,
+ flags: 0,
+
+ // nsIFactory implementation
+ createInstance: function (outer, iid) {
+ Services.console.logStringMessage("abDirectoryFactory.js: createInstance ("+outer+", " + iid + ")");
+ return this.QueryInterface(iid);
+ },
+
+ /* nsIAbDirFactory */
+ getDirectories: function(aDirName, aURI, aPrefId) {
+ Services.console.logStringMessage("DirectoryFactory.js: getDirectories"
+ + "\n aDirName: " + aDirName
+ + "\n aURI: " + aURI
+ + "\n aPrefId: " + aPrefId + "\n");
+
+ let baseArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+
+ try {
+ let directoryURI = "tbsync-abdirectory://" + aPrefId;
+ Services.console.logStringMessage("Getting directory at URI: " + directoryURI + "\n");
+ let directory = MailServices.ab.getDirectory(directoryURI);
+ baseArray.appendElement(directory, false);
+ } catch (e) {
+ Services.console.logStringMessage("Error in getDirectories(): " + e + "\n");
+ }
+
+ let directoryEnum = baseArray.enumerate();
+ return directoryEnum;
+ },
+
+ //void deleteDirectory ( nsIAbDirectory directory )
+ deleteDirectory: function(directory) {
+ Services.console.logStringMessage("DirectoryFactory.js: deleteDirectory: directory: " + directory + "\n");
+ //throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ getInterfaces: function(count) {
+ const ifaces = [
+ Components.interfaces.nsIAbDirFactory,
+ Components.interfaces.nsIClassInfo,
+ Components.interfaces.nsISupports];
+ count.value = ifaces.length;
+ return ifaces;
+ },
+
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIAbDirFactory)
+ && !aIID.equals(Components.interfaces.nsIClassInfo)
+ && !aIID.equals(Components.interfaces.nsISupports)) {
+ //Services.console.logStringMessage("CardDAVDirectoryFactory.js: NO INTERFACE: " + aIID + "\n");
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ }
+}