<commit_before>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import {
  IChangedArgs,
  IStateDB,
  PathExt,
  PageConfig,
  Poll
} from '@jupyterlab/coreutils';

import { IDocumentManager, shouldOverwrite } from '@jupyterlab/docmanager';

import { Contents, Kernel, Session } from '@jupyterlab/services';

import {
  ArrayIterator,
  each,
  find,
  IIterator,
  IterableOrArrayLike,
  ArrayExt
} from '@phosphor/algorithm';

import { PromiseDelegate, ReadonlyJSONObject } from '@phosphor/coreutils';

import { IDisposable } from '@phosphor/disposable';

import { ISignal, Signal } from '@phosphor/signaling';

import { showDialog, Dialog } from '@jupyterlab/apputils';

/**
 * The default duration of the auto-refresh in ms
 */
const DEFAULT_REFRESH_INTERVAL = 10000;

/**
 * The maximum upload size (in bytes) for notebook version < 5.1.0
 */
export const LARGE_FILE_SIZE = 15 * 1024 * 1024;

/**
 * The size (in bytes) of the biggest chunk we should upload at once.
 */
export const CHUNK_SIZE = 1024 * 1024;

/**
 * An upload progress event for a file at `path`.
 */
export interface IUploadModel {
  path: string;
  /**
   * % uploaded [0, 1)
   */
  progress: number;
}

/**
 * An implementation of a file browser model.
 *
 * #### Notes
 * All paths parameters without a leading `'/'` are interpreted as relative to
 * the current directory.  Supports `'../'` syntax.
 */
export class FileBrowserModel implements IDisposable {
  /**
   * Construct a new file browser model.
   */
  constructor(options: FileBrowserModel.IOptions) {
    this.manager = options.manager;
    this._driveName = options.driveName || '';
    let rootPath = this._driveName ? this._driveName + ':' : '';
    this._model = {
      path: rootPath,
      name: PathExt.basename(rootPath),
      type: 'directory',
      content: undefined,
      writable: false,
      created: 'unknown',
      last_modified: 'unknown',
      mimetype: 'text/plain',
      format: 'text'
    };
    this._state = options.state || null;
    const refreshInterval = options.refreshInterval || DEFAULT_REFRESH_INTERVAL;

    const { services } = options.manager;
    services.contents.fileChanged.connect(this._onFileChanged, this);
    services.sessions.runningChanged.connect(this._onRunningChanged, this);

    this._unloadEventListener = (e: Event) => {
      if (this._uploads.length > 0) {
        const confirmationMessage = 'Files still uploading';

        (e as any).returnValue = confirmationMessage;
        return confirmationMessage;
      }
    };
    window.addEventListener('beforeunload', this._unloadEventListener);
    this._poll = new Poll({
      factory: () => this.cd('.'),
      frequency: {
        interval: refreshInterval,
        backoff: true,
        max: 300 * 1000
      },
      standby: 'when-hidden'
    });
  }

  /**
   * The document manager instance used by the file browser model.
   */
  readonly manager: IDocumentManager;

  /**
   * A signal emitted when the file browser model loses connection.
   */
  get connectionFailure(): ISignal<this, Error> {
    return this._connectionFailure;
  }

  /**
   * The drive name that gets prepended to the path.
   */
  get driveName(): string {
    return this._driveName;
  }

  /**
   * A promise that resolves when the model is first restored.
   */
  get restored(): Promise<void> {
    return this._restored.promise;
  }

  /**
   * Get the file path changed signal.
   */
  get fileChanged(): ISignal<this, Contents.IChangedArgs> {
    return this._fileChanged;
  }

  /**
   * Get the current path.
   */
  get path(): string {
    return this._model ? this._model.path : '';
  }

  /**
   * A signal emitted when the path changes.
   */
  get pathChanged(): ISignal<this, IChangedArgs<string>> {
    return this._pathChanged;
  }

  /**
   * A signal emitted when the directory listing is refreshed.
   */
  get refreshed(): ISignal<this, void> {
    return this._refreshed;
  }

  /**
   * Get the kernel spec models.
   */
  get specs(): Kernel.ISpecModels | null {
    return this.manager.services.sessions.specs;
  }

  /**
   * Get whether the model is disposed.
   */
  get isDisposed(): boolean {
    return this._isDisposed;
  }

