<commit_before>// Copyright © 2014 Intel Corporation. All rights reserved.
// Use  of this  source  code is  governed by  an Apache v2
// license that can be found in the LICENSE-APACHE-V2 file.

var Path = require('path');
var ShellJS = require("shelljs");
var AndroidSDK = require("./AndroidSDK");
var Console = require("./Console");
var Project = require("./Project");
var TemplateFile = require("./TemplateFile");

/**
 * Android project class.
 * @throws {@link AndroidSDK~SDKNotFoundError} If the Android SDK was not found in the environment.
 * @constructor
 */
function AndroidProject() {

    this._sdk = new AndroidSDK();
}
AndroidProject.prototype = Project;

AndroidProject.prototype.generateTemplates =
function(packageId, apiTarget, path) {

    var parts = packageId.split('.');
    var packageName = parts[parts.length - 1];
    var data = {
        "packageId" : packageId,
        "packageName" : packageName,
        "apiTarget" : apiTarget
    };

    // AndroidManifest.xml
    var tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "AndroidManifest.xml.tpl");
    tpl.render(data, path + Path.sep + "AndroidManifest.xml");

    // build.xml
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "build.xml.tpl");
    tpl.render(data, path + Path.sep + "build.xml");

    // project.properties
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "project.properties.tpl");
    tpl.render(data, path + Path.sep + "project.properties");

    // MainActivity.java
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "MainActivity.java.tpl");
    var activityPath = path + Path.sep +
                       "src" + Path.sep +
                       parts.join(Path.sep);
    tpl.render(data, activityPath + Path.sep + "MainActivity.java");

    // Copy
    var dirs = ShellJS.ls('crosswalk-*.*.*.*');
    if (dirs.length === 0) {
        // Also try parent dir.
        // This is especially useful for tests that run in a temporary dir.
        dirs = ShellJS.ls('../crosswalk-*.*.*.*');
        if (dirs.length === 0) {
            Console.error("Unpacked Crosswalk not found in current or parent directory " + ShellJS.pwd());
            return false;
        }
    }
    var appTplPath = dirs[0];

    // Copy xwalk_core_library
    ShellJS.cp("-r",
               appTplPath + Path.sep + "xwalk_core_library",
               path);

    // Copy jars
    ShellJS.cp(appTplPath + Path.sep + "template" + Path.sep + "libs" + Path.sep + "*.jar",
               path + Path.sep + "libs");

    // Copy res
    ShellJS.cp("-r",
               appTplPath + Path.sep + "template" + Path.sep + "res",
               path);

    // Make html5 app dir and copy sample content
    ShellJS.mkdir("-p", path + Path.sep + "assets");
    ShellJS.cp("-r",
               __dirname + Path.sep + ".."+ Path.sep + "data" + Path.sep + "www",
               path + Path.sep + "assets");

    return true;
};

/**
 * Implements {@link Project.generate}
 */
AndroidProject.prototype.generate =
function(packageId, callback) {

    var minApiLevel = 19;
    var apiTarget;
    this._sdk.queryTarget(minApiLevel,
                          function(apiTarget, errormsg) {

        if (errormsg) {
            callback(errormsg);
            return;
        }

        this._sdk.generateProjectTemplate(packageId, apiTarget,
                                          function(path, logmsg, errormsg) {

            if (!path || errormsg) {
                callback(errormsg);
                return;
            }

            var ret = this.generateTemplates(packageId, apiTarget, path);
            if (!ret) {
                callback("Creating project template failed.");
                return;
            }

            Console.log("Project template created at '" + path + "'");
            callback(null);

        }.bind(this));
    }.bind(this));
};

AndroidProject.prototype.update =
function() {

    // TODO implement
};

AndroidProject.prototype.refresh =
function() {

    // TODO implement
};

AndroidProject.prototype.build =
function() {

    // TODO implement
};

module.exports = AndroidProject;
<commit_msg>AndroidProject: Force copy of template content<commit_after>// Copyright © 2014 Intel Corporation. All rights reserved.
// Use  of this  source  code is  governed by  an Apache v2
// license that can be found in the LICENSE-APACHE-V2 file.

var Path = require('path');
var ShellJS = require("shelljs");
var AndroidSDK = require("./AndroidSDK");
var Console = require("./Console");
var Project = require("./Project");
var TemplateFile = require("./TemplateFile");

/**
 * Android project class.
 * @throws {@link AndroidSDK~SDKNotFoundError} If the Android SDK was not found in the environment.
 * @constructor
 */
function AndroidProject() {

    this._sdk = new AndroidSDK();
}
AndroidProject.prototype = Project;

AndroidProject.prototype.generateTemplates =
function(packageId, apiTarget, path) {

    var parts = packageId.split('.');
    var packageName = parts[parts.length - 1];
    var data = {
        "packageId" : packageId,
        "packageName" : packageName,
        "apiTarget" : apiTarget
    };

    // AndroidManifest.xml
    var tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "AndroidManifest.xml.tpl");
    tpl.render(data, path + Path.sep + "AndroidManifest.xml");

    // build.xml
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "build.xml.tpl");
    tpl.render(data, path + Path.sep + "build.xml");

    // project.properties
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "project.properties.tpl");
    tpl.render(data, path + Path.sep + "project.properties");

    // MainActivity.java
    tpl = new TemplateFile(__dirname + Path.sep +
                               ".."+ Path.sep +
                               "data" + Path.sep +
                               "MainActivity.java.tpl");
    var activityPath = path + Path.sep +
                       "src" + Path.sep +
                       parts.join(Path.sep);
    tpl.render(data, activityPath + Path.sep + "MainActivity.java");

    // Copy
    var dirs = ShellJS.ls('crosswalk-*.*.*.*');
    if (dirs.length === 0) {
        // Also try parent dir.
        // This is especially useful for tests that run in a temporary dir.
        dirs = ShellJS.ls('../crosswalk-*.*.*.*');
        if (dirs.length === 0) {
            Console.error("Unpacked Crosswalk not found in current or parent directory " + ShellJS.pwd());
            return false;
        }
    }
    var appTplPath = dirs[0];

    // Copy xwalk_core_library
    ShellJS.cp("-r",
               appTplPath + Path.sep + "xwalk_core_library",
               path);

    // Copy jars
    ShellJS.cp(appTplPath + Path.sep + "template" + Path.sep + "libs" + Path.sep + "*.jar",
               path + Path.sep + "libs");

    // Copy res
    ShellJS.cp("-rf",
               appTplPath + Path.sep + "template" + Path.sep + "res",
               path);

    // Make html5 app dir and copy sample content
    ShellJS.mkdir("-p", path + Path.sep + "assets");
    ShellJS.cp("-r",
               __dirname + Path.sep + ".."+ Path.sep + "data" + Path.sep + "www",
               path + Path.sep + "assets");

    return true;
};

/**
 * Implements {@link Project.generate}
 */
AndroidProject.prototype.generate =
function(packageId, callback) {

    var minApiLevel = 19;
    var apiTarget;
    this._sdk.queryTarget(minApiLevel,
                          function(apiTarget, errormsg) {

        if (errormsg) {
            callback(errormsg);
            return;
        }

        this._sdk.generateProjectTemplate(packageId, apiTarget,
                                          function(path, logmsg, errormsg) {

            if (!path || errormsg) {
                callback(errormsg);
                return;
            }

            var ret = this.generateTemplates(packageId, apiTarget, path);
            if (!ret) {
                callback("Creating project template failed.");
                return;
            }

            Console.log("Project template created at '" + path + "'");
            callback(null);

        }.bind(this));
    }.bind(this));
};

AndroidProject.prototype.update =
function() {

    // TODO implement
};

AndroidProject.prototype.refresh =
function() {

    // TODO implement
};

AndroidProject.prototype.build =
function() {

    // TODO implement
};

module.exports = AndroidProject;
<|endoftext|>


<commit_before>// ==UserScript==
// @name            nHentai Downloader
// @name:vi         nHentai Downloader
// @namespace       http://devs.forumvi.com
// @description     Download manga on nHentai.
// @description:vi  Tải truyện tranh tại NhệnTái.
// @version         2.0.0
// @icon            http://i.imgur.com/FAsQ4vZ.png
// @author          Zzbaivong
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           http://nhentai.net/g/*
// @match           https://nhentai.net/g/*
// @require         https://code.jquery.com/jquery-3.5.1.min.js
// @require         https://unpkg.com/jszip@3.1.5/dist/jszip.min.js
// @require         https://greasyfork.org/scripts/28536-gm-config/code/GM_config.js?version=184529
// @require         https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?v=a834d46
// @require         https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.0.2/dist/ponyfill.min.js
// @require         https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js
// @noframes
// @connect         self
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           GM.xmlHttpRequest
// @grant           GM_xmlhttpRequest
// @grant           unsafeWindow
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM.getValue
// @grant           GM.setValue
// ==/UserScript==

