<commit_before>const {
  IMAPConnection,
  IMAPErrors,
  PromiseUtils,
} = require('isomorphic-core');
const LocalDatabaseConnector = require('../shared/local-database-connector')
const {
  jsonError,
} = require('./sync-utils')

const FetchFolderList = require('./imap/fetch-folder-list')
const FetchMessagesInFolder = require('./imap/fetch-messages-in-folder')
const SyncbackTaskFactory = require('./syncback-task-factory')


class SyncWorker {
  constructor(account, db, onExpired) {
    this._db = db;
    this._conn = null;
    this._account = account;
    this._startTime = Date.now();
    this._lastSyncTime = null;
    this._onExpired = onExpired;
    this._logger = global.Logger.forAccount(account)

    this._syncTimer = null;
    this._destroyed = false;

    this.syncNow({reason: 'Initial'});
  }

  cleanup() {
    clearTimeout(this._syncTimer);
    this._syncTimer = null;
    this._destroyed = true;
    this.closeConnection()
  }

  closeConnection() {
    if (this._conn) {
      this._conn.end();
    }
  }

  _onAccountUpdated() {
    const syncingNow = !this.isWaitingForNextSync()
    const syncingJustFinished = (Date.now() - this._lastSyncTime < 5000);

    if (syncingNow || syncingJustFinished) {
      return;
    }

    this._getAccount().then((account) => {
      this._account = account;
      this.syncNow({reason: 'Account Modification'});
    })
    .catch((err) => {
      this._logger.error(err, 'SyncWorker: Error getting account for update')
    })
  }

  _onConnectionIdleUpdate() {
    if (!this.isWaitingForNextSync()) {
      return;
    }
    this.syncNow({reason: 'IMAP IDLE Fired'});
  }

  _getAccount() {
    return LocalDatabaseConnector.forShared().then(({Account}) =>
      Account.find({where: {id: this._account.id}})
    );
  }

  _getIdleFolder() {
    return this._db.Folder.find({where: {role: ['all', 'inbox']}})
  }

  ensureConnection() {
    if (this._conn) {
      return this._conn.connect();
    }
    const settings = this._account.connectionSettings;
    const credentials = this._account.decryptedCredentials();

    if (!settings || !settings.imap_host) {
      return Promise.reject(new Error("ensureConnection: There are no IMAP connection settings for this account."))
    }
    if (!credentials) {
      return Promise.reject(new Error("ensureConnection: There are no IMAP connection credentials for this account."))
    }

    const conn = new IMAPConnection({
      db: this._db,
      settings: Object.assign({}, settings, credentials),
      logger: this._logger,
    });

    conn.on('mail', () => {
      this._onConnectionIdleUpdate();
    })
    conn.on('update', () => {
      this._onConnectionIdleUpdate();
    })
    conn.on('queue-empty', () => {
    });

    this._conn = conn;
    return this._conn.connect();
  }

  syncbackMessageActions() {
    const where = {where: {status: "NEW"}, limit: 100};
    return PromiseUtils.each((this._db.SyncbackRequest.findAll(where)
          .map((req) => SyncbackTaskFactory.create(this._account, req))),
          this.runSyncbackTask.bind(this))
  }

  runSyncbackTask(task) {
    const syncbackRequest = task.syncbackRequestObject()
    return this._conn.runOperation(task)
    .then(() => {
      syncbackRequest.status = "SUCCEEDED"
    })
    .catch((error) => {
      syncbackRequest.error = error
      syncbackRequest.status = "FAILED"
      this._logger.error(syncbackRequest.toJSON(), `${task.description()} failed`)
    })
    .finally(() => syncbackRequest.save())
  }

  syncAllCategories() {
    const {Folder} = this._db;
    const {folderSyncOptions} = this._account.syncPolicy;

    return Folder.findAll().then((categories) => {
      const priority = ['inbox', 'all', 'drafts', 'sent', 'spam', 'trash'].reverse();
      const categoriesToSync = categories.sort((a, b) =>
        (priority.indexOf(a.role) - priority.indexOf(b.role)) * -1
      )

      return Promise.all(categoriesToSync.map((cat) =>
        this._conn.runOperation(new FetchMessagesInFolder(cat, folderSyncOptions, this._logger))
      ))
    });
  }

