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

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

import {isFakeEntry, unwrapEntry} from '../../common/js/entry_utils.js';
import type {FilesAppEntry} from '../../common/js/files_app_entry_types.js';
import {type EntryMetadata, updateMetadata} from '../../state/ducks/all_entries.js';
import {getStore} from '../../state/store.js';

import type {DirectoryModel} from './directory_model.js';
import type {MetadataSetEvent} from './metadata/metadata_cache_set.js';
import type {MetadataModel} from './metadata/metadata_model.js';
import type {FileMetadataFormatter} from './ui/file_metadata_formatter.js';
import type {ListContainer} from './ui/list_container.js';


/** Number of milliseconds in a day. */
const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;

/** Controller for list contents update. */
export class MetadataUpdateController {
  private readonly store_ = getStore();

  constructor(
      private readonly listContainer_: ListContainer,
      private readonly directoryModel_: DirectoryModel,
      private readonly metadataModel_: MetadataModel,
      private readonly fileMetadataFormatter_: FileMetadataFormatter) {
    chrome.fileManagerPrivate.onPreferencesChanged.addListener(
        this.onPreferencesChanged_.bind(this));
    this.onPreferencesChanged_();
    this.metadataModel_.addEventListener(
        'update', this.onCachedMetadataUpdate_.bind(this));

    // Update metadata to change 'Today' and 'Yesterday' dates.
    const today = new Date();
    today.setHours(0);
    today.setMinutes(0);
    today.setSeconds(0);
    today.setMilliseconds(0);
    setTimeout(
        this.dailyUpdateModificationTime_.bind(this),
        today.getTime() + MILLISECONDS_IN_DAY - Date.now() + 1000);
  }

  /** Clears metadata cache for the current directory and its descendants. */
  refreshCurrentDirectoryMetadata() {
    const entries = this.directoryModel_.getFileList().slice();
    const directoryEntry = this.directoryModel_.getCurrentDirEntry();
    if (!directoryEntry) {
      return;
    }

    const changedEntries =
        (isFakeEntry(directoryEntry) ? [] : [
          unwrapEntry(directoryEntry) as (Entry | FilesAppEntry),
        ]).concat(entries);
    this.metadataModel_.notifyEntriesChanged(changedEntries);

    // We don't pass callback here. When new metadata arrives, we have an
    // observer registered to update the UI.
    this.metadataModel_.get(
        changedEntries, this.directoryModel_.getPrefetchPropertyNames());
  }

  /**
   * Handles local metadata changes in the current directory.
   * @param event Change event.
   */
  private onCachedMetadataUpdate_(event: MetadataSetEvent) {
    this.updateStore_(event.entries);
    this.listContainer_.dataModel?.refreshGroupBySnapshot();
    // TODO(hirono): Specify property name instead of metadata type.
    this.listContainer_.currentView.updateListItemsMetadata(
        'filesystem', event.entries);
    this.listContainer_.currentView.updateListItemsMetadata(
        'external', event.entries);
  }

  private dailyUpdateModificationTime_() {
    const entries = this.directoryModel_.getFileList().slice() as Entry[];
    this.metadataModel_.get(entries, ['modificationTime']).then(() => {
      this.listContainer_.currentView.updateListItemsMetadata(
          'filesystem', entries);
    });
    setTimeout(
        this.dailyUpdateModificationTime_.bind(this), MILLISECONDS_IN_DAY);
  }

  private onPreferencesChanged_() {
    chrome.fileManagerPrivate.getPreferences(prefs => {
      const use12hourClock = !prefs.use24hourClock;
      this.fileMetadataFormatter_.setDateTimeFormat(use12hourClock);
      // TODO(oka): Remove these two lines, and add fileMetadataFormatter to
      // constructor for each field instead.
      this.listContainer_.table.setDateTimeFormat(use12hourClock);
      this.refreshCurrentDirectoryMetadata();
    });
  }

  /** Sends the new metadata to the Store. */
  private updateStore_(entries: Array<Entry|FilesAppEntry>) {
    const metadata: EntryMetadata[] = entries.map(
        (e: Entry|FilesAppEntry): EntryMetadata => ({
          entry: e,
          metadata: this.metadataModel_.getCache(
              [e], this.directoryModel_.getPrefetchPropertyNames())[0]!,
        }));

    this.store_.dispatch(updateMetadata({metadata}));
  }
}