(function ($, window) {
  'use strict';

  var configFrame = document.createElement('div');
  $('#info-block').append(configFrame);

  GM_config.init({
    id: 'nHentaiDlConfig',
    title: 'Downloader Settings',
    fields: {
      outputExt: {
        options: ['cbz', 'zip'],
        label: 'Export as',
        type: 'radio',
        default: 'cbz',
      },
      outputName: {
        label: 'Filename',
        type: 'select',
        options: ['pretty', 'english', 'japanese'],
        default: 'pretty',
      },
      threading: {
        label: 'Max. conn. number',
        type: 'unsigned int',
        min: 1,
        max: 32,
        default: 4,
      },
      hideTorrentBtn: {
        label: 'Hide the download torrent button',
        type: 'checkbox',
        default: false,
      }
    },
    frame: configFrame,
    events: {
      save: function () {
        outputExt = GM_config.get('outputExt');
        outputName = GM_config.get('outputName');
        threading = GM_config.get('threading');

        $download.find('span').text(' as ' + outputExt.toUpperCase());

        if (GM_config.get('hideTorrentBtn') == true) {
          $_download.hide();
        } else {
          $_download.show();
        }

        var $saveBtn = $('#nHentaiDlConfig_saveBtn');
        $saveBtn.prop('disabled', true).addClass('saved').text('Saved!');

        setTimeout(function () {
          $saveBtn.prop('disabled', false).removeClass('saved').text('Save');
        }, 1500);
      },
    },
    css:
      '#nHentaiDlConfig{width:100%!important;position:initial!important;padding:10px!important;background:#0d0d0d;border:1px solid #313131!important;border-radius:5px;text-align:left}#nHentaiDlConfig *{font-family:"Noto Sans",sans-serif}#nHentaiDlConfig .config_header{text-align:left;font-size:17px;font-weight:700;margin-bottom:20px;color:#999}#nHentaiDlConfig .reset_holder{float:left;height:30px;line-height:30px}#nHentaiDlConfig .reset{color:#4d4d4d;text-align:left}#nHentaiDlConfig .saveclose_buttons{margin:0;padding:4px;min-width:100px;height:30px;line-height:14px;border-radius:2px;border:1px solid;cursor:pointer}#nHentaiDlConfig .saveclose_buttons.saved{background:#ffeb3b;border:1px solid #ffc107}#nHentaiDlConfig #nHentaiDlConfig_closeBtn{display:none}#nHentaiDlConfig_buttons_holder{margin-top:20px;border-top:1px dashed #4d4d4d;padding-top:11px}#nHentaiDlConfig .config_var::after{clear:both;content:"";display:block}#nHentaiDlConfig .config_var{position:relative}#nHentaiDlConfig .field_label{font-size:14px;height:26px;line-height:26px;margin:0;padding:0 10px 0 0;width:60%;display:block;float:left}#nHentaiDlConfig .config_var>[type=text],#nHentaiDlConfig .config_var>div,#nHentaiDlConfig .config_var>select,#nHentaiDlConfig .config_var>textarea{width:40%;border-radius:0;display:block;height:26px;line-height:26px;padding:0 10px;float:left}#nHentaiDlConfig .config_var>textarea{height:auto;line-height:14px;padding:10px;min-height:5em}#nHentaiDlConfig .config_var>select{background:#4d4d4d;color:#d9d9d9;padding:0}#nHentaiDlConfig .config_var>select:hover{background:#666}#nHentaiDlConfig .config_var>select:focus{outline:0 none}#nHentaiDlConfig .config_var>div>label{display:inline-block;vertical-align:top;margin-right:5px}#nHentaiDlConfig .config_var>#nHentaiDlConfig_field_outputName{width:150px;text-transform:capitalize}#nHentaiDlConfig .config_var>#nHentaiDlConfig_field_threading{width:70px}#nHentaiDlConfig .config_var>div{padding:0}#nHentaiDlConfig_field_outputExt{text-transform:uppercase}#nHentaiDlConfig_field_outputExt [value=cbz]{margin-right:20px!important}',
  });

  /**
   * Output extension
   * @type {'cbz'|'zip'}
   *
   * Tips: Convert .zip to .cbz
   * Windows
   * $ ren *.zip *.cbz
   * Linux
   * $ rename 's/\.zip$/\.cbz/' *.zip
   */
  var outputExt = GM_config.get('outputExt') || 'cbz';

  /**
   * File name
   * @type {'pretty'|'english'|'japanese'}
   */
  var outputName = GM_config.get('outputName') || 'pretty';

  /**
   * Multithreading
   * @type {Number} [1 -> 32]
   */
  var threading = GM_config.get('threading') || 4;

  /**
   * Logging
   * @type {Boolean}
   */
  var debug = false;

  function end() {
    $win.off('beforeunload');

    if (debug) console.timeEnd('nHentai');
  }

  function getInfo() {
    var info = '',
      tags = [],
      artists = [],
      groups = [],
      parodies = [],
      characters = [],
      categories = [],
      languages = [];

    if (gallery.title.english) info += gallery.title.english + '\r\n';
    if (gallery.title.japanese) info += gallery.title.japanese + '\r\n';
    if (gallery.title.pretty) info += gallery.title.pretty + '\r\n';
    info += '#' + gallery.id + '\r\n';

    if (gallery.tags) {
      for (const tag of gallery.tags) {
        if (tag.type === 'tag') tags.push(tag.name);
        if (tag.type === 'artist') artists.push(tag.name);
        if (tag.type === 'category') categories.push(tag.name);
        if (tag.type === 'group') groups.push(tag.name);
        if (tag.type === 'parody') parodies.push(tag.name);
        if (tag.type === 'character') characters.push(tag.name);
        if (tag.type === 'language') languages.push(tag.name);
      }
    }
    if (tags.length) info += '\r\n' + 'Tags: ' + tags.join(', ');
    if (categories.length) info += '\r\n' + 'Categories: ' + categories.join(', ');
    if (groups.length) info += '\r\n' + 'Groups: ' + groups.join(', ');
    if (parodies.length) info += '\r\n' + 'Parodies: ' + parodies.join(', ');
    if (characters.length) info += '\r\n' + 'Characters: ' + characters.join(', ');
    if (languages.length) info += '\r\n' + 'Languages: ' + languages.join(', ');

    info += '\r\n\r\n' + 'Pages: ' + total;
    info += '\r\n' + 'Uploaded at: ' + new Date(gallery.upload_date * 1000).toLocaleString() + '\r\n';

    if (debug) console.log(info);
    return info;
  }

  function genZip() {
    zip.file('info.txt', getInfo());

    zip
      .generateAsync(
        {
          type: 'blob',
          compression: 'STORE',
          streamFiles: true // Less memory but less compatibility, https://stuk.github.io/jszip/documentation/api_jszip/generate_async.html#streamfiles-option
        },
        function updateCallback(metadata) {
          $download.html(' ' + metadata.percent.toFixed(2) + ' %');
        }
      )
      .then(
        function (blob) {
          var filename = gallery.title[outputName] || gallery.title["english"]; // e.g. #321311
          var zipName = filename.replace(/\s+/g, '-') + '.' + comicId + '.' + outputExt;
          zipName.replace(/・/g, '·'); // compatibility for MangaMeeya

          $download
            .html(' Complete')
            .css('backgroundColor', hasErr ? 'red' : 'green')
            .attr({
              href: 'javascript:void(0);',
              download: zipName,
            });

          const fileStream = streamSaver.createWriteStream(zipName, {
            size: blob.size
          })
          const readableStream = blob.stream()

          window.FSwriter = fileStream.getWriter()
          const reader = readableStream.getReader()
          const pump = () => reader.read()
            .then(res => res.done
              ? FSwriter.close()
              : FSwriter.write(res.value).then(pump))
          pump(); // Firefox does not support pipeTo() yet.

          doc.title = '[⇓] ' + filename;
          if (debug) console.log('COMPLETE');
          end();
        },
        function (reason) {
          $download.html(' Fail').css('backgroundColor', 'red');

          doc.title = '[x] ' + gallery.title[outputName];
          if (debug) console.error(reason, 'ERROR');
          end();
        }
      );
  }

  function dlImg(current, success, error) {
    var url = images[current].url,
      filename = url.replace(/.*\//g, '');

    filename = ('0000' + filename).slice(-8);
    if (debug) console.log(filename, 'progress');

    GM.xmlHttpRequest({
      method: 'GET',
      url: url,
      responseType: 'arraybuffer',
      onload: function (response) {
        final++;
        success(response, filename);
      },
      onerror: function (err) {
        if (images[current].attempt <= 0) {
          final++;
          error(err, filename);
          return;
        }

        setTimeout(function () {
          if (debug) console.log(filename, 'retry ' + images[current].attempt);
          dlImg(current, success, error);
          images[current].attempt--;
        }, 2000);
      },
    });
  }

  function next() {
    $download.html(' ' + final + '/' + total);
    if (debug) console.log(final, current);

    if (final < current) return;
    final < total ? addZip() : genZip();
  }

  function addZip() {
    var max = current + threading;
    if (max > total) max = total;

    for (current; current < max; current++) {
      if (debug) console.log(images[current].url, 'download');
      dlImg(
        current,
        function (response, filename) {
          zip.file(filename, response.response);

          if (debug) console.log(filename, 'success');
          next();
        },
        function (err, filename) {
          hasErr = true;
          zip.file(filename + '_error.txt', err.statusText + '\r\n' + err.finalUrl);
          $download.css('backgroundColor', '#FF7F7F');

          if (debug) console.log(filename, 'error');
          next();
        }
      );
    }
    if (debug) console.log(current, 'current');
  }

  var gallery = JSON.parse(JSON.stringify(window._gallery));
  if (debug) console.log(gallery, 'gallery');
  if (!gallery) return;

  var zip = new JSZip(),
    current = 0,
    final = 0,
    total = gallery.num_pages,
    images = gallery.images.pages,
    hasErr = false,
    $_download = $('#download-torrent, #download'),
    $download,
    $config,
    doc = document,
    $win = $(window),
    comicId = gallery.id;

  if (!$_download.length) return;
  GM_config.open();

  window.URL = window.URL || window.webkitURL;

  $download = $_download.clone();
  $download.removeAttr('id');
  $download.removeClass('btn-disabled');
  $download.attr('href', '#download');
  $download.find('.top').html('No login required
No sign up required');
  $download.append(' as ' + outputExt.toUpperCase() + '');

  $download.insertAfter($_download);
  $download.before('\n');

  $download.css('backgroundColor', 'cornflowerblue').one('click', function (e) {
    e.preventDefault();
    if (debug) console.time('nHentai');
    if (debug) console.log({ outputExt, outputName, threading });

    if (threading < 1) threading = 1;
    if (threading > 32) threading = 32;

    $win.on('beforeunload', function () {
      return 'Progress is running...';
    });

    $download.html(' Waiting...').css('backgroundColor', 'orange');

    images = images.map(function (img, index) {
      return {
        url:
          'https://i.nhentai.net/galleries/' +
          gallery.media_id +
          '/' +
          (index + 1) +
          '.' +
          { j: 'jpg', p: 'png', g: 'gif' }[img.t],
        attempt: 3,
      };
    });
    if (debug) console.log(images, 'images');

    addZip();
  });

  $("#nHentaiDlConfig").toggle();
  $config = $_download.clone();
  $config.removeAttr('id');
  $config.removeClass('btn-disabled');
  $config.attr('href', 'javascript:void(0);');
  $config.css('min-width', '12px');
  $config.html('');

  $config.insertAfter($download);
  $config.before('\n');
  $config.click(function() {
    $("#nHentaiDlConfig").toggle("fast");
  });

  if (GM_config.get('hideTorrentBtn') == true) {
    $_download.hide();
  }
})(jQuery, unsafeWindow);
<commit_msg>Use a picture to allow detection of error from images viewers<commit_after>// ==UserScript==
// @name            nHentai Downloader
// @name:vi         nHentai Downloader
// @namespace       http://devs.forumvi.com
// @description     Download manga on nHentai.
// @description:vi  Tải truyện tranh tại NhệnTái.
// @version         2.0.0
// @icon            http://i.imgur.com/FAsQ4vZ.png
// @author          Zzbaivong
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           http://nhentai.net/g/*
// @match           https://nhentai.net/g/*
// @require         https://code.jquery.com/jquery-3.5.1.min.js
// @require         https://unpkg.com/jszip@3.1.5/dist/jszip.min.js
// @require         https://greasyfork.org/scripts/28536-gm-config/code/GM_config.js?version=184529
// @require         https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?v=a834d46
// @require         https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.0.2/dist/ponyfill.min.js
// @require         https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js
// @noframes
// @connect         self
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           GM.xmlHttpRequest
// @grant           GM_xmlhttpRequest
// @grant           unsafeWindow
// @grant           GM_getValue
// @grant           GM_setValue
// @grant           GM.getValue
// @grant           GM.setValue
// ==/UserScript==

(function ($, window) {
  'use strict';

  var configFrame = document.createElement('div');
  $('#info-block').append(configFrame);

  GM_config.init({
    id: 'nHentaiDlConfig',
    title: 'Downloader Settings',
    fields: {
      outputExt: {
        options: ['cbz', 'zip'],
        label: 'Export as',
        type: 'radio',
        default: 'cbz',
      },
      outputName: {
        label: 'Filename',
        type: 'select',
        options: ['pretty', 'english', 'japanese'],
        default: 'pretty',
      },
      threading: {
        label: 'Max. conn. number',
        type: 'unsigned int',
        min: 1,
        max: 32,
        default: 4,
      },
      hideTorrentBtn: {
        label: 'Hide the download torrent button',
        type: 'checkbox',
        default: false,
      }
    },
    frame: configFrame,
    events: {
      save: function () {
        outputExt = GM_config.get('outputExt');
        outputName = GM_config.get('outputName');
        threading = GM_config.get('threading');

        $download.find('span').text(' as ' + outputExt.toUpperCase());

        if (GM_config.get('hideTorrentBtn') == true) {
          $_download.hide();
        } else {
          $_download.show();
        }

        var $saveBtn = $('#nHentaiDlConfig_saveBtn');
        $saveBtn.prop('disabled', true).addClass('saved').text('Saved!');

        setTimeout(function () {
          $saveBtn.prop('disabled', false).removeClass('saved').text('Save');
        }, 1500);
      },
    },
    css:
      '#nHentaiDlConfig{width:100%!important;position:initial!important;padding:10px!important;background:#0d0d0d;border:1px solid #313131!important;border-radius:5px;text-align:left}#nHentaiDlConfig *{font-family:"Noto Sans",sans-serif}#nHentaiDlConfig .config_header{text-align:left;font-size:17px;font-weight:700;margin-bottom:20px;color:#999}#nHentaiDlConfig .reset_holder{float:left;height:30px;line-height:30px}#nHentaiDlConfig .reset{color:#4d4d4d;text-align:left}#nHentaiDlConfig .saveclose_buttons{margin:0;padding:4px;min-width:100px;height:30px;line-height:14px;border-radius:2px;border:1px solid;cursor:pointer}#nHentaiDlConfig .saveclose_buttons.saved{background:#ffeb3b;border:1px solid #ffc107}#nHentaiDlConfig #nHentaiDlConfig_closeBtn{display:none}#nHentaiDlConfig_buttons_holder{margin-top:20px;border-top:1px dashed #4d4d4d;padding-top:11px}#nHentaiDlConfig .config_var::after{clear:both;content:"";display:block}#nHentaiDlConfig .config_var{position:relative}#nHentaiDlConfig .field_label{font-size:14px;height:26px;line-height:26px;margin:0;padding:0 10px 0 0;width:60%;display:block;float:left}#nHentaiDlConfig .config_var>[type=text],#nHentaiDlConfig .config_var>div,#nHentaiDlConfig .config_var>select,#nHentaiDlConfig .config_var>textarea{width:40%;border-radius:0;display:block;height:26px;line-height:26px;padding:0 10px;float:left}#nHentaiDlConfig .config_var>textarea{height:auto;line-height:14px;padding:10px;min-height:5em}#nHentaiDlConfig .config_var>select{background:#4d4d4d;color:#d9d9d9;padding:0}#nHentaiDlConfig .config_var>select:hover{background:#666}#nHentaiDlConfig .config_var>select:focus{outline:0 none}#nHentaiDlConfig .config_var>div>label{display:inline-block;vertical-align:top;margin-right:5px}#nHentaiDlConfig .config_var>#nHentaiDlConfig_field_outputName{width:150px;text-transform:capitalize}#nHentaiDlConfig .config_var>#nHentaiDlConfig_field_threading{width:70px}#nHentaiDlConfig .config_var>div{padding:0}#nHentaiDlConfig_field_outputExt{text-transform:uppercase}#nHentaiDlConfig_field_outputExt [value=cbz]{margin-right:20px!important}',
  });

  /**
   * Output extension
   * @type {'cbz'|'zip'}
   *
   * Tips: Convert .zip to .cbz
   * Windows
   * $ ren *.zip *.cbz
   * Linux
   * $ rename 's/\.zip$/\.cbz/' *.zip
   */
  var outputExt = GM_config.get('outputExt') || 'cbz';

  /**
   * File name
   * @type {'pretty'|'english'|'japanese'}
   */
  var outputName = GM_config.get('outputName') || 'pretty';

  /**
   * Multithreading
   * @type {Number} [1 -> 32]
   */
  var threading = GM_config.get('threading') || 4;

  /**
   * Logging
   * @type {Boolean}
   */
  var debug = false;

  function end() {
    $win.off('beforeunload');

    if (debug) console.timeEnd('nHentai');
  }

  function getInfo() {
    var info = '',
      tags = [],
      artists = [],
      groups = [],
      parodies = [],
      characters = [],
      categories = [],
      languages = [];

    if (gallery.title.english) info += gallery.title.english + '\r\n';
    if (gallery.title.japanese) info += gallery.title.japanese + '\r\n';
    if (gallery.title.pretty) info += gallery.title.pretty + '\r\n';
    info += '#' + gallery.id + '\r\n';

    if (gallery.tags) {
      for (const tag of gallery.tags) {
        if (tag.type === 'tag') tags.push(tag.name);
        if (tag.type === 'artist') artists.push(tag.name);
        if (tag.type === 'category') categories.push(tag.name);
        if (tag.type === 'group') groups.push(tag.name);
        if (tag.type === 'parody') parodies.push(tag.name);
        if (tag.type === 'character') characters.push(tag.name);
        if (tag.type === 'language') languages.push(tag.name);
      }
    }
    if (tags.length) info += '\r\n' + 'Tags: ' + tags.join(', ');
    if (categories.length) info += '\r\n' + 'Categories: ' + categories.join(', ');
    if (groups.length) info += '\r\n' + 'Groups: ' + groups.join(', ');
    if (parodies.length) info += '\r\n' + 'Parodies: ' + parodies.join(', ');
    if (characters.length) info += '\r\n' + 'Characters: ' + characters.join(', ');
    if (languages.length) info += '\r\n' + 'Languages: ' + languages.join(', ');

    info += '\r\n\r\n' + 'Pages: ' + total;
    info += '\r\n' + 'Uploaded at: ' + new Date(gallery.upload_date * 1000).toLocaleString() + '\r\n';

    if (debug) console.log(info);
    return info;
  }

  function genZip() {
    zip.file('info.txt', getInfo());

    zip
      .generateAsync(
        {
          type: 'blob',
          compression: 'STORE',
          streamFiles: true // Less memory but less compatibility, https://stuk.github.io/jszip/documentation/api_jszip/generate_async.html#streamfiles-option
        },
        function updateCallback(metadata) {
          $download.html(' ' + metadata.percent.toFixed(2) + ' %');
        }
      )
      .then(
        function (blob) {
          var filename = gallery.title[outputName] || gallery.title["english"]; // e.g. #321311
          var zipName = filename.replace(/\s+/g, '-') + '.' + comicId + '.' + outputExt;
          zipName.replace(/・/g, '·'); // compatibility for MangaMeeya

          $download
            .html(' Complete')
            .css('backgroundColor', hasErr ? 'red' : 'green')
            .attr({
              href: 'javascript:void(0);',
              download: zipName,
            });

          const fileStream = streamSaver.createWriteStream(zipName, {
            size: blob.size
          })
          const readableStream = blob.stream()

          window.FSwriter = fileStream.getWriter()
          const reader = readableStream.getReader()
          const pump = () => reader.read()
            .then(res => res.done
              ? FSwriter.close()
              : FSwriter.write(res.value).then(pump))
          pump(); // Firefox does not support pipeTo() yet.

          doc.title = '[⇓] ' + filename;
          if (debug) console.log('COMPLETE');
          end();
        },
        function (reason) {
          $download.html(' Fail').css('backgroundColor', 'red');

          doc.title = '[x] ' + gallery.title[outputName];
          if (debug) console.error(reason, 'ERROR');
          end();
        }
      );
  }

  function dlImg(current, success, error) {
    var url = images[current].url,
      filename = url.replace(/.*\//g, '');

    filename = ('0000' + filename).slice(-8);
    if (debug) console.log(filename, 'progress');

    GM.xmlHttpRequest({
      method: 'GET',
      url: url,
      responseType: 'arraybuffer',
      onload: function (response) {
        final++;
        success(response, filename);
      },
      onerror: function (err) {
        if (images[current].attempt <= 0) {
          final++;
          error(err, filename);
          return;
        }

        setTimeout(function () {
          if (debug) console.log(filename, 'retry ' + images[current].attempt);
          dlImg(current, success, error);
          images[current].attempt--;
        }, 2000);
      },
    });
  }

  function next() {
    $download.html(' ' + final + '/' + total);
    if (debug) console.log(final, current);

    if (final < current) return;
    final < total ? addZip() : genZip();
  }

  function addZip() {
    var max = current + threading;
    if (max > total) max = total;

    for (current; current < max; current++) {
      if (debug) console.log(images[current].url, 'download');
      dlImg(
        current,
        function (response, filename) {
          zip.file(filename, response.response);

          if (debug) console.log(filename, 'success');
          next();
        },
        function (err, filename) {
          hasErr = true;
          //zip.file(filename + '_error.txt', err.statusText + '\r\n' + err.finalUrl);
          zip.file(filename + '_' + comicId + '_error.gif', 'R0lGODdhBQAFAIACAAAAAP/eACwAAAAABQAFAAACCIwPkWerClIBADs=', {
            base64: true
          });
          $download.css('backgroundColor', '#FF7F7F');

          if (debug) console.log(filename, 'error');
          next();
        }
      );
    }
    if (debug) console.log(current, 'current');
  }

  var gallery = JSON.parse(JSON.stringify(window._gallery));
  if (debug) console.log(gallery, 'gallery');
  if (!gallery) return;

  var zip = new JSZip(),
    current = 0,
    final = 0,
    total = gallery.num_pages,
    images = gallery.images.pages,
    hasErr = false,
    $_download = $('#download-torrent, #download'),
    $download,
    $config,
    doc = document,
    $win = $(window),
    comicId = gallery.id;

  if (!$_download.length) return;
  GM_config.open();

  window.URL = window.URL || window.webkitURL;

  $download = $_download.clone();
  $download.removeAttr('id');
  $download.removeClass('btn-disabled');
  $download.attr('href', '#download');
  $download.find('.top').html('No login required
No sign up required');
  $download.append(' as ' + outputExt.toUpperCase() + '');

  $download.insertAfter($_download);
  $download.before('\n');

  $download.css('backgroundColor', 'cornflowerblue').one('click', function (e) {
    e.preventDefault();
    if (debug) console.time('nHentai');
    if (debug) console.log({ outputExt, outputName, threading });

    if (threading < 1) threading = 1;
    if (threading > 32) threading = 32;

    $win.on('beforeunload', function () {
      return 'Progress is running...';
    });

    $download.html(' Waiting...').css('backgroundColor', 'orange');

    images = images.map(function (img, index) {
      return {
        url:
          'https://i.nhentai.net/galleries/' +
          gallery.media_id +
          '/' +
          (index + 1) +
          '.' +
          { j: 'jpg', p: 'png', g: 'gif' }[img.t],
        attempt: 3,
      };
    });
    if (debug) console.log(images, 'images');

    addZip();
  });

  $("#nHentaiDlConfig").toggle();
  $config = $_download.clone();
  $config.removeAttr('id');
  $config.removeClass('btn-disabled');
  $config.attr('href', 'javascript:void(0);');
  $config.css('min-width', '12px');
  $config.html('');

  $config.insertAfter($download);
  $config.before('\n');
  $config.click(function() {
    $("#nHentaiDlConfig").toggle("fast");
  });

  if (GM_config.get('hideTorrentBtn') == true) {
    $_download.hide();
  }
})(jQuery, unsafeWindow);
<|endoftext|>


<commit_before>// ReSharper disable UnusedParameter
// ReSharper disable InconsistentNaming
(function (testFns) {
    "use strict";

    /*********************************************************
    * Breeze configuration and module setup
    *********************************************************/
    var ajaxAdapter = breeze.config.getAdapterInstance('ajax');
    var ajaxSpy, em;

    // Classes we'll need from the breeze namespaces
    var EntityQuery = breeze.EntityQuery;
    var handleFail = testFns.handleFail;

    // Target the Todos service
    var serviceName = testFns.todosServiceName;
    var newEm = testFns.newEmFactory(serviceName);

    /*=========== SaveQueuing Module ===================*/
    var moduleOptions = {
        setup: function () {
            ajaxSpy = sinon.spy(ajaxAdapter, 'ajax');
            testFns.populateMetadataStore(newEm);
            em = newEm();
            em.enableSaveQueuing(true); // <--
        },
        teardown: function () {
            // reset Todos db and adapter after each test because
            // we're messing them up
            ajaxSpy.restore();
            testFns.teardown_todosReset();
        }
    };
    module("saveQueuing", moduleOptions);

    /*********************************************************
    * overwrites value in added entity that is changed while saving
    * when that save completes before the modified entity is saved
    * This is standard behavior, w/ or w/o saveQueuing
    *********************************************************/
    asyncTest("save overwrites value of entity modified while saving", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges()
          .then(success).catch(handleFail).finally(start);

        // make change while save is in progress
        todo.setProperty('Description', 'Test mod');

        function success(data) {
            var aspect = todo.entityAspect;
            equal(aspect.entityState.name, 'Unchanged',
                "added Todo was saved and now is Unchanged");
            equal(todo.getProperty('Description'), 'Test',
                "description has the saved (not the modified) value");
        }
    });

    /*********************************************************
    * saves value in added entity that is changed while save in progress
    * then saved again before first save returns.
    * This test would fail in the 2nd assert in saveQueuing v.1; works in v.2
    *********************************************************/
    asyncTest("saves modified value of added entity when saved before 1st save completes", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges().catch(handleFail);

        // make change while save is in progress
        todo.setProperty('Description', 'Test mod');

        // save immediately, before first save response
        return em.saveChanges()
          .then(success).catch(handleFail).finally(start);

        // After second save
        function success(data) {
            var aspect = todo.entityAspect;
            equal(aspect.entityState.name, 'Unchanged',
                "modified Todo was saved and now is Unchanged");
            equal(todo.getProperty('Description'), 'Test mod',
                "description has the modified value, changed between saves");
        }
    });

    /*********************************************************
    * saves value in modified entity that is changed while save in progress
    * then saved again before first save returns.
    * This test would fail in the 2nd assert in saveQueuing v.1; works in v.2
    *********************************************************/
    asyncTest("saves modified value of modified entity when saved before 1st save completes", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges()
          .then(modAndSave)
          .catch(handleFail).finally(start);

        function modAndSave(){
            // modify the existing Todo
            todo.setProperty('Description', 'Test mod 1');

            // save the first mod
            em.saveChanges().catch(handleFail);

            // modify it again while the save is in progress
            todo.setProperty('Description', 'Test mod 2');

            // save immediately, before first save response
            return em.saveChanges()
              .then(success).catch(handleFail);

            // After second save
            function success(data) {
                var aspect = todo.entityAspect;
                equal(aspect.entityState.name, 'Unchanged',
                    "double modified Todo was saved and now is Unchanged");
                equal(todo.getProperty('Description'), 'Test mod 2',
                    "description has the 2nd modified value");
            }
        }
    });
    /*********************************************************
    * second save w/ savequeuing does not resave
    *********************************************************/
    asyncTest("Second save w/ savequeuing does not resave", function () {
        expect(4);

        var description = 'Test'+testFns.newGuid().toString();
        description=description.substr(0,30); // max allowed
        em.createEntity('TodoItem', { Description: description });

        var save1 = em.saveChanges();
        var save2 = em.saveChanges();

        Q.all([save1, save2])
         .then(requery)
         .then(success)
         .catch(handleFail)
         .finally(start);

        function requery(results){
            equal(ajaxSpy.callCount, 1,
                'ajax should only have been called once .. for a single save');

            var urlCalled = ajaxSpy.args[0][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "should have called SaveChanges; was "+urlCalled);

            return EntityQuery.from('Todos')
                .where('Description', 'eq', description)
                .using(em).execute();
        }

        function success(data) {
            equal(data.results.length, 1,
                "Re-queried exactly one TodoItem w/ that description.");
            equal(em.getEntities().length, 1, "Only one entity in cache");
        }
    });

    /*********************************************************
    * Two [add+save] events are in two separate saves
    * The saves are in order
    *********************************************************/
    asyncTest("Two [add+save] events are in two separate saves", function () {
        expect(6);

        var todo1 = em.createEntity('TodoItem', { Description: "DeleteMe 1" });

        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(handleFail);

        var todo2 = em.createEntity('TodoItem', { Description: "DeleteMe 2" });

        var save2 = em.saveChanges()
            .then(secondSaveSucceeded)
            .catch(handleFail);

        equal(em.getChanges().length, 2, "two pending changes while first save is in flight");

        Q.all([save1, save2])
            .then(bothSucceeded)
            .catch(handleFail)
            .finally(start);

        function firstSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "1st save should save a single Todo");
            equal(saveResult.entities[0].getProperty('Description'),
                    todo1.getProperty('Description'),
                    "1st save should be 'todo1'");
            return saveResult;
        }

        function secondSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "2nd save should save a single Todo");
            equal(saveResult.entities[0].Description(), todo2.Description(),
                    "2nd save should be 'todo2'");
            return saveResult;
        }
        function bothSucceeded (results) {
            ok(!em.hasChanges(), "should have no more pending changes");
        }
    });

    /*********************************************************
    * Queued saves will be combined but will not double-save
    * Here the 2nd and 3rd are combined while #1 is in-flight
    *********************************************************/
    asyncTest("Queued saves will be combined", function () {
        expect(7);

        var description = 'Test'+testFns.newGuid().toString();
        description=description.substr(0,28); // max allowed is 30

        em.createEntity('TodoItem', { Description: description + '-1' });
        var save1 = em.saveChanges();

        em.createEntity('TodoItem', { Description: description + '-2' });
        var save2 = em.saveChanges();

        em.createEntity('TodoItem', { Description: description + '-3' });
        var save3 = em.saveChanges();

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
            .then(requery)
            .then(confirm)
            .catch(handleFail)
            .finally(start);

        function requery(results){
            var entities = em.getEntities();
            equal(entities.length, 3, 'should have exactly 3 in cache');
            equal(em.getChanges().length, 0, "should have no more pending changes");

            equal(ajaxSpy.callCount, 2,
                'ajax should have been called twice');

            var urlCalled = ajaxSpy.args[0][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "1st time should have been SaveChanges; was "+urlCalled);

            urlCalled = ajaxSpy.args[1][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "2nd time should have been SaveChanges; was "+urlCalled);

            return EntityQuery.from('Todos')
                .where('Description', 'startsWith', description)
                .using(em).execute();
        }

        function confirm(data) {
            var results = data.results;
            equal(results.length, 3, 'should have requeried 3 entities');
        }
    });

    /*********************************************************
    * Failure in a middle save aborts the rest
    *********************************************************/
    asyncTest("Failure in a middle save aborts the rest", function () {
        expect(9);

        var todo1 = em.createEntity('TodoItem', { Description: "DeleteMe 1" });
        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(handleFail);

        // fake a change to non-existent entity
        // save should fail
        var todo2 = em.createEntity('TodoItem', {
            Id: 100000, // not a real id
            Description: "DeleteMe 2"
        }, breeze.EntityState.Modified);

        var save2 = em.saveChanges()
            .then(secondSaveSucceeded)
            .catch(secondSaveFailed);

        var todo3 = em.createEntity('TodoItem', { Description: "DeleteMe 3" });
        var save3 = em.saveChanges()
        .then(thirdSaveSucceeded)
        .catch(thirdSaveFailed);

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
            .then(postSave, postSave)
            .finally(start);

        function firstSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "1st save should save a single Todo");
            equal(saveResult.entities[0].Description(), todo1.Description(),
                    "1st save should be 'todo1'");
            return saveResult;
        }

        function secondSaveSucceeded(saveResult) {
            ok(false, "the 2nd save should have failed");
        }
        function secondSaveFailed(error) {
            ok(true,
                "the 2nd save should have failed, the error was '{0}'"
                .format(error.message));
        }
        function thirdSaveSucceeded(saveResult) {
            ok(false, "the 3rd save should have been aborted");
        }
        function thirdSaveFailed(error) {
            var expected = /queued save failed/i;
            ok(expected.test(error.message),
                "the 3rd save should have aborted with "+
                "queued save termination error: '{0}'"
                .format(error.message));
        }
        function postSave(results) {
            ok(typeof results[1] === 'undefined' &&
                typeof results[2] === 'undefined',
                "the 2nd and 3rd save promise results should be undefined " +
                "because failed saves were caught and errors not re-thrown");

            equal(todo1.entityAspect.entityState.name, "Unchanged",
                "'todo1' was saved and is in the 'Unchanged' state.");

            var changes = em.getChanges();
            ok(changes.indexOf(todo2) > -1,
                "todo2 is among the pending changes");
            ok(changes.indexOf(todo3) > -1,
                "todo3 is among the pending changes");
        }
    });

    /*********************************************************
    * Failure in first save aborts the rest
    *********************************************************/
    asyncTest("Failure in first save aborts the rest", function () {
        expect(6);

        // fake a change to non-existent entity
        // save should fail
        em.createEntity('TodoItem', {
            Id: 100000, // not a real id
            Description: "DeleteMe 1"
        }, breeze.EntityState.Modified);

        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(firstSaveFailed);

        em.createEntity('TodoItem', { Description: "DeleteMe 2" });
        var save2 = em.saveChanges()
            .then(laterSaveSucceeded)
            .catch(laterSaveFailed);

        em.createEntity('TodoItem', { Description: "DeleteMe 3" });
        var save3 = em.saveChanges()
            .then(laterSaveSucceeded)
            .catch(laterSaveFailed);

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
         .then(allSucceeded)
         .finally(start);

        function firstSaveSucceeded(saveResult) {
            ok(false, "the 1st save should have failed");
        }
        function firstSaveFailed(error) {
            ok(true,
               "the 1st save should have failed, the error was '{0}'"
               .format(error.message));
        }
        function laterSaveSucceeded(saveResult) {
            ok(false, "any save after the 1st should have been aborted");
        }
        function laterSaveFailed(error) {
            var expected = /queued save failed/i;
            ok(expected.test(error.message),
               "any save after the 1st should have aborted with " +
               "queued save termination error: '{0}'"
               .format(error.message));
        }

        function allSucceeded(results) {
            ok(results.reduce(
                    function(p, c) { return p && typeof c === 'undefined'; },
                    true),
                "all save promise results should be undefined because " +
                "failed saves were caught and errors not re-thrown");

            equal(em.getChanges().length, 3,
                "all three entities should still have pending changes");
        }
    });

})(docCode.testFns);<commit_msg>Updated DocCode.saveQueuingTests in response to SO See http://stackoverflow.com/questions/27106389/multiple-fast-saves-on-the-same-entity-with-breeze/27162405?noredirect=1#comment42822684_27162405<commit_after>// ReSharper disable UnusedParameter
// ReSharper disable InconsistentNaming
(function (testFns) {
    "use strict";

    /*********************************************************
    * Breeze configuration and module setup
    *********************************************************/
    var ajaxAdapter = breeze.config.getAdapterInstance('ajax');
    var ajaxSpy, em;

    // Classes we'll need from the breeze namespaces
    var EntityQuery = breeze.EntityQuery;
    var handleFail = testFns.handleFail;

    // Target the Todos service
    var serviceName = testFns.todosServiceName;
    var newEm = testFns.newEmFactory(serviceName);

    /*=========== SaveQueuing Module ===================*/
    var moduleOptions = {
        setup: function () {
            ajaxSpy = sinon.spy(ajaxAdapter, 'ajax');
            testFns.populateMetadataStore(newEm);
            em = newEm();
            em.enableSaveQueuing(true); // <--
        },
        teardown: function () {
            // reset Todos db and adapter after each test because
            // we're messing them up
            ajaxSpy.restore();
            testFns.teardown_todosReset();
        }
    };
    module("saveQueuing", moduleOptions);

    /*********************************************************
    * overwrites same value in added entity that is changed while saving
    * when that save completes before the modified entity is saved
    * This is standard behavior, w/ or w/o saveQueuing
    *********************************************************/
    asyncTest("the save response overwrites changes to entity made during save", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges()
          .then(success).catch(handleFail).finally(start);

        // make change while save is in progress
        todo.setProperty('Description', 'Test mod');

        function success(data) {
            var aspect = todo.entityAspect;
            equal(aspect.entityState.name, 'Unchanged',
                "added Todo was saved and now is Unchanged");
            equal(todo.getProperty('Description'), 'Test',
                "description has the saved (not the modified) value");
        }
    });

    /*********************************************************
    * Cannot delete an ADDED entity while it's being saved
    * This is standard behavior, w/ or w/o saveQueuing,
    * although the exception will be thrown when setDeleted called
    * in Breeze v.1.5.2
    *********************************************************/
    asyncTest("cannot delete an ADDED entity while it's being saved", function () {
        expect(1);

        var startCalled = false;
        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges()
          .then(success).catch(failedDuringPostSave)
          .finally(function(){
            if (!startCalled) { start(); }
          });

        try {
            todo.entityAspect.setDeleted();
        } catch(e){
            ok(true, 'setDeleted() threw exception: '+e.message);
            startCalled = true;
            start();
        }

        function success(data) {
            ok(false, "save should have failed");
        }
        function failedDuringPostSave(error){
            if (error.innerError) {error = error.innerError;}
            ok(true, "post save processing should have failed; msg was "+error.message);
        }
    });

    /*********************************************************
    * saves value in added entity that is changed while save in progress
    * then saved again before first save returns.
    * This test would fail in the 2nd assert in saveQueuing v.1; works in v.2
    *********************************************************/
    asyncTest("can save modified value during save of ADDED entity", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges().catch(handleFail);

        // make change while save is in progress
        todo.setProperty('Description', 'Test mod');

        // save immediately, before first save response
        return em.saveChanges()
          .then(success).catch(handleFail).finally(start);

        // After second save
        function success(data) {
            var aspect = todo.entityAspect;
            equal(aspect.entityState.name, 'Unchanged',
                "modified Todo was saved and now is Unchanged");
            equal(todo.getProperty('Description'), 'Test mod',
                "description has the modified value, changed between saves");
        }
    });

    /*********************************************************
    * saves same value in modified entity that is changed while save in progress
    * then saved again before first save returns.
    * This test would fail in the 2nd assert in saveQueuing v.1; works in v.2
    *********************************************************/
    asyncTest("saves SAME modified value of MODIFIED entity when saved before 1st save completes", function () {
        expect(2);

        var todo = em.createEntity('TodoItem', { Description: 'Test' });
        em.saveChanges()
          .then(modAndSave)
          .catch(handleFail).finally(start);

        function modAndSave(){
            // modify the existing Todo
            todo.setProperty('Description', 'Test mod 1');

            // save the first mod
            em.saveChanges().catch(handleFail);

            // modify it again while the save is in progress
            todo.setProperty('Description', 'Test mod 2');

            // save immediately, before first save response
            return em.saveChanges()
              .then(success).catch(handleFail);

            // After second save
            function success(data) {
                var aspect = todo.entityAspect;
                equal(aspect.entityState.name, 'Unchanged',
                    "double modified Todo was saved and now is Unchanged");
                equal(todo.getProperty('Description'), 'Test mod 2',
                    "description has the value modified during save");
            }
        }
    });

    /*********************************************************
    * saves different value in modified entity that is changed while save in progress
    * then saved again before first save returns.
    * This test would fail in the 2nd assert in saveQueuing v.1; works in v.2
    *********************************************************/
    asyncTest("saves DIFFERENT modified value of MODIFIED entity when saved before 1st save completes", function () {
        expect(3);

        var todo = em.createEntity('TodoItem', {
            Description: 'Test',
            IsDone: false
        });
        em.saveChanges()
          .then(modAndSave)
          .catch(handleFail).finally(start);

        function modAndSave(){
            // modify the existing Todo
            todo.setProperty('Description', 'Test mod 1');

            // save the first mod
            em.saveChanges().catch(handleFail);

            // modify different property while the save is in progress
            todo.setProperty('IsDone', true);

            // save immediately, before first save response
            return em.saveChanges()
              .then(success).catch(handleFail);

            // After second save
            function success(data) {
                var aspect = todo.entityAspect;
                equal(aspect.entityState.name, 'Unchanged',
                    "double modified Todo was saved and now is Unchanged");
                equal(todo.getProperty('Description'), 'Test mod 1',
                    "description has the original modified value");
                equal(todo.getProperty('IsDone'), true,
                    "isDone has the value modified during save");            }
        }
    });

    /*********************************************************
    * second save w/ savequeuing does not resave an added entity
    *********************************************************/
    asyncTest("second save does not duplicate-save the added entity", function () {
        expect(4);

        var description = 'Test'+testFns.newGuid().toString();
        description=description.substr(0,30); // max allowed
        em.createEntity('TodoItem', { Description: description });

        var save1 = em.saveChanges();
        var save2 = em.saveChanges();

        Q.all([save1, save2])
         .then(requery)
         .then(success)
         .catch(handleFail)
         .finally(start);

        function requery(results){
            equal(ajaxSpy.callCount, 1,
                'ajax should only have been called once .. for a single save');

            var urlCalled = ajaxSpy.args[0][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "should have called SaveChanges; was "+urlCalled);

            return EntityQuery.from('Todos')
                .where('Description', 'eq', description)
                .using(em).execute();
        }

        function success(data) {
            equal(data.results.length, 1,
                "Re-queried exactly one TodoItem w/ that description.");
            equal(em.getEntities().length, 1, "Only one entity in cache");
        }
    });

    /*********************************************************
    * Two [add+save] events are in two separate saves
    *********************************************************/
    asyncTest("Two [add+save] events are in two separate saves", function () {
        expect(6);

        var todo1 = em.createEntity('TodoItem', { Description: "DeleteMe 1" });

        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(handleFail);

        var todo2 = em.createEntity('TodoItem', { Description: "DeleteMe 2" });

        var save2 = em.saveChanges()
            .then(secondSaveSucceeded)
            .catch(handleFail);

        equal(em.getChanges().length, 2, "two pending changes while first save is in flight");

        Q.all([save1, save2])
            .then(bothSucceeded)
            .catch(handleFail)
            .finally(start);

        function firstSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "1st save should save a single Todo");
            equal(saveResult.entities[0].getProperty('Description'),
                    todo1.getProperty('Description'),
                    "1st save should be 'todo1'");
            return saveResult;
        }

        function secondSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "2nd save should save a single Todo");
            equal(saveResult.entities[0].Description(), todo2.Description(),
                    "2nd save should be 'todo2'");
            return saveResult;
        }
        function bothSucceeded (results) {
            ok(!em.hasChanges(), "should have no more pending changes");
        }
    });

    /*********************************************************
    * Queued saves will be combined but will not double-save
    * Here the 2nd and 3rd are combined while #1 is in-flight
    *********************************************************/
    asyncTest("Queued saves will be combined", function () {
        expect(7);

        var description = 'Test'+testFns.newGuid().toString();
        description=description.substr(0,28); // max allowed is 30

        em.createEntity('TodoItem', { Description: description + '-1' });
        var save1 = em.saveChanges();

        em.createEntity('TodoItem', { Description: description + '-2' });
        var save2 = em.saveChanges();

        em.createEntity('TodoItem', { Description: description + '-3' });
        var save3 = em.saveChanges();

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
            .then(requery)
            .then(confirm)
            .catch(handleFail)
            .finally(start);

        function requery(results){
            var entities = em.getEntities();
            equal(entities.length, 3, 'should have exactly 3 in cache');
            equal(em.getChanges().length, 0, "should have no more pending changes");

            equal(ajaxSpy.callCount, 2,
                'ajax should have been called twice');

            var urlCalled = ajaxSpy.args[0][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "1st time should have been SaveChanges; was "+urlCalled);

            urlCalled = ajaxSpy.args[1][0].url;
            ok(/SaveChanges/.test(urlCalled),
                "2nd time should have been SaveChanges; was "+urlCalled);

            return EntityQuery.from('Todos')
                .where('Description', 'startsWith', description)
                .using(em).execute();
        }

        function confirm(data) {
            var results = data.results;
            equal(results.length, 3, 'should have requeried 3 entities');
        }
    });

    /*********************************************************
    * Failure in a middle save aborts the rest
    *********************************************************/
    asyncTest("Failure in a middle save aborts the rest", function () {
        expect(9);

        var todo1 = em.createEntity('TodoItem', { Description: "DeleteMe 1" });
        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(handleFail);

        // fake a change to non-existent entity
        // save should fail
        var todo2 = em.createEntity('TodoItem', {
            Id: 100000, // not a real id
            Description: "DeleteMe 2"
        }, breeze.EntityState.Modified);

        var save2 = em.saveChanges()
            .then(secondSaveSucceeded)
            .catch(secondSaveFailed);

        var todo3 = em.createEntity('TodoItem', { Description: "DeleteMe 3" });
        var save3 = em.saveChanges()
        .then(thirdSaveSucceeded)
        .catch(thirdSaveFailed);

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
            .then(postSave, postSave)
            .finally(start);

        function firstSaveSucceeded(saveResult) {
            var savedCount = saveResult.entities.length;
            equal(savedCount, 1,
                    "1st save should save a single Todo");
            equal(saveResult.entities[0].Description(), todo1.Description(),
                    "1st save should be 'todo1'");
            return saveResult;
        }

        function secondSaveSucceeded(saveResult) {
            ok(false, "the 2nd save should have failed");
        }
        function secondSaveFailed(error) {
            if (error.innerError) {error = error.innerError;}
            ok(true,
                "the 2nd save should have failed, the error was '{0}'"
                .format(error.message));
        }
        function thirdSaveSucceeded(saveResult) {
            ok(false, "the 3rd save should have been aborted");
        }
        function thirdSaveFailed(error) {
            var expectedErr = /queued save failed/i.test(error.message);
            if (error.innerError) {error = error.innerError;}
            ok(expectedErr,
                "the 3rd save should have aborted with "+
                "queued save termination error: '{0}'"
                .format(error.message));
        }
        function postSave(results) {
            ok(typeof results[1] === 'undefined' &&
                typeof results[2] === 'undefined',
                "the 2nd and 3rd save promise results should be undefined " +
                "because failed saves were caught and errors not re-thrown");

            equal(todo1.entityAspect.entityState.name, "Unchanged",
                "'todo1' was saved and is in the 'Unchanged' state.");

            var changes = em.getChanges();
            ok(changes.indexOf(todo2) > -1,
                "todo2 is among the pending changes");
            ok(changes.indexOf(todo3) > -1,
                "todo3 is among the pending changes");
        }
    });

    /*********************************************************
    * Failure in first save aborts the rest
    *********************************************************/
    asyncTest("Failure in first save aborts the rest", function () {
        expect(6);

        // fake a change to non-existent entity
        // save should fail
        em.createEntity('TodoItem', {
            Id: 100000, // not a real id
            Description: "DeleteMe 1"
        }, breeze.EntityState.Modified);

        var save1 = em.saveChanges()
            .then(firstSaveSucceeded)
            .catch(firstSaveFailed);

        em.createEntity('TodoItem', { Description: "DeleteMe 2" });
        var save2 = em.saveChanges()
            .then(laterSaveSucceeded)
            .catch(laterSaveFailed);

        em.createEntity('TodoItem', { Description: "DeleteMe 3" });
        var save3 = em.saveChanges()
            .then(laterSaveSucceeded)
            .catch(laterSaveFailed);

        equal(em.getChanges().length, 3, "three pending changes while first save is in flight");

        Q.all([save1, save2, save3])
         .then(allSucceeded)
         .finally(start);

        function firstSaveSucceeded(saveResult) {
            ok(false, "the 1st save should have failed");
        }
        function firstSaveFailed(error) {
            ok(true,
               "the 1st save should have failed, the error was '{0}'"
               .format(error.message));
        }
        function laterSaveSucceeded(saveResult) {
            ok(false, "any save after the 1st should have been aborted");
        }
        function laterSaveFailed(error) {
            var expected = /queued save failed/i;
            ok(expected.test(error.message),
               "any save after the 1st should have aborted with " +
               "queued save termination error: '{0}'"
               .format(error.message));
        }

        function allSucceeded(results) {
            ok(results.reduce(
                    function(p, c) { return p && typeof c === 'undefined'; },
                    true),
                "all save promise results should be undefined because " +
                "failed saves were caught and errors not re-thrown");

            equal(em.getChanges().length, 3,
                "all three entities should still have pending changes");
        }
    });

})(docCode.testFns);<|endoftext|>