  syncNow({reason} = {}) {
    clearTimeout(this._syncTimer);
    this._syncTimer = null;

    this._account.reload().then(() => {
      console.log(this._account)
      if (!process.env.SYNC_AFTER_ERRORS && this._account.errored()) {
        this._logger.error(`SyncWorker: Account is in error state - Skipping sync\n${this._account.syncError.message}`, this._account.syncError.stack.join('\n'))
        return Promise.resolve();
      }
      this._logger.info({reason}, `SyncWorker: Account sync started`)

      return this._account.update({syncError: null})
      .then(() => this.ensureConnection())
      .then(() => this.syncbackMessageActions())
      .then(() => this._conn.runOperation(new FetchFolderList(this._account.provider, this._logger)))
      .then(() => this.syncAllCategories())
      .then(() => this.onSyncDidComplete())
      .catch((error) => this.onSyncError(error))<commit_msg>[local-sync] retry sync after error<commit_after>const {
  IMAPConnection,
  IMAPErrors,
  PromiseUtils,
} = require('isomorphic-core');
const LocalDatabaseConnector = require('../shared/local-database-connector')
const {
  jsonError,
} = require('./sync-utils')

const FetchFolderList = require('./imap/fetch-folder-list')
const FetchMessagesInFolder = require('./imap/fetch-messages-in-folder')
const SyncbackTaskFactory = require('./syncback-task-factory')


class SyncWorker {
  constructor(account, db, onExpired) {
    this._db = db;
    this._conn = null;
    this._account = account;
    this._startTime = Date.now();
    this._lastSyncTime = null;
    this._onExpired = onExpired;
    this._logger = global.Logger.forAccount(account)

    this._syncTimer = null;
    this._destroyed = false;

    this.syncNow({reason: 'Initial'});
  }

  cleanup() {
    clearTimeout(this._syncTimer);
    this._syncTimer = null;
    this._destroyed = true;
    this.closeConnection()
  }

  closeConnection() {
    if (this._conn) {
      this._conn.end();
    }
  }

  _onAccountUpdated() {
    const syncingNow = !this.isWaitingForNextSync()
    const syncingJustFinished = (Date.now() - this._lastSyncTime < 5000);

    if (syncingNow || syncingJustFinished) {
      return;
    }

    this._getAccount().then((account) => {
      this._account = account;
      this.syncNow({reason: 'Account Modification'});
    })
    .catch((err) => {
      this._logger.error(err, 'SyncWorker: Error getting account for update')
    })
  }

  _onConnectionIdleUpdate() {
    if (!this.isWaitingForNextSync()) {
      return;
    }
    this.syncNow({reason: 'IMAP IDLE Fired'});
  }

  _getAccount() {
    return LocalDatabaseConnector.forShared().then(({Account}) =>
      Account.find({where: {id: this._account.id}})
    );
  }

  _getIdleFolder() {
    return this._db.Folder.find({where: {role: ['all', 'inbox']}})
  }

  ensureConnection() {
    if (this._conn) {
      return this._conn.connect();
    }
    const settings = this._account.connectionSettings;
    const credentials = this._account.decryptedCredentials();

    if (!settings || !settings.imap_host) {
      return Promise.reject(new Error("ensureConnection: There are no IMAP connection settings for this account."))
    }
    if (!credentials) {
      return Promise.reject(new Error("ensureConnection: There are no IMAP connection credentials for this account."))
    }

    const conn = new IMAPConnection({
      db: this._db,
      settings: Object.assign({}, settings, credentials),
      logger: this._logger,
    });

    conn.on('mail', () => {
      this._onConnectionIdleUpdate();
    })
    conn.on('update', () => {
      this._onConnectionIdleUpdate();
    })
    conn.on('queue-empty', () => {
    });

    this._conn = conn;
    return this._conn.connect();
  }

  syncbackMessageActions() {
    const where = {where: {status: "NEW"}, limit: 100};
    return PromiseUtils.each((this._db.SyncbackRequest.findAll(where)
          .map((req) => SyncbackTaskFactory.create(this._account, req))),
          this.runSyncbackTask.bind(this))
  }

  runSyncbackTask(task) {
    const syncbackRequest = task.syncbackRequestObject()
    return this._conn.runOperation(task)
    .then(() => {
      syncbackRequest.status = "SUCCEEDED"
    })
    .catch((error) => {
      syncbackRequest.error = error
      syncbackRequest.status = "FAILED"
      this._logger.error(syncbackRequest.toJSON(), `${task.description()} failed`)
    })
    .finally(() => syncbackRequest.save())
  }

  syncAllCategories() {
    const {Folder} = this._db;
    const {folderSyncOptions} = this._account.syncPolicy;

    return Folder.findAll().then((categories) => {
      const priority = ['inbox', 'all', 'drafts', 'sent', 'spam', 'trash'].reverse();
      const categoriesToSync = categories.sort((a, b) =>
        (priority.indexOf(a.role) - priority.indexOf(b.role)) * -1
      )

      return Promise.all(categoriesToSync.map((cat) =>
        this._conn.runOperation(new FetchMessagesInFolder(cat, folderSyncOptions, this._logger))
      ))
    });
  }

  syncNow({reason} = {}) {
    clearTimeout(this._syncTimer);
    this._syncTimer = null;

    this._account.reload().then(() => {
      console.log(this._account)
      if (this._account.errored()) {
        this._logger.error(`SyncWorker: Account is in error state - Retrying sync\n${this._account.syncError.message}`, this._account.syncError.stack.join('\n'))
      }
      this._logger.info({reason}, `SyncWorker: Account sync started`)

      return this._account.update({syncError: null})
      .then(() => this.ensureConnection())
      .then(() => this.syncbackMessageActions())
      .then(() => this._conn.runOperation(new FetchFolderList(this._account.provider, this._logger)))
      .then(() => this.syncAllCategories())
      .then(() => this.onSyncDidComplete())
      .catch((error) => this.onSyncError(error))
{'fraction_non_alphanumeric': 0.15602263540824576, 'fraction_numerical': 0.0016168148746968471, 'mean_word_length': 3.2421774539219888, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3105247', 'n_tokens_mistral': 3068, 'n_tokens_neox': 3018, 'n_words': 538}