chromium/ui/file_manager/file_manager/common/js/file_type.ts

// Copyright 2012 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 {FilesAppEntry} from '../../common/js/files_app_entry_types.js';
import type {FileData} from '../../state/state.js';

import {getFileTypeForName, getFinalExtension} from './file_types_base.js';
import {FileExtensionType, MIME_TO_TYPE} from './file_types_data.js';
import type {VolumeEntry} from './files_app_entry_types.js';
import {RootType, VolumeType} from './volume_manager_types.js';

export {FileExtensionType};

// All supported file types are now defined in
// ui/file_manager/base/gn/file_types.json5.

/** A special type for directory. */
const DIRECTORY = {
  translationKey: 'FOLDER',
  type: '.folder',
  icon: 'folder',
  subtype: '',
  extensions: undefined,
  mime: undefined,
  encrypted: undefined,
  originalMimeType: undefined,
};

/**
 * Returns the file path extension for a given file.
 *
 * @param entry Reference to the file.
 * @return The extension including a leading '.', or empty string if not found.
 */
export function getExtension(entry: Entry|FilesAppEntry): string {
  // No extension for a directory.
  if (entry.isDirectory) {
    return '';
  }

  return getFinalExtension(entry.name);
}

/**
 * Gets the file type object for a given entry. If mime type is provided, then
 * uses it with higher priority than the extension.
 *
 * @param entry Reference to the entry.
 * @param mimeType Optional mime type for the entry.
 * @return The matching descriptor or a placeholder.
 */
export function getType(
    entry: Entry|FilesAppEntry, mimeType?: string): FileExtensionType {
  if (entry.isDirectory) {
    const volumeInfo = (entry as VolumeEntry).volumeInfo;
    // For removable partitions, use the file system type.
    if (volumeInfo && volumeInfo.diskFileSystemType) {
      return {
        translationKey: '',
        type: 'partition',
        subtype: volumeInfo.diskFileSystemType,
        icon: '',
        extensions: undefined,
        mime: undefined,
        encrypted: undefined,
        originalMimeType: undefined,
      };
    }

    return DIRECTORY;
  }

  if (mimeType) {
    const cseMatch = mimeType.match(
        /^application\/vnd.google-gsuite.encrypted; content="([a-z\/.-]+)"$/);
    if (cseMatch) {
      const type = {...getType(entry, cseMatch[1])};
      type.encrypted = true;
      type.originalMimeType = cseMatch[1];
      return type;
    }
  }

  if (mimeType && MIME_TO_TYPE.has(mimeType)) {
    return MIME_TO_TYPE.get(mimeType) as FileExtensionType;
  }

  return getFileTypeForName(entry.name);
}

/**
 * Gets the media type for a given file.
 *
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return The value of 'type' property from one of the elements in the knows
 *     file types (file_types.json5) or undefined.
 */
export function getMediaType(
    entry: Entry|FilesAppEntry, mimeType?: string): string {
  return getType(entry, mimeType).type;
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if audio file.
 */
export function isAudio(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getMediaType(entry, mimeType) === 'audio';
}

/**
 * Returns whether the |entry| is image file that can be opened in browser.
 * Note that it returns false for RAW images.
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if image file.
 */
export function isImage(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getMediaType(entry, mimeType) === 'image';
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if video file.
 */
export function isVideo(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getMediaType(entry, mimeType) === 'video';
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if document file.
 */
export function isDocument(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  const type = getMediaType(entry, mimeType);
  return type === 'document' || type === 'hosted' || type === 'text';
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if raw file.
 */
export function isRaw(entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getMediaType(entry, mimeType) === 'raw';
}

/**
 * @param entry Reference to the file
 * @param mimeType Optional mime type for this file.
 * @return Whether or not this is a PDF file.
 */
export function isPDF(entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getType(entry, mimeType).subtype === 'PDF';
}

/**
 * Files with more pixels won't have preview.
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return True if type is in specified set.
 */
export function isType(
    types: string[], entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  const type = getMediaType(entry, mimeType);
  return !!type && types.indexOf(type) !== -1;
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return Returns true if the file is hosted.
 */
export function isHosted(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  return getType(entry, mimeType).type === 'hosted';
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @return Returns true if the file is encrypted with CSE.
 */
export function isEncrypted(
    entry: Entry|FilesAppEntry, mimeType?: string): boolean {
  const type = getType(entry, mimeType);
  return type.encrypted !== undefined && type.encrypted;
}

/**
 * @param entry Reference to the file.
 * @param mimeType Optional mime type for the file.
 * @param rootType The root type of the entry.
 * @return Returns string that represents the file icon. It refers to a file
 *     'images/filetype_' + icon + '.png'.
 */
export function getIcon(
    entry: Entry|FilesAppEntry|VolumeEntry|FileData, mimeType?: string,
    rootType?: RootType): string {
  // Handles the FileData and FilesAppEntry types.
  if (entry && 'iconName' in entry) {
    return entry.iconName;
  }

  let icon;
  // Handles other types of entries.
  if (entry) {
    const ventry = entry as Entry | FilesAppEntry;
    const fileType = getType(ventry, mimeType);
    const overridenIcon = getIconOverrides(ventry, rootType);
    icon = overridenIcon || fileType.icon || fileType.type;
  }
  return icon || 'unknown';
}

/**
 * Returns a string to be used as an attribute value to customize the entry
 * icon.
 *
 * @param rootType The root type of the entry.
 */
export function getIconOverrides(
    entry: Entry|FilesAppEntry, rootType?: RootType): string {
  if (!rootType) {
    return '';
  }

  // Overrides per RootType and defined by fullPath.
  const overrides: Partial<Record<RootType, Record<string, string>>> = {
    [RootType.DOWNLOADS]: {
      '/Camera': 'camera-folder',
      '/Downloads': VolumeType.DOWNLOADS,
      '/PvmDefault': 'plugin_vm',
    },
  };

  const root = overrides[rootType];
  if (!root) {
    return '';
  }

  return root[entry.fullPath] ?? '';
}