<commit_before>'use strict'

/**
 * adonis-ace
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
*/

const commander = require('commander')
const Command = require('../Command')
const CatLog = require('cat-log')
const logger = new CatLog('adonis:ace')
const CE = require('../Exceptions')
const _ = require('lodash')
const Ioc = require('adonis-fold').Ioc

class ConsoleKernel {

  constructor () {
    this.commands = {}
  }

  /**
   * adds a new command to the commands list
   *
   * @param  {String|Object} command
   *
   * @public
   */
  add (command) {
    const commandInstance = this.resolveCommand(command)
    this.commands[commandInstance.command._name] = commandInstance
  }

  /**
   * registers an array of commands to the
   * commands list.
   *
   * @param  {Array} command
   *
   * @public
   */
  register (commands) {
    _.each(commands, (command) => this.add(command))
  }

  /**
   * resolves a command from the Ioc container and
   * makes sure it is instance of Command.
   *
   * @param  {String|Object}       command
   *
   * @public
   */
  resolveCommand (command) {
    logger.verbose('resolving command %s', command)
    const commandInstance = Ioc.make(command)
    if (commandInstance instanceof Command === false) {
      throw new CE.InvalidArgumentException(`${command} must be an instance of Base Command`)
    }
    commandInstance.initialize()
    return commandInstance
  }

