<commit_before>import * as actions from "@nteract/actions";
import { Notebook, stringifyNotebook, toJS } from "@nteract/commutable";
import * as selectors from "@nteract/selectors";
import { AppState, ContentRef, DirectoryContentRecordProps, DummyContentRecordProps, FileContentRecordProps, IContent, IContentProvider, JupyterHostRecord, NotebookContentRecordProps, ServerConfig } from "@nteract/types";
import FileSaver from "file-saver";
import { existsSync } from "fs";
import { RecordOf } from "immutable";
import { Action } from "redux";
import { ActionsObservable, ofType, StateObservable } from "redux-observable";
import { EMPTY, from, interval, Observable, of } from "rxjs";
import { AjaxResponse } from "rxjs/ajax";
import { catchError, concatMap, distinctUntilChanged, map, mergeMap, pluck, switchMap, take, tap } from "rxjs/operators";
import urljoin from "url-join";

export function updateContentEpic(
  action$: ActionsObservable<actions.ChangeContentName>,
  state$: StateObservable<AppState>,
  dependencies: { contentProvider: IContentProvider }
): Observable<unknown> {
  return action$.pipe(
    ofType(actions.CHANGE_CONTENT_NAME),
    switchMap(action => {
      const state = state$.value;
      const { filepath, prevFilePath } = action.payload;
      
      const host = selectors.currentHost(state) as JupyterHostRecord;
      const serverConfig: ServerConfig = selectors.serverConfig(host);

      return dependencies.contentProvider
        .update(serverConfig, prevFilePath, { path: filepath.slice(1) })
        .pipe(
          tap(xhr => {
            if (xhr.status !== 200) {
              throw new Error(xhr.response);
            }
          }),
          map(() => {
            /*
             * Modifying the url's file name in the browser.
             * This effects back button behavior.
             * Is there a better way to accomplish this?
             */
            window.history.replaceState(
              {},
              filepath,
              urljoin(host.basePath, `/nteract/edit${filepath}`)
            );

            return actions.changeContentNameFulfilled({
              contentRef: action.payload.contentRef,
              filepath: action.payload.filepath,
              prevFilePath
            });
          }),
          catchError((xhrError: any) =>
            of(
              actions.changeContentNameFailed({
                basepath: host.basePath,
                filepath: action.payload.filepath,
                prevFilePath,
                error: xhrError,
                contentRef: action.payload.contentRef
              })
            )
          )
        );
    })
  );
}

export function fetchContentEpic(
  action$: ActionsObservable<
    | actions.FetchContent
    | actions.FetchContentFailed
    | actions.FetchContentFulfilled
  >,
  state$: StateObservable<AppState>,
  dependencies: { contentProvider: IContentProvider }
): Observable<unknown> {
  return action$.pipe(
    ofType(actions.FETCH_CONTENT),
    switchMap(action => {
      const state = state$.value;
      const host = selectors.currentHost(state) as JupyterHostRecord;
      const serverConfig: ServerConfig = selectors.serverConfig(host);

      return dependencies.contentProvider
        .get(
          serverConfig,
          (action as actions.FetchContent).payload.filepath,
          (action as actions.FetchContent).payload.params
        )
        .pipe(
          tap(xhr => {
            if (xhr.status !== 200) {
              throw new Error(xhr.response.toString());
            }
          }),
          map(xhr => {
            if (typeof xhr.response === "string") {
              throw new Error(`Invalid API response: ${xhr.response}`);
            }

            return actions.fetchContentFulfilled({
              filepath: action.payload.filepath,
              model: xhr.response,
              kernelRef: action.payload.kernelRef,
              contentRef: action.payload.contentRef
            });
          }),
          catchError((xhrError: any) =>
            of(
              actions.fetchContentFailed({
                filepath: action.payload.filepath,
                error: xhrError,
                kernelRef: action.payload.kernelRef,
                contentRef: action.payload.contentRef
              })
            )
          )
        );
    })
  );
}

export function downloadString(
  fileContents: string,
  filepath: string,
  contentType: string
): void {
  const filename = filepath.split("/").pop();
  const blob = new Blob([fileContents], { type: contentType });
  // NOTE: There is no callback for this, we have to rely on the browser
  //       to do this well, so we assume it worked
  FileSaver.saveAs(blob, filename);
}