  /**
   * A signal emitted when an upload progresses.
   */
  get uploadChanged(): ISignal<this, IChangedArgs<IUploadModel>> {
    return this._uploadChanged;
  }

  /**
   * Create an iterator over the status of all in progress uploads.
   */
  uploads(): IIterator<IUploadModel> {
    return new ArrayIterator(this._uploads);
  }

  /**
   * Dispose of the resources held by the model.
   */
  dispose(): void {
    if (this.isDisposed) {
      return;
    }
    window.removeEventListener('beforeunload', this._unloadEventListener);
    this._isDisposed = true;
    this._poll.dispose();
    this._sessions.length = 0;
    this._items.length = 0;
    Signal.clearData(this);
  }

  /**
   * Create an iterator over the model's items.
   *
   * @returns A new iterator over the model's items.
   */
  items(): IIterator<Contents.IModel> {
    return new ArrayIterator(this._items);
  }

  /**
   * Create an iterator over the active sessions in the directory.
   *
   * @returns A new iterator over the model's active sessions.
   */
  sessions(): IIterator<Session.IModel> {
    return new ArrayIterator(this._sessions);
  }

  /**
   * Force a refresh of the directory contents.
   */
  async refresh(): Promise<void> {
    await this._poll.refresh();
    await this._poll.tick;
  }

  /**
   * Change directory.
   *
   * @param path - The path to the file or directory.
   *
   * @returns A promise with the contents of the directory.
   */
  async cd(newValue = '.'): Promise<void> {
    if (newValue !== '.') {
      newValue = Private.normalizePath(
        this.manager.services.contents,
        this._model.path,
        newValue
      );
    } else {
      newValue = this._pendingPath || this._model.path;
    }
    if (this._pending) {
      // Collapse requests to the same directory.
      if (newValue === this._pendingPath) {
        return this._pending;
      }
      // Otherwise wait for the pending request to complete before continuing.
      await this._pending;
    }
    let oldValue = this.path;
    let options: Contents.IFetchOptions = { content: true };
    this._pendingPath = newValue;
    if (oldValue !== newValue) {
      this._sessions.length = 0;
    }
    let services = this.manager.services;
    this._pending = services.contents
      .get(newValue, options)
      .then(contents => {
        if (this.isDisposed) {
          return;
        }
        this._handleContents(contents);
        this._pendingPath = null;
        this._pending = null;
        if (oldValue !== newValue) {
          // If there is a state database and a unique key, save the new path.
          // We don't need to wait on the save to continue.
          if (this._state && this._key) {
            void this._state.save(this._key, { path: newValue });
          }

          this._pathChanged.emit({
            name: 'path',
            oldValue,
            newValue
          });
        }
        this._onRunningChanged(services.sessions, services.sessions.running());
        this._refreshed.emit(void 0);
      })
      .catch(error => {
        this._pendingPath = null;
        this._pending = null;
        if (error.response && error.response.status === 404) {
          error.message = `Directory not found: "${this._model.path}"`;
          console.error(error);
          this._connectionFailure.emit(error);
          return this.cd('/');
        } else {
          this._connectionFailure.emit(error);
        }
      });
    return this._pending;
  }

  /**
   * Download a file.
   *
   * @param path - The path of the file to be downloaded.
   *
   * @returns A promise which resolves when the file has begun
   *   downloading.
   */
  download(path: string): Promise<void> {
    return this.manager.services.contents.getDownloadUrl(path).then(url => {
      let element = document.createElement('a');
      document.body.appendChild(element);
      element.setAttribute('href', url);
      element.setAttribute('target', '_blank');
      element.setAttribute('download', '');
      element.click();
      document.body.removeChild(element);
      return void 0;
    });
  }