  /**
   * invoke command.
   *
   * @param  {Object} packageFile
   *
   * @public
   */
  invoke (packageFile) {
    const commandName = process.argv.slice(2)
    if (!commandName || !commandName[0]) {
      return commander.outputHelp()
    }

    commander.on('*', function (args) {
      console.log(`${args[0]} is not registered with ace`)
      process.exit(1)
    })

    commander.version(packageFile.version).parse(process.argv)
  }

  /**
   * calls a given command and returns it's output back
   *
   * @param  {String} command
   * @param  {Object} args
   * @param  {Object} options
   * @return {Mixed}
   *
   * @public
   */
  call (command, args, options) {
    const commandInstance = this.commands[command]
    if (!commandInstance) {
      throw new CE.CommandNotFound(`${command} is not registered with ace`)
    }
    const formattedArgs = {}
    _.each(args, (value, index) => { formattedArgs[commandInstance.args[index].name] = value })
    commandInstance.setup()
    return commandInstance.handle(formattedArgs, options)
  }

}

module.exports = ConsoleKernel
<commit_msg>feat(console): add extra help on help screen<commit_after>'use strict'

/**
 * adonis-ace
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
*/

const commander = require('commander')
const Command = require('../Command')
const CatLog = require('cat-log')
const logger = new CatLog('adonis:ace')
const CE = require('../Exceptions')
const _ = require('lodash')
const Ioc = require('adonis-fold').Ioc

