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

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

import type {VolumeManager} from '../../background/js/volume_manager.js';
import {isFakeEntry} from '../../common/js/entry_utils.js';
import type {FilesAppEntry} from '../../common/js/files_app_entry_types.js';
import {getEntryLabel, getRootTypeLabel, str} from '../../common/js/translations.js';
import {COMPUTERS_DIRECTORY_PATH, RootType, SHARED_DRIVES_DIRECTORY_PATH} from '../../common/js/volume_manager_types.js';

/**
 * File path component.
 *
 * File path can be represented as a series of path components. Each component
 * has its name used as a visible label and URL which point to the component
 * in the path.
 * PathComponent.computeComponentsFromEntry computes an array of PathComponent
 * of the given entry.
 */
export class PathComponent {
  /**
   * @param name Name.
   * @param url Url.
   * @param fakeEntry Fake entry should be set when this component represents
   *     fake entry.
   */
  constructor(
      public readonly name: string, private readonly url_: string,
      private fakeEntry_?: FilesAppEntry) {}

  /**
   * Resolve an entry of the component.
   * @return A promise which is resolved with an entry.
   */
  resolveEntry(): Promise<Entry|FilesAppEntry> {
    if (this.fakeEntry_) {
      return Promise.resolve(this.fakeEntry_);
    } else {
      return new Promise(
          window.webkitResolveLocalFileSystemURL.bind(null, this.url_));
    }
  }

  /**
   * Returns the key of this component (its URL).
   */
  getKey() {
    return this.url_;
  }

  /**
   * Computes path components for the path of entry.
   * @param entry An entry.
   * @return Components.
   */
  static computeComponentsFromEntry(
      entry: Entry|FilesAppEntry,
      volumeManager: VolumeManager): PathComponent[] {
    /**
     * Replace the root directory name at the end of a url.
     * The input, |url| is a displayRoot URL of a Drive volume like
     * filesystem:chrome-extension://....foo.com-hash/root
     * The output is like:
     * filesystem:chrome-extension://....foo.com-hash/other
     *
     * @param url which points to a volume display root
     * @param newRoot new root directory name
     * @return new URL with the new root directory name
     */
    const replaceRootName = (url: string, newRoot: string): string => {
      return url.slice(0, url.length - '/root'.length) + newRoot;
    };

    const components: PathComponent[] = [];
    const locationInfo = volumeManager.getLocationInfo(entry);

    if (!locationInfo) {
      return components;
    }

    if (isFakeEntry(entry)) {
      components.push(new PathComponent(
          getEntryLabel(locationInfo, entry), entry.toURL(), entry));
      return components;
    }

    // Add volume component.
    const volumeInfo = locationInfo.volumeInfo;
    if (!volumeInfo) {
      return components;
    }

    let displayRootUrl = volumeInfo.displayRoot.toURL();
    let displayRootFullPath = volumeInfo.displayRoot.fullPath;

    const prefixEntry = volumeInfo.prefixEntry;
    // Directories under Drive Fake Root can return the fake root entry list as
    // prefix entry, but we will never show "Google Drive" as the prefix in the
    // breadcrumb.
    if (prefixEntry && prefixEntry.rootType !== RootType.DRIVE_FAKE_ROOT) {
      components.push(new PathComponent(
          prefixEntry.name, prefixEntry.toURL(), prefixEntry));
    }
    if (locationInfo.rootType === RootType.DRIVE_SHARED_WITH_ME) {
      // DriveFS shared items are in either of:
      // <drivefs>/.files-by-id/<id>/<item>
      // <drivefs>/.shortcut-targets-by-id/<id>/<item>
      const match =
          entry.fullPath.match(/^\/\.(files|shortcut-targets)-by-id\/.+?\//);
      if (match) {
        displayRootFullPath = match[0];
      } else {
        console.warn('Unexpected shared DriveFS path: ', entry.fullPath);
      }
      displayRootUrl = replaceRootName(displayRootUrl, displayRootFullPath);
      const sharedWithMeFakeEntry =
          volumeInfo.fakeEntries[RootType.DRIVE_SHARED_WITH_ME];
      if (sharedWithMeFakeEntry) {
        components.push(new PathComponent(
            str('DRIVE_SHARED_WITH_ME_COLLECTION_LABEL'),
            sharedWithMeFakeEntry.toURL(), sharedWithMeFakeEntry));
      }
    } else if (locationInfo.rootType === RootType.SHARED_DRIVE) {
      displayRootUrl =
          replaceRootName(displayRootUrl, SHARED_DRIVES_DIRECTORY_PATH);
      components.push(
          new PathComponent(getRootTypeLabel(locationInfo), displayRootUrl));
    } else if (locationInfo.rootType === RootType.COMPUTER) {
      displayRootUrl =
          replaceRootName(displayRootUrl, COMPUTERS_DIRECTORY_PATH);
      components.push(
          new PathComponent(getRootTypeLabel(locationInfo), displayRootUrl));
    } else {
      components.push(
          new PathComponent(getRootTypeLabel(locationInfo), displayRootUrl));
    }

    // Get relative path to display root (e.g. /root/foo/bar -> foo/bar).
    let relativePath = entry.fullPath.slice(displayRootFullPath.length);
    if (entry.fullPath.startsWith(SHARED_DRIVES_DIRECTORY_PATH)) {
      relativePath = entry.fullPath.slice(SHARED_DRIVES_DIRECTORY_PATH.length);
    } else if (entry.fullPath.startsWith(COMPUTERS_DIRECTORY_PATH)) {
      relativePath = entry.fullPath.slice(COMPUTERS_DIRECTORY_PATH.length);
    }
    if (relativePath.indexOf('/') === 0) {
      relativePath = relativePath.slice(1);
    }
    if (relativePath.length === 0) {
      return components;
    }

    // currentUrl should be without trailing slash.
    let currentUrl = /^.+\/$/.test(displayRootUrl) ?
        displayRootUrl.slice(0, displayRootUrl.length - 1) :
        displayRootUrl;

    // Add directory components to the target path.
    const paths = relativePath.split('/');
    for (let i = 0; i < paths.length; i++) {
      currentUrl += '/' + encodeURIComponent(paths[i]!);
      let path = paths[i]!;
      if (i === 0 && locationInfo.rootType === RootType.DOWNLOADS) {
        if (path === 'Downloads') {
          path = str('DOWNLOADS_DIRECTORY_LABEL');
        }
        if (path === 'PvmDefault') {
          path = str('PLUGIN_VM_DIRECTORY_LABEL');
        }
        if (path === 'Camera') {
          path = str('CAMERA_DIRECTORY_LABEL');
        }
      }
      components.push(new PathComponent(path, currentUrl));
    }

    return components;
  }
}