  /**
   * Restore the state of the file browser.
   *
   * @param id - The unique ID that is used to construct a state database key.
   *
   * @returns A promise when restoration is complete.
   *<commit_msg>Workaround for Chrome download issue<commit_after>// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import {
  IChangedArgs,
  IStateDB,
  PathExt,
  PageConfig,
  Poll
} from '@jupyterlab/coreutils';

import { IDocumentManager, shouldOverwrite } from '@jupyterlab/docmanager';

import { Contents, Kernel, Session } from '@jupyterlab/services';

import {
  ArrayIterator,
  each,
  find,
  IIterator,
  IterableOrArrayLike,
  ArrayExt
} from '@phosphor/algorithm';

import { PromiseDelegate, ReadonlyJSONObject } from '@phosphor/coreutils';

import { IDisposable } from '@phosphor/disposable';

import { ISignal, Signal } from '@phosphor/signaling';

import { showDialog, Dialog } from '@jupyterlab/apputils';

/**
 * The default duration of the auto-refresh in ms
 */
const DEFAULT_REFRESH_INTERVAL = 10000;

/**
 * The maximum upload size (in bytes) for notebook version < 5.1.0
 */
export const LARGE_FILE_SIZE = 15 * 1024 * 1024;

/**
 * The size (in bytes) of the biggest chunk we should upload at once.
 */
export const CHUNK_SIZE = 1024 * 1024;

/**
 * An upload progress event for a file at `path`.
 */
export interface IUploadModel {
  path: string;
  /**
   * % uploaded [0, 1)
   */
  progress: number;
}

/**
 * An implementation of a file browser model.
 *
 * #### Notes
 * All paths parameters without a leading `'/'` are interpreted as relative to
 * the current directory.  Supports `'../'` syntax.
 */
export class FileBrowserModel implements IDisposable {
  /**
   * Construct a new file browser model.
   */
  constructor(options: FileBrowserModel.IOptions) {
    this.manager = options.manager;
    this._driveName = options.driveName || '';
    let rootPath = this._driveName ? this._driveName + ':' : '';
    this._model = {
      path: rootPath,
      name: PathExt.basename(rootPath),
      type: 'directory',
      content: undefined,
      writable: false,
      created: 'unknown',
      last_modified: 'unknown',
      mimetype: 'text/plain',
      format: 'text'
    };
    this._state = options.state || null;
    const refreshInterval = options.refreshInterval || DEFAULT_REFRESH_INTERVAL;

    const { services } = options.manager;
    services.contents.fileChanged.connect(this._onFileChanged, this);
    services.sessions.runningChanged.connect(this._onRunningChanged, this);

    this._unloadEventListener = (e: Event) => {
      if (this._uploads.length > 0) {
        const confirmationMessage = 'Files still uploading';

        (e as any).returnValue = confirmationMessage;
        return confirmationMessage;
      }
    };
    window.addEventListener('beforeunload', this._unloadEventListener);
    this._poll = new Poll({
      factory: () => this.cd('.'),
      frequency: {
        interval: refreshInterval,
        backoff: true,
        max: 300 * 1000
      },
      standby: 'when-hidden'
    });
  }

  /**
   * The document manager instance used by the file browser model.
   */
  readonly manager: IDocumentManager;

  /**
   * A signal emitted when the file browser model loses connection.
   */
  get connectionFailure(): ISignal<this, Error> {
    return this._connectionFailure;
  }

  /**
   * The drive name that gets prepended to the path.
   */
  get driveName(): string {
    return this._driveName;
  }

  /**
   * A promise that resolves when the model is first restored.
   */
  get restored(): Promise<void> {
    return this._restored.promise;
  }

  /**
   * Get the file path changed signal.
   */
  get fileChanged(): ISignal<this, Contents.IChangedArgs> {
    return this._fileChanged;
  }

  /**
   * Get the current path.
   */
  get path(): string {
    return this._model ? this._model.path : '';
  }

  /**
   * A signal emitted when the path changes.
   */
  get pathChanged(): ISignal<this, IChangedArgs<string>> {
    return this._pathChanged;
  }

  /**
   * A signal emitted when the directory listing is refreshed.
   */
  get refreshed(): ISignal<this, void> {
    return this._refreshed;
  }

  /**
   * Get the kernel spec models.
   */
  get specs(): Kernel.ISpecModels | null {
    return this.manager.services.sessions.specs;
  }

  /**
   * Get whether the model is disposed.
   */
  get isDisposed(): boolean {
    return this._isDisposed;
  }

  /**
   * A signal emitted when an upload progresses.
   */
  get uploadChanged(): ISignal<this, IChangedArgs<IUploadModel>> {
    return this._uploadChanged;
  }