commander.on('--help', function () {
  console.log('  Extras:')
  console.log('')
  console.log('    [command] --help output command detail info')
  console.log('')
})

class ConsoleKernel {

  constructor () {
    this.commands = {}
  }

  /**
   * adds a new command to the commands list
   *
   * @param  {String|Object} command
   *
   * @public
   */
  add (command) {
    const commandInstance = this.resolveCommand(command)
    this.commands[commandInstance.command._name] = commandInstance
  }

  /**
   * registers an array of commands to the
   * commands list.
   *
   * @param  {Array} command
   *
   * @public
   */
  register (commands) {
    _.each(commands, (command) => this.add(command))
  }

  /**
   * resolves a command from the Ioc container and
   * makes sure it is instance of Command.
   *
   * @param  {String|Object}       command
   *
   * @public
   */
  resolveCommand (command) {
    logger.verbose('resolving command %s', command)
    const commandInstance = Ioc.make(command)
    if (commandInstance instanceof Command === false) {
      throw new CE.InvalidArgumentException(`${command} must be an instance of Base Command`)
    }
    commandInstance.initialize()
    return commandInstance
  }

  /**
   * invoke command.
   *
   * @param  {Object} packageFile
   *
   * @public
   */
  invoke (packageFile) {
    const commandName = process.argv.slice(2)
    if (!commandName || !commandName[0]) {
      return commander.outputHelp()
    }

    commander.on('*', function (args) {
      console.log(`${args[0]} is not registered with ace`)
      process.exit(1)
    })

    commander.version(packageFile.version).parse(process.argv)
  }

