chromium/ui/file_manager/file_manager/foreground/js/task_history.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {dispatchSimpleEvent} from 'chrome://resources/ash/common/cr_deprecated.js';
import {NativeEventTarget as EventTarget} from 'chrome://resources/ash/common/event_target.js';

import {type ChangedValues, storage} from '../../common/js/storage.js';
import {makeTaskID} from '../../common/js/util.js';

export enum EventType {
  UPDATE = 'update',
}

/** Key used to store the task history in local storage. */
const STORAGE_KEY_LAST_EXECUTED_TIME = 'task-last-executed-time';
const LAST_EXECUTED_TIME_HISTORY_MAX = 100;

/**
 * TaskHistory object keeps track of the history of task executions. Recent
 * history is stored in local storage.
 */
export class TaskHistory extends EventTarget {
  /**
   * The recent history of task executions. Key is task ID and value is time
   * stamp of the latest execution of the task.
   */
  private lastExecutedTime_: Record<string, number> = {};

  constructor() {
    super();

    storage.onChanged.addListener(this.onLocalStorageChanged_.bind(this));
    this.load_();
  }

  /** Records the timing of task execution. */
  recordTaskExecuted(descriptor: chrome.fileManagerPrivate.FileTaskDescriptor) {
    const taskId = makeTaskID(descriptor);
    this.lastExecutedTime_[taskId] = Date.now();
    this.truncate_();
    this.save_();
  }

  /**
   * Gets the time stamp of last execution of given task. If the record is not
   * found, returns 0.
   */
  getLastExecutedTime(descriptor: chrome.fileManagerPrivate.FileTaskDescriptor):
      number {
    const taskId = makeTaskID(descriptor);
    return this.lastExecutedTime_[taskId] ?? 0;
  }

  /** Loads the current history from local storage. */
  private load_() {
    storage.local.get(
        STORAGE_KEY_LAST_EXECUTED_TIME, (value: Record<string, any>) => {
          this.lastExecutedTime_ = value[STORAGE_KEY_LAST_EXECUTED_TIME] ?? {};
        });
  }

  /** Saves the current history to local storage. */
  private save_() {
    storage.local.set(
        {[STORAGE_KEY_LAST_EXECUTED_TIME]: this.lastExecutedTime_});
  }

  /** Handles local storage change event to update the current history. */
  private onLocalStorageChanged_(changes: ChangedValues, areaName: string) {
    if (areaName !== 'local') {
      return;
    }

    for (const key in changes) {
      if (key === STORAGE_KEY_LAST_EXECUTED_TIME) {
        this.lastExecutedTime_ = changes[key]?.newValue;
        dispatchSimpleEvent(this, EventType.UPDATE);
      }
    }
  }

  /**
   * Truncates current history so that the size of history does not exceed
   * STORAGE_KEY_LAST_EXECUTED_TIME.
   */
  private truncate_() {
    const keys = Object.keys(this.lastExecutedTime_);
    if (keys.length <= LAST_EXECUTED_TIME_HISTORY_MAX) {
      return;
    }

    interface Item {
      id: string;
      timestamp: number;
    }

    let items: Item[] = [];
    for (const key of keys) {
      items.push({id: key, timestamp: this.lastExecutedTime_[key]!});
    }

    items.sort((a, b) => b.timestamp - a.timestamp);
    items = items.slice(0, LAST_EXECUTED_TIME_HISTORY_MAX);

    const newObject: Record<string, number> = {};
    for (const item of items) {
      newObject[item.id] = item.timestamp;
    }

    this.lastExecutedTime_ = newObject;
  }
}