  /**
   * Create an iterator over the status of all in progress uploads.
   */
  uploads(): IIterator<IUploadModel> {
    return new ArrayIterator(this._uploads);
  }

  /**
   * Dispose of the resources held by the model.
   */
  dispose(): void {
    if (this.isDisposed) {
      return;
    }
    window.removeEventListener('beforeunload', this._unloadEventListener);
    this._isDisposed = true;
    this._poll.dispose();
    this._sessions.length = 0;
    this._items.length = 0;
    Signal.clearData(this);
  }

  /**
   * Create an iterator over the model's items.
   *
   * @returns A new iterator over the model's items.
   */
  items(): IIterator<Contents.IModel> {
    return new ArrayIterator(this._items);
  }

  /**
   * Create an iterator over the active sessions in the directory.
   *
   * @returns A new iterator over the model's active sessions.
   */
  sessions(): IIterator<Session.IModel> {
    return new ArrayIterator(this._sessions);
  }

  /**
   * Force a refresh of the directory contents.
   */
  async refresh(): Promise<void> {
    await this._poll.refresh();
    await this._poll.tick;
  }

  /**
   * Change directory.
   *
   * @param path - The path to the file or directory.
   *
   * @returns A promise with the contents of the directory.
   */
  async cd(newValue = '.'): Promise<void> {
    if (newValue !== '.') {
      newValue = Private.normalizePath(
        this.manager.services.contents,
        this._model.path,
        newValue
      );
    } else {
      newValue = this._pendingPath || this._model.path;
    }
    if (this._pending) {
      // Collapse requests to the same directory.
      if (newValue === this._pendingPath) {
        return this._pending;
      }
      // Otherwise wait for the pending request to complete before continuing.
      await this._pending;
    }
    let oldValue = this.path;
    let options: Contents.IFetchOptions = { content: true };
    this._pendingPath = newValue;
    if (oldValue !== newValue) {
      this._sessions.length = 0;
    }
    let services = this.manager.services;
    this._pending = services.contents
      .get(newValue, options)
      .then(contents => {
        if (this.isDisposed) {
          return;
        }
        this._handleContents(contents);
        this._pendingPath = null;
        this._pending = null;
        if (oldValue !== newValue) {
          // If there is a state database and a unique key, save the new path.
          // We don't need to wait on the save to continue.
          if (this._state && this._key) {
            void this._state.save(this._key, { path: newValue });
          }

          this._pathChanged.emit({
            name: 'path',
            oldValue,
            newValue
          });
        }
        this._onRunningChanged(services.sessions, services.sessions.running());
        this._refreshed.emit(void 0);
      })
      .catch(error => {
        this._pendingPath = null;
        this._pending = null;
        if (error.response && error.response.status === 404) {
          error.message = `Directory not found: "${this._model.path}"`;
          console.error(error);
          this._connectionFailure.emit(error);
          return this.cd('/');
        } else {
          this._connectionFailure.emit(error);
        }
      });
    return this._pending;
  }

  /**
   * Download a file.
   *
   * @param path - The path of the file to be downloaded.
   *
   * @returns A promise which resolves when the file has begun
   *   downloading.
   */
  download(path: string): Promise<void> {
    return this.manager.services.contents.getDownloadUrl(path).then(url => {
      // Check the browser is Chrome https://stackoverflow.com/a/9851769
      const chrome = (window as any).chrome;
      const isChrome = !!chrome && (!!chrome.webstore || !!chrome.runtime);
      if (isChrome) {
        // Workaround https://bugs.chromium.org/p/chromium/issues/detail?id=455987
        window.open(url);
      } else {
        let element = document.createElement('a');
        document.body.appendChild(element);
        element.setAttribute('href', url);
        element.setAttribute('download', '');
        element.click();
        document.body.removeChild(element);
        return void 0;
      }
    });
  }

  /**
   * Restore the state of the file browser.
   *
   * @param id - The unique ID that is used to construct a state database key.
   *
   * @returns A promise when restoration is complete.
   *
{'fraction_non_alphanumeric': 0.11994971141208069, 'fraction_numerical': 0.0057717583861934965, 'mean_word_length': 2.5346394667743892, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 43, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 15, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6417974', 'n_tokens_mistral': 5303, 'n_tokens_neox': 5117, 'n_words': 1432}