  /**
   * calls a given command and returns it's output back
   *
   * @param  {String} command
   * @param  {Object} args
   * @param  {Object} options
   * @return {Mixed}
   *
   * @public
   */
  call (command, args, options) {
    const commandInstance = this.commands[command]
    if (!commandInstance) {
      throw new CE.CommandNotFound(`${command} is not registered with ace`)
    }
    const formattedArgs = {}
    _.each(args, (value, index) => { formattedArgs[commandInstance.args[index].name] = value })
    commandInstance.setup()
    return commandInstance.handle(formattedArgs, options)
  }

}

module.exports = ConsoleKernel
<|endoftext|>


<commit_before>/**
 * Copyright 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see dis tribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.Text',
    dependencies: ['og.common.gadgets.manager'],
    obj: function () {
        var loading_template;
        return function (config) {
            var gadget = this, alive = og.common.id('gadget_text'), $selector = $(config.selector),
                instantiated, tash, $msg, sbar_size = og.common.util.scrollbar_size + 'px',
                cell_options = {source: config.source, col: config.col, row: config.row, format: 'CELL'/*, log: true*/},
                css_position = {position: 'absolute', top: '0', left: 0, right: 0, bottom: 0};
            gadget.alive = function () {
                return $(config.selector).length ? true : (gadget.die(), false);
            };
            gadget.load = function () {
                $selector.addClass(alive).css(css_position).html(loading_template({text: 'loading...'}));
                gadget.dataman = new og.analytics.Cell(cell_options, 'text').on('data', function (cell) {
                    if (gadget.data = cell.v) {
                        if (!instantiated)
                            $.when(og.api.text({module: 'og.analytics.text_tash'})).then(function (tmpl) {
                                tash = Handlebars.compile(tmpl);
                                instantiated = true;
                                gadget.update();
                            });
                        else if (!$msg.is(':visible')) {
                            $selector.find('.text-contents').css('marginTop', '25px');
                            $msg.slideDown().on('click', '.og-link', function () {$msg.slideUp(null, gadget.update);});
                        }
                    } else $selector.html('No text available'), instantiated = null;
                });
            };
            gadget.die = function () {
                try { gadget.dataman.kill(); } catch (error) {}
            };
            gadget.update = function () {
                if (tash) $msg = $selector.html(tash({text: gadget.data})).find('.og-message').css('right', sbar_size);
            };
            gadget.resize = function () {
                gadget.update();
            };
            if (loading_template) gadget.load();
            else og.api.text({module: 'og.analytics.loading_tash'}).pipe(function (template) {
                loading_template = Handlebars.compile(template); gadget.load();
            });
            if (!config.child) og.common.gadgets.manager.register(gadget);
        };
    }
});<commit_msg>removed log property from cell options<commit_after>/**
 * Copyright 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
 * Please see dis tribution for license.
 */
