<commit_before>/*
 *   This file is part of 6502.ts, an emulator for 6502 based systems built
 *   in Typescript.
 *
 *   Copyright (C) 2014 - 2017 <NAME> & contributors
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import {Middleware, MiddlewareAPI, Action, Store} from 'redux';
import {push} from 'react-router-redux';
import * as JsZip from 'jszip';

import CartridgeManagerInterface from '../CartridgeManager';
import StorageManager from '../StorageManager';
import {
    actions,
    UploadNewCartridgeAction,
    SelectCartridgeAction,
    ConfirmLoadAction,
    ConfirmSelectAction,
    SelectRomFromZipfileAction
} from '../../actions/cartridgeManager';
import {
    saveCurrentCartride,
    registerNewCartridge,
    selectCartridge,
    types as rootActions
} from '../../actions/root';
import {
    setMode,
    openLoadPendingChangesModal,
    openSelectPendingChangesModal,
    closeLoadPendingChangesModal,
    closeSelectPendingChangesModal
} from '../../actions/guiState';
import {
    set as setZipfile,
    clear as clearZipfile,
    setError as setZipfileError
} from '../../actions/zipfile';
import {start as startEmulation} from '../../actions/emulation';
import {GuiMode} from '../../model/types';
import State from '../../state/State';
import Cartride from '../../model/Cartridge';
import {calculateFromUint8Array as md5sum} from '../../../../../tools/hash/md5';

class CartridgeManager implements CartridgeManagerInterface {

    constructor(
        private _storage: StorageManager
    ) {}

    setStore(store: Store<State>): void {
        this._store = store;
    }

    getMiddleware(): Middleware {
        return this._middleware;
    }

    private async _onRunCurrentCartridge(): Promise<void> {
        await this._store.dispatch(saveCurrentCartride());
        await this._store.dispatch(setMode(GuiMode.run));
        await this._store.dispatch(startEmulation());
        await this._store.dispatch(push('/emulation'));
    }

    private async _onUpload(action: UploadNewCartridgeAction): Promise<void> {
        const reader = new FileReader(),
            fileContent = await new Promise<Uint8Array>(
                r => {
                    reader.addEventListener('load', () => r(new Uint8Array(reader.result)));
                    reader.readAsArrayBuffer(action.file);
                }
            );

        if (action.file.name.match(/\.zip$/)) {
            return this._handleZipfile(action.file.name, fileContent);
        } else {
            return this._handleCartridge(action.file.name, fileContent);
        }
    }

    private async _handleCartridge(name: string, content: Uint8Array) {
        const state = this._store.getState(),
            changes = !!state.currentCartridge &&
                !Cartride.equals(state.currentCartridge, state.cartridges[state.currentCartridge.hash]);

        if (changes) {
            await this._store.dispatch(openLoadPendingChangesModal(content, name));
        } else {
            await this._storage.saveImage(md5sum(content), content);
            await this._store.dispatch(registerNewCartridge(name, content));
        }
    }

    private async _handleZipfile(name: string, content: Uint8Array): Promise<void> {
        const zipfile = new JsZip();

        try {
            await zipfile.loadAsync(content);

            const files = zipfile.file(/\.(bin|a26)$/);

            if (files.length === 1) {
                const file = files[0],
                    deflatedImage = await file.async('uint8array');

                this._handleCartridge(file.name.replace(/^.*\//, ''), deflatedImage);
            } else if (files.length > 1) {
                await this._store.dispatch(setZipfile(
                    content,
                    files.map(f => f.name).sort()
                ));
            }
        } catch (e) {
            await this._store.dispatch(clearZipfile());
            await this._store.dispatch(setZipfileError('Unable to read ZIP.'));
        }
    }

    private async _onSelectRomFromZipfile(action: SelectRomFromZipfileAction): Promise<void> {
        const zipfile = new JsZip(),
            state = this._store.getState();

        try {
            await zipfile.loadAsync(state.zipfile.content);

            const file = zipfile.file(action.filename);

            if (!file) {
                throw new Error('no such file in archive');
            }

            const deflatedImage = await file.async('uint8array');

            await this._handleCartridge(action.filename.replace(/^.*\//, ''), deflatedImage);
            await this._store.dispatch(clearZipfile());
        } catch (e) {<commit_msg>Error handling for zipfiles w/o ROMs.<commit_after>/*
 *   This file is part of 6502.ts, an emulator for 6502 based systems built
 *   in Typescript.
 *
 *   Copyright (C) 2014 - 2017 <NAME> & contributors
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import {Middleware, MiddlewareAPI, Action, Store} from 'redux';
import {push} from 'react-router-redux';
import * as JsZip from 'jszip';

import CartridgeManagerInterface from '../CartridgeManager';
import StorageManager from '../StorageManager';
import {
    actions,
    UploadNewCartridgeAction,
    SelectCartridgeAction,
    ConfirmLoadAction,
    ConfirmSelectAction,
    SelectRomFromZipfileAction
} from '../../actions/cartridgeManager';
import {
    saveCurrentCartride,
    registerNewCartridge,
    selectCartridge,
    types as rootActions
} from '../../actions/root';
import {
    setMode,
    openLoadPendingChangesModal,
    openSelectPendingChangesModal,
    closeLoadPendingChangesModal,
    closeSelectPendingChangesModal
} from '../../actions/guiState';
import {
    set as setZipfile,
    clear as clearZipfile,
    setError as setZipfileError
} from '../../actions/zipfile';
import {start as startEmulation} from '../../actions/emulation';
import {GuiMode} from '../../model/types';
import State from '../../state/State';
import Cartride from '../../model/Cartridge';
import {calculateFromUint8Array as md5sum} from '../../../../../tools/hash/md5';

class CartridgeManager implements CartridgeManagerInterface {

    constructor(
        private _storage: StorageManager
    ) {}

    setStore(store: Store<State>): void {
        this._store = store;
    }

    getMiddleware(): Middleware {
        return this._middleware;
    }

    private async _onRunCurrentCartridge(): Promise<void> {
        await this._store.dispatch(saveCurrentCartride());
        await this._store.dispatch(setMode(GuiMode.run));
        await this._store.dispatch(startEmulation());
        await this._store.dispatch(push('/emulation'));
    }

    private async _onUpload(action: UploadNewCartridgeAction): Promise<void> {
        const reader = new FileReader(),
            fileContent = await new Promise<Uint8Array>(
                r => {
                    reader.addEventListener('load', () => r(new Uint8Array(reader.result)));
                    reader.readAsArrayBuffer(action.file);
                }
            );

        if (action.file.name.match(/\.zip$/)) {
            return this._handleZipfile(action.file.name, fileContent);
        } else {
            return this._handleCartridge(action.file.name, fileContent);
        }
    }

    private async _handleCartridge(name: string, content: Uint8Array) {
        const state = this._store.getState(),
            changes = !!state.currentCartridge &&
                !Cartride.equals(state.currentCartridge, state.cartridges[state.currentCartridge.hash]);

        if (changes) {
            await this._store.dispatch(openLoadPendingChangesModal(content, name));
        } else {
            await this._storage.saveImage(md5sum(content), content);
            await this._store.dispatch(registerNewCartridge(name, content));
        }
    }

    private async _handleZipfile(name: string, content: Uint8Array): Promise<void> {
        const zipfile = new JsZip();

        try {
            await zipfile.loadAsync(content);

            const files = zipfile.file(/\.(bin|a26)$/);

            if (files.length === 0) {
                this._store.dispatch(setZipfileError('No ROM images in ZIP file.'));
            }
            else if (files.length === 1) {
                const file = files[0],
                    deflatedImage = await file.async('uint8array');

                this._handleCartridge(file.name.replace(/^.*\//, ''), deflatedImage);
            } else {
                await this._store.dispatch(setZipfile(
                    content,
                    files.map(f => f.name).sort()
                ));
            }
        } catch (e) {
            await this._store.dispatch(clearZipfile());
            await this._store.dispatch(setZipfileError('Unable to read ZIP.'));
        }
    }

    private async _onSelectRomFromZipfile(action: SelectRomFromZipfileAction): Promise<void> {
        const zipfile = new JsZip(),
            state = this._store.getState();

        try {
            await zipfile.loadAsync(state.zipfile.content);

            const file = zipfile.file(action.filename);

            if (!file) {
                throw new Error('no such file in archive');
            }

            const deflatedImage = await file.async('uint8array');

            await this._handleCartridge(action.filename.replace(/^.*\//, ''), deflatedImage);
            await this._store.dispatch(clearZipfile());
        } catch (e) {
{'fraction_non_alphanumeric': 0.11256448047162859, 'fraction_numerical': 0.007921886514369933, 'mean_word_length': 2.457643312101911, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1464260', 'n_tokens_mistral': 3033, 'n_tokens_neox': 2882, 'n_words': 782}