export function autoSaveCurrentContentEpic(
  action$: ActionsObservable<Action>,
  state$: StateObservable<AppState>
): Observable<actions.Save> {
  const state = state$.value;
  const duration = selectors.autoSaveInterval(state);
  return interval(duration).pipe(
    mergeMap(() => {
      return from(
        selectors<commit_msg>Fix auto save<commit_after>import * as actions from "@nteract/actions";
import { Notebook, stringifyNotebook, toJS } from "@nteract/commutable";
import * as selectors from "@nteract/selectors";
import { AppState, ContentRef, DirectoryContentRecordProps, DummyContentRecordProps, FileContentRecordProps, IContent, IContentProvider, JupyterHostRecord, NotebookContentRecordProps, ServerConfig } from "@nteract/types";
import FileSaver from "file-saver";
import { existsSync } from "fs";
import { RecordOf } from "immutable";
import { Action } from "redux";
import { ActionsObservable, ofType, StateObservable } from "redux-observable";
import { EMPTY, from, interval, Observable, of } from "rxjs";
import { AjaxResponse } from "rxjs/ajax";
import { catchError, concatMap, distinctUntilChanged, map, mergeMap, pluck, switchMap, take, tap } from "rxjs/operators";
import urljoin from "url-join";

export function updateContentEpic(
  action$: ActionsObservable<actions.ChangeContentName>,
  state$: StateObservable<AppState>,
  dependencies: { contentProvider: IContentProvider }
): Observable<unknown> {
  return action$.pipe(
    ofType(actions.CHANGE_CONTENT_NAME),
    switchMap(action => {
      const state = state$.value;
      const { filepath, prevFilePath } = action.payload;
      
      const host = selectors.currentHost(state) as JupyterHostRecord;
      const serverConfig: ServerConfig = selectors.serverConfig(host);

      return dependencies.contentProvider
        .update(serverConfig, prevFilePath, { path: filepath.slice(1) })
        .pipe(
          tap(xhr => {
            if (xhr.status !== 200) {
              throw new Error(xhr.response);
            }
          }),
          map(() => {
            /*
             * Modifying the url's file name in the browser.
             * This effects back button behavior.
             * Is there a better way to accomplish this?
             */
            window.history.replaceState(
              {},
              filepath,
              urljoin(host.basePath, `/nteract/edit${filepath}`)
            );

            return actions.changeContentNameFulfilled({
              contentRef: action.payload.contentRef,
              filepath: action.payload.filepath,
              prevFilePath
            });
          }),
          catchError((xhrError: any) =>
            of(
              actions.changeContentNameFailed({
                basepath: host.basePath,
                filepath: action.payload.filepath,
                prevFilePath,
                error: xhrError,
                contentRef: action.payload.contentRef
              })
            )
          )
        );
    })
  );
}

export function fetchContentEpic(
  action$: ActionsObservable<
    | actions.FetchContent
    | actions.FetchContentFailed
    | actions.FetchContentFulfilled
  >,
  state$: StateObservable<AppState>,
  dependencies: { contentProvider: IContentProvider }
): Observable<unknown> {
  return action$.pipe(
    ofType(actions.FETCH_CONTENT),
    switchMap(action => {
      const state = state$.value;
      const host = selectors.currentHost(state) as JupyterHostRecord;
      const serverConfig: ServerConfig = selectors.serverConfig(host);

      return dependencies.contentProvider
        .get(
          serverConfig,
          (action as actions.FetchContent).payload.filepath,
          (action as actions.FetchContent).payload.params
        )
        .pipe(
          tap(xhr => {
            if (xhr.status !== 200) {
              throw new Error(xhr.response.toString());
            }
          }),
          map(xhr => {
            if (typeof xhr.response === "string") {
              throw new Error(`Invalid API response: ${xhr.response}`);
            }

            return actions.fetchContentFulfilled({
              filepath: action.payload.filepath,
              model: xhr.response,
              kernelRef: action.payload.kernelRef,
              contentRef: action.payload.contentRef
            });
          }),
          catchError((xhrError: any) =>
            of(
              actions.fetchContentFailed({
                filepath: action.payload.filepath,
                error: xhrError,
                kernelRef: action.payload.kernelRef,
                contentRef: action.payload.contentRef
              })
            )
          )
        );
    })
  );
}

export function downloadString(
  fileContents: string,
  filepath: string,
  contentType: string
): void {
  const filename = filepath.split("/").pop();
  const blob = new Blob([fileContents], { type: contentType });
  // NOTE: There is no callback for this, we have to rely on the browser
  //       to do this well, so we assume it worked
  FileSaver.saveAs(blob, filename);
}

export function autoSaveCurrentContentEpic(
  action$: ActionsObservable<Action>,
  state$: StateObservable<AppState>
): Observable<actions.Save> {
  const duration = selectors.autoSaveInterval(state$.value);
  return interval(duration).pipe(
    mergeMap(() => {
      const state = state$.value;
      return from(
        selectors
{'fraction_non_alphanumeric': 0.10362796185232524, 'fraction_numerical': 0.0013764624913971094, 'mean_word_length': 2.304743339831059, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6250198', 'n_tokens_mistral': 2729, 'n_tokens_neox': 2676, 'n_words': 631}