$.register_module({
    name: 'og.common.gadgets.Text',
    dependencies: ['og.common.gadgets.manager'],
    obj: function () {
        var loading_template;
        return function (config) {
            var gadget = this, alive = og.common.id('gadget_text'), $selector = $(config.selector),
                instantiated, tash, $msg, sbar_size = og.common.util.scrollbar_size + 'px',
                cell_options = {source: config.source, col: config.col, row: config.row, format: 'CELL'},
                css_position = {position: 'absolute', top: '0', left: 0, right: 0, bottom: 0};
            gadget.alive = function () {
                return $(config.selector).length ? true : (gadget.die(), false);
            };
            gadget.load = function () {
                $selector.addClass(alive).css(css_position).html(loading_template({text: 'loading...'}));
                gadget.dataman = new og.analytics.Cell(cell_options, 'text').on('data', function (cell) {
                    if (gadget.data = cell.v) {
                        if (!instantiated)
                            $.when(og.api.text({module: 'og.analytics.text_tash'})).then(function (tmpl) {
                                tash = Handlebars.compile(tmpl);
                                instantiated = true;
                                gadget.update();
                            });
                        else if (!$msg.is(':visible')) {
                            $selector.find('.text-contents').css('marginTop', '25px');
                            $msg.slideDown().on('click', '.og-link', function () {$msg.slideUp(null, gadget.update);});
                        }
                    } else $selector.html('No text available'), instantiated = null;
                });
            };
            gadget.die = function () {
                try { gadget.dataman.kill(); } catch (error) {}
            };
            gadget.update = function () {
                if (tash) $msg = $selector.html(tash({text: gadget.data})).find('.og-message').css('right', sbar_size);
            };
            gadget.resize = function () {
                gadget.update();
            };
            if (loading_template) gadget.load();
            else og.api.text({module: 'og.analytics.loading_tash'}).pipe(function (template) {
                loading_template = Handlebars.compile(template); gadget.load();
            });
            if (!config.child) og.common.gadgets.manager.register(gadget);
        };
    }
});<|endoftext|>