/* 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 https://mozilla.org/MPL/2.0/. */

import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs";
import { MeasurementUtils } from "resource:///modules/backup/MeasurementUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs",
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
});

const BOOKMARKS_BACKUP_FILENAME = "bookmarks.jsonlz4";

/**
 * Class representing Places database related files within a user profile.
 */
export class PlacesBackupResource extends BackupResource {
  static get key() {
    return "places";
  }

  static get requiresEncryption() {
    return false;
  }

  static get priority() {
    return 1;
  }

  async backup(
    stagingPath,
    profilePath = PathUtils.profileDir,
    _isEncrypting = false
  ) {
    /**
     * Do not backup places.sqlite and favicons.sqlite if users have history disabled, want history cleared on shutdown or are using permanent private browsing mode.
     * Instead, export all existing bookmarks to a compressed JSON file that we can read when restoring the backup.
     */
    if (!BackupResource.canBackupHistory()) {
      let bookmarksBackupFile = PathUtils.join(
        stagingPath,
        BOOKMARKS_BACKUP_FILENAME
      );
      await lazy.BookmarkJSONUtils.exportToFile(bookmarksBackupFile, {
        compress: true,
      });
      return { bookmarksOnly: true };
    }

    // These are copied in parallel because they're attached[1], and we don't
    // want them to get out of sync with one another.
    //
    // [1]: https://www.sqlite.org/lang_attach.html
    let timedCopies = [
      MeasurementUtils.measure(
        Glean.browserBackup.placesTime,
        BackupResource.copySqliteDatabases(profilePath, stagingPath, [
          "places.sqlite",
        ])
      ),
      MeasurementUtils.measure(
        Glean.browserBackup.faviconsTime,
        BackupResource.copySqliteDatabases(profilePath, stagingPath, [
          "favicons.sqlite",
        ])
      ),
    ];
    await Promise.all(timedCopies);

    // Now that both databases are copied, open the places db copy to remove
    // downloaded files, since they won't be valid in the restored profile.
    await lazy.PlacesDBUtils.removeDownloadsMetadataFromDb(
      PathUtils.join(stagingPath, "places.sqlite")
    );

    return null;
  }

  async recover(manifestEntry, recoveryPath, destProfilePath) {
    if (!manifestEntry) {
      const simpleCopyFiles = ["places.sqlite", "favicons.sqlite"];
      await BackupResource.copyFiles(
        recoveryPath,
        destProfilePath,
        simpleCopyFiles
      );
    } else {
      const { bookmarksOnly } = manifestEntry;

      /**
       * If the recovery file only has bookmarks backed up, pass the file path to postRecovery()
       * so that we can import all bookmarks into the new profile once it's been launched and restored.
       */
      if (bookmarksOnly) {
        let bookmarksBackupPath = PathUtils.join(
          recoveryPath,
          BOOKMARKS_BACKUP_FILENAME
        );
        return { bookmarksBackupPath };
      }
    }

    return null;
  }

  async postRecovery(postRecoveryEntry) {
    if (postRecoveryEntry?.bookmarksBackupPath) {
      await lazy.BookmarkJSONUtils.importFromFile(
        postRecoveryEntry.bookmarksBackupPath,
        {
          replace: true,
        }
      );
    }
  }

  async measure(profilePath = PathUtils.profileDir) {
    let placesDBPath = PathUtils.join(profilePath, "places.sqlite");
    let faviconsDBPath = PathUtils.join(profilePath, "favicons.sqlite");
    let placesDBSize = await BackupResource.getFileSize(placesDBPath);
    let faviconsDBSize = await BackupResource.getFileSize(faviconsDBPath);

    Glean.browserBackup.placesSize.set(placesDBSize);
    Glean.browserBackup.faviconsSize.set(faviconsDBSize);
  }
}
