summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/export/content/exportDialog.js
blob: 4887526865fb373c20ea8dc4010f82b263175833 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* 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 { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

var nsFile = Components.Constructor(
  "@mozilla.org/file/local;1",
  "nsIFile",
  "initWithPath"
);

// No need to backup those paths, they are not used when importing.
const IGNORE_PATHS = [
  "cache2",
  "chrome_debugger_profile",
  "crashes",
  "datareporting",
  "extensions",
  "extension-store",
  "logs",
  "lock",
  "minidumps",
  "parent.lock",
  "shader-cache",
  "saved-telemetry-pings",
  "security_state",
  "storage",
  "xulstore",
];

var zipW;

var logger = console.createInstance({
  prefix: "mail.export",
  maxLogLevel: "Warn",
  maxLogLevelPref: "mail.export.loglevel",
});

document.addEventListener("dialogaccept", async event => {
  if (zipW) {
    // This will close the dialog.
    return;
  }

  // Do not close the dialog, but open a FilePicker to set the output location.
  event.preventDefault();

  let [filePickerTitle, brandName] = await document.l10n.formatValues([
    "export-dialog-file-picker",
    "export-dialog-brand-name",
  ]);
  let filePicker = Components.Constructor(
    "@mozilla.org/filepicker;1",
    "nsIFilePicker"
  )();
  filePicker.init(window, filePickerTitle, Ci.nsIFilePicker.modeSave);
  filePicker.defaultString = `${brandName}_profile_backup.zip`;
  filePicker.defaultExtension = "zip";
  filePicker.appendFilter("", "*.zip");
  filePicker.open(rv => {
    if (
      [Ci.nsIFilePicker.returnOK, Ci.nsIFilePicker.returnReplace].includes(
        rv
      ) &&
      filePicker.file
    ) {
      exportCurrentProfile(filePicker.file);
    } else {
      window.close();
    }
  });
});

/**
 * Export the current profile to the specified target zip file.
 *
 * @param {nsIFile} targetFile - A target zip file to write to.
 */
async function exportCurrentProfile(targetFile) {
  let [progressExporting, progressExported, buttonLabelFinish] =
    await document.l10n.formatValues([
      "export-dialog-exporting",
      "export-dialog-exported",
      "export-dialog-button-finish",
    ]);
  document.getElementById("progressBar").hidden = false;
  let progressStatus = document.getElementById("progressStatus");
  progressStatus.value = progressExporting;
  let buttonAccept = document.querySelector("dialog").getButton("accept");
  buttonAccept.disabled = true;
  document.querySelector("dialog").getButton("cancel").hidden = true;

  zipW = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter")();
  // MODE_WRONLY (0x02) and MODE_CREATE (0x08)
  zipW.open(targetFile, 0x02 | 0x08);
  let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
  let rootPath = profileDir.parent.path;
  let zipEntryMap = new Map();
  await collectFilesToZip(zipEntryMap, rootPath, profileDir);

  let progressElement = document.getElementById("progress");
  progressElement.max = zipEntryMap.size;
  let i = 0;
  for (let [path, file] of zipEntryMap) {
    logger.debug("Adding entry file:", path);
    zipW.addEntryFile(
      path,
      0, // no compression, bigger file but much faster
      file,
      false
    );
    if (++i % 10 === 0) {
      progressElement.value = i;
      await new Promise(resolve => setTimeout(resolve));
    }
  }
  progressElement.value = progressElement.max;
  zipW.close();

  progressStatus.value = progressExported;
  buttonAccept.disabled = false;
  buttonAccept.label = buttonLabelFinish;
}

/**
 * Recursively collect files to be zipped, save the entries into zipEntryMap.
 *
 * @param {Map<string, nsIFile>} zipEntryMap - Collection of files to be zipped.
 * @param {string} rootPath - The rootPath to zip from.
 * @param {nsIFile} folder - The folder to search for files to zip.
 */
async function collectFilesToZip(zipEntryMap, rootPath, folder) {
  let entries = await IOUtils.getChildren(folder.path);
  let separator = Services.appinfo.OS == "WINNT" ? "\\" : "/";
  for (let entry of entries) {
    let file = nsFile(entry);
    if (file.isDirectory()) {
      await collectFilesToZip(zipEntryMap, rootPath, file);
    } else {
      // We don't want to include the rootPath part in the zip file.
      let path = entry.slice(rootPath.length + 1);
      // path now looks like this: profile-default/lock.
      let parts = path.split(separator);
      if (IGNORE_PATHS.includes(parts[1])) {
        continue;
      }
      // Path separator inside a zip file is always "/".
      zipEntryMap.set(parts.join("/"), file);
    }
  }
}