text: import angular from "angular";
  
  export default angular
    .module("webui.ctrls.download", [
      "ui.bootstrap",
      "webui.services.utils",
      "webui.services.rpc",
      "webui.services.rpc.helpers",
      "webui.services.alerts",
      "webui.services.settings",
      "webui.services.modals",
      "webui.services.configuration",
      "webui.services.errors"
    ])
    .controller("MainCtrl", [
      "$scope",
      "$name",
      "$enable",
      "$rpc",
      "$rpchelpers",
      "$utils",
      "$alerts",
      "$modals",
      "$fileSettings",
      "$activeInclude",
      "$waitingExclude",
      "$pageSize",
      "$getErrorStatus",
      // for document title
      "$rootScope",
      "$filter",
      function(
        scope,
        name,
        enable,
        rpc,
        rhelpers,
        utils,
        alerts,
        modals,
        fsettings,
        activeInclude,
        waitingExclude,
        pageSize,
        getErrorStatus,
        rootScope,
        filter
      ) {
        scope.name = name; // default UI name
        scope.enable = enable; // UI enable options
  
        var re_slashes = /\\/g;
        var slash = "/";
        var allStopped = [];
  
        (scope.active = []), (scope.waiting = []), (scope.stopped = []);
        scope.gstats = {};
        scope.hideLinkedMetadata = true;
        scope.propFilter = "";
  
        // pause the download
        // d: the download ctx
        scope.pause = function(d) {
          rpc.once("forcePause", [d.gid]);
        };
  
        // resume the download
        // d: the download ctx
        scope.resume = function(d) {
          rpc.once("unpause", [d.gid]);
        };
  
        scope.restart = function(d) {
          // assumes downloads which are started by URIs, not torrents.
          // the preferences are also not transferred, just simple restart
  
          rpc.once("getOption", [d.gid], function(data) {
            var prefs = data[0];
            rpc.once("getFiles", [d.gid], function(data) {
              var files = data[0];
              var uris = _.chain(files)
                .map(function(f) {
                  return f.uris;
                })
                .filter(function(uris) {
                  return uris && uris.length;
                })
                .map(function(uris) {
                  var u = _.chain(uris)
                    .map(function(u) {
                      return u.uri;
                    })
                    .uniq()
                    .value();
                  return u;
                })
                .value();
  
              if (uris.length > 0) {
                console.log("adding uris:", uris, prefs);
                scope.remove(
                  d,
                  function() {
                    rhelpers.addUris(uris, prefs);
                  },
                  true
                );
              }
            });
          });
        };
  
        scope.canRestart = function(d) {
          return ["active", "paused"].indexOf(d.status) == -1 && !d.bittorrent;
        };
  
        // remove the download,
        // put it in stopped list if active,
        // otherwise permanantly remove it
        // d: the download ctx
        scope.remove = function(d, cb, noConfirm) {
          // HACK to make sure an angular digest is not running, as only one can happen at a time, and confirm is a blocking
          // call so an rpc response can also trigger a digest call
          setTimeout(function() {
            if (
              !noConfirm &&
              !confirm(
                filter("translate")("Remove {{name}} and associated meta-data?", { name: d.name })
              )
            ) {
              return;
            }
  
            var method = "remove";
  
            if (scope.getType(d) == "stopped") method = "removeDownloadResult";
  
            if (d.followedFrom) {
              scope.remove(d.followedFrom, function() {}, true);
              d.followedFrom = null;
            }
            rpc.once(method, [d.gid], cb);
  
            var lists = [scope.active, scope.waiting, scope.stopped],
              ind = -1,
              i;
            for (var i = 0; i < lists.length; ++i) {
              var list = lists[i];
              var idx = list.indexOf(d);
              if (idx < 0) {
                continue;
              }
              list.splice(idx, 1);
              return;
            }
          }, 0);
        };
  
        // start filling in the model of active,
        // waiting and stopped download
        rpc.subscribe("tellActive", [], function(data) {
          scope.$apply(function() {
            utils.mergeMap(data[0], scope.active, scope.getCtx);
          });
        });
  
        rpc.subscribe("tellWaiting", [0, 1000], function(data) {
          scope.$apply(function() {
            utils.mergeMap(data[0], scope.waiting, scope.getCtx);
          });
        });
  
        rpc.subscribe("tellStopped", [0, 1000], function(data) {
          scope.$apply(function() {
            if (!scope.hideLinkedMetadata) {
              utils.mergeMap(data[0], scope.stopped, scope.getCtx);
              return;
            }
            utils.mergeMap(data[0], allStopped, scope.getCtx);
            var gids = {};
            _.forEach(allStopped, function(e) {
              gids[e.gid] = e;
            });
            _.forEach(scope.active, function(e) {
              gids[e.gid] = e;
            });
            _.forEach(scope.waiting, function(e) {
              gids[e.gid] = e;
            });
            scope.stopped = _.filter(allStopped, function(e) {
              if (!e.metadata || !e.followedBy || !(e.followedBy in gids)) {
                return true;
              }
              var linked = gids[e.followedBy];
              linked.followedFrom = e;
              return false;
            });
          });
        });
  
        rootScope.pageTitle = utils.getTitle();
        rpc.subscribe("getGlobalStat", [], function(data) {
          scope.$apply(function() {
            scope.gstats = data[0];
            rootScope.pageTitle = utils.getTitle(scope.gstats);
          });
        });
  
        rpc.once("getVersion", [], function(data) {
          scope.$apply(function() {
            scope.miscellaneous = data[0];
          });
        });
  
        // total number of downloads, updates dynamically as downloads are
        // stored in scope
        scope.totalDownloads = 0;
  
        // download search filter
        scope.downloadFilter = "";
        scope.downloadFilterCommitted = "";
  
        scope.onDownloadFilter = function() {
          if (scope.downloadFilterTimer) {
            clearTimeout(scope.downloadFilterTimer);
          }
          scope.downloadFilterTimer = setTimeout(function() {
            delete scope.downloadFilterTimer;
            if (scope.downloadFilterCommitted !== scope.downloadFilter) {
              scope.downloadFilterCommitted = scope.downloadFilter;
              scope.$digest();
            }
          }, 500);
        };
  
        scope.filterDownloads = function(downloads) {
          if (!scope.downloadFilterCommitted) {
            return downloads;
          }
          var filter = scope.downloadFilterCommitted
            .replace(/[{}()\[\]\\^$.?]/g, "\\$&")
            .replace(/\*/g, ".*")
            .replace(/\./g, ".");
          filter = new RegExp(filter, "i");
          return _.filter(downloads, function(d) {
            if (filter.test(d.name)) return true;
            return _.filter(d.files, function(f) {
              return filter.test(f.relpath);
            }).length;
          });
        };
  
        scope.clearFilter = function() {
          scope.downloadFilter = scope.downloadFilterCommitted = "";
        };
  
        scope.toggleStateFilters = function() {
          scope.filterSpeed = !scope.filterSpeed;
          scope.filterActive = !scope.filterActive;
          scope.filterWaiting = !scope.filterWaiting;
          scope.filterComplete = !scope.filterComplete;
          scope.filterError = !scope.filterError;
          scope.filterPaused = !scope.filterPaused;
          scope.filterRemoved = !scope.filterRemoved;
          scope.persistFilters();
        };
  
        scope.resetFilters = function() {
          scope.filterSpeed = scope.filterActive = scope.filterWaiting = scope.filterComplete = scope.filterError = scope.filterPaused = scope.filterRemoved = true;
          scope.clearFilter();
          scope.persistFilters();
        };
  
        scope.persistFilters = function() {
          var o = JSON.stringify({
            s: scope.filterSpeed,
            a: scope.filterActive,
            w: scope.filterWaiting,
            c: scope.filterComplete,
            e: scope.filterError,
            p: scope.filterPaused,
            r: scope.filterRemoved
          });
          utils.setCookie("aria2filters", o);
        };
  
        scope.loadFilters = function() {
          var o = JSON.parse(utils.getCookie("aria2filters"));
          if (!o) {
            scope.resetFilters();
            return;
          }
          scope.filterSpeed = !!o.s;
          scope.filterActive = !!o.a;
          scope.filterWaiting = !!o.w;
          scope.filterComplete = !!o.c;
          scope.filterError = !!o.e;
          scope.filterPaused = !!o.p;
          scope.filterRemoved = !!o.r;
        };
  
        scope.loadFilters();
  
        scope.toggleCollapsed = function(download) {
          if (!download.collapsed) {
            download.animCollapsed = true;
            // ng-unswitch after half a second.
            // XXX hacky way, because I'm to lazy right now to wire up proper
            // transitionend events.
            setTimeout(function() {
              scope.$apply(function() {
                download.collapsed = true;
              });
            }, 500);
            return;
          }
  
          download.collapsed = false;
          setTimeout(function() {
            scope.$apply(function() {
              download.animCollapsed = false;
            });
          }, 0);
        };
  
        // max downloads shown in one page
        scope.pageSize = pageSize;
  
        // current displayed page
        scope.currentPage = 1;
  
        // total amount of downloads returned by aria2
        scope.totalAria2Downloads = function() {
          return scope.active.length + scope.waiting.length + scope.stopped.length;
        };
  
        scope.getErrorStatus = function(errorCode) {
          return getErrorStatus(+errorCode);
        };
  
        // actual downloads used by the view
        scope.getDownloads = function() {
          var categories = [];
  
          if (scope.filterActive) {
            if (!scope.filterSpeed) {
              categories.push(
                _.filter(scope.active, function(e) {
                  return !+e.uploadSpeed && !+e.downloadSpeed;
                })
              );
            } else {
              categories.push(scope.active);
            }
          } else if (scope.filterSpeed) {
            categories.push(
              _.filter(scope.active, function(e) {
                return +e.uploadSpeed || +e.downloadSpeed;
              })
            );
          }
  
          if (scope.filterWaiting) {
            categories.push(
              _.filter(scope.waiting, function(e) {
                return e.status == "waiting";
              })
            );
          }
  
          if (scope.filterPaused) {
            categories.push(
              _.filter(scope.waiting, function(e) {
                return e.status == "paused";
              })
            );
          }
  
          if (scope.filterError) {
            categories.push(
              _.filter(scope.stopped, function(e) {
                return e.status == "error";
              })
            );
          }
  
          if (scope.filterComplete) {
            categories.push(
              _.filter(scope.stopped, function(e) {
                return e.status == "complete";
              })
            );
          }
  
          if (scope.filterRemoved) {
            categories.push(
              _.filter(scope.stopped, function(e) {
                return e.status == "removed";
              })
            );
          }
  
          var downloads = categories
            .map(function(category) {
              // sort downloads within category by completness, most completed first
              return _.sortBy(category, function(e) {
                return -(e.completedLength / e.totalLength);
              });
            })
            .reduce(function(downloads, category) {
              return downloads.concat(category);
            }, []);
  
          downloads = scope.filterDownloads(downloads);
  
          scope.totalDownloads = downloads.length;
  
          downloads = downloads.slice((scope.currentPage - 1) * scope.pageSize);
          downloads.splice(scope.pageSize);
  
          return downloads;
        };
  
        scope.hasDirectURL = function() {
          return rpc.getDirectURL() != "";
        };
  
        scope.getDirectURL = function() {
          return rpc.getDirectURL();
        };
  
        // convert the donwload form aria2 to once used by the view,
        // minor additions of some fields and checks
        scope.getCtx = function(d, ctx) {
          if (!ctx) {
            ctx = {
              dir: d.dir,
              status: d.status,
              gid: d.gid,
              followedBy: d.followedBy && d.followedBy.length == 1 ? d.followedBy[0] : null,
              followedFrom: null,
              numPieces: d.numPieces,
              connections: d.connections,
              connectionsTitle: "Connections",
              numSeeders: d.numSeeders,
              bitfield: d.bitfield,
              errorCode: d.errorCode,
              totalLength: d.totalLength,
              fmtTotalLength: utils.fmtsize(d.totalLength),
              completedLength: d.completedLength,
              fmtCompletedLength: utils.fmtsize(d.completedLength),
              uploadLength: d.uploadLength,
              fmtUploadLength: utils.fmtsize(d.uploadLength),
              pieceLength: d.pieceLength,
              fmtPieceLength: utils.fmtsize(d.pieceLength),
              downloadSpeed: d.downloadSpeed,
              fmtDownloadSpeed: utils.fmtspeed(d.downloadSpeed),
              uploadSpeed: d.uploadSpeed,
              fmtUploadSpeed: utils.fmtspeed(d.uploadSpeed),
              collapsed: true,
              animCollapsed: true,
              files: []
            };
            if (d.verifiedLength) {
              ctx.verifiedLength = d.verifiedLength;
              ctx.status = "verifying";
            }
            if (d.verifyIntegrityPending) {
              ctx.verifyIntegrityPending = d.verifyIntegrityPending;
              ctx.status = "verifyPending";
            }
          } else {
            if (ctx.gid !== d.gid) ctx.files = [];
            ctx.dir = d.dir;
            ctx.status = d.status;
            if (d.verifiedLength) ctx.status = "verifying";
            if (d.verifyIntegrityPending) ctx.status = "verifyPending";
            ctx.errorCode = d.errorCode;
            ctx.gid = d.gid;
            ctx.followedBy = d.followedBy && d.followedBy.length == 1 ? d.followedBy[0] : null;
            ctx.followedFrom = null;
            ctx.numPieces = d.numPieces;
            ctx.connections = d.connections;
            if (typeof d.numSeeders === "undefined") {
              ctx.numSeeders = "";
            } else {
              ctx.connectionsTitle = "Connections (Seeders)";
              ctx.numSeeders = " (" + d.numSeeders + ")";
            }
            ctx.bitfield = d.bitfield;
            if (ctx.totalLength !== d.totalLength) {
              ctx.totalLength = d.totalLength;
              ctx.fmtTotalLength = utils.fmtsize(d.totalLength);
            }
            if (ctx.completedLength !== d.completedLength) {
              ctx.completedLength = d.completedLength;
              ctx.fmtCompletedLength = utils.fmtsize(d.completedLength);
            }
            if (!d.verifiedLength) {
              delete ctx.verifiedLength;
            } else if (ctx.verifiedLength !== d.verifiedLength) {
              ctx.verifiedLength = d.verifiedLength;
            }
            if (!d.verifyIntegrityPending) {
              delete ctx.verifyIntegrityPending;
            } else if (ctx.verifyIntegrityPending !== d.verifyIntegrityPending) {
              ctx.verifyIntegrityPending = d.verifyIntegrityPending;
            }
            if (ctx.uploadLength !== d.uploadLength) {
              ctx.uploadLength = d.uploadLength;
              ctx.fmtUploadLength = utils.fmtsize(d.uploadLength);
            }
            if (ctx.pieceLength !== d.pieceLength) {
              ctx.pieceLength = d.pieceLength;
              ctx.fmtPieceLength = utils.fmtsize(d.pieceLength);
            }
            if (ctx.downloadSpeed !== d.downloadSpeed) {
              ctx.downloadSpeed = d.downloadSpeed;
              ctx.fmtDownloadSpeed = utils.fmtspeed(d.downloadSpeed);
            }
            if (ctx.uploadSpeed !== d.uploadSpeed) {
              ctx.uploadSpeed = d.uploadSpeed;
              ctx.fmtUploadSpeed = utils.fmtspeed(d.uploadSpeed);
            }
          }
  
          var dlName;
          var files = d.files;
          if (files) {
            var cfiles = ctx.files;
            for (var i = 0; i < files.length; ++i) {
              var cfile = cfiles[i] || (cfiles[i] = {});
              var file = files[i];
              if (file.path !== cfile.path) {
                cfile.index = +file.index;
                cfile.path = file.path;
                cfile.length = file.length;
                cfile.fmtLength = utils.fmtsize(file.length);
                cfile.relpath = file.path.replace(re_slashes, slash);
                if (!cfile.relpath) {
                  cfile.relpath = (file.uris && file.uris[0] && file.uris[0].uri) || "Unknown";
                } else if (!cfile.relpath.startsWith("[")) {
                  // METADATA
                  cfile.relpath = cfile.relpath.substr(ctx.dir.length + 1);
                }
              }
              cfile.selected = file.selected === "true";
            }
            cfiles.length = files.length;
            if (cfiles.length) {
              dlName = cfiles[0].relpath;
            }
          } else {
            delete ctx.files;
          }
  
          var btName;
          if (d.bittorrent) {
            btName = d.bittorrent.info && d.bittorrent.info.name;
            ctx.bittorrent = true;
          } else {
            delete ctx.bittorrent;
          }
  
          ctx.name = btName || dlName || "Unknown";
          ctx.metadata = ctx.name.startsWith("[METADATA]");
          if (ctx.metadata) {
            ctx.name = ctx.name.substr(10);
          }
  
          return ctx;
        };
  
        scope.hasStatus = function hasStatus(d, status) {
          if (_.isArray(status)) {
            if (status.length == 0) return false;
            return hasStatus(d, status[0]) || hasStatus(d, status.slice(1));
          } else {
            return d.status == status;
          }
        };
  
        // get time left for the download with
        // current download speed,
        // could be smarter by different heuristics
        scope.getEta = function(d) {
          return (d.totalLength - d.completedLength) / d.downloadSpeed;
        };
  
        scope.getProgressClass = function(d) {
          switch (d.status) {
            case "paused":
              return "progress-bar-info";
            case "error":
              return "progress-bar-danger";
            case "removed":
              return "progress-bar-warning";
            case "active":
              return "active";
            case "verifying":
              return "progress-bar-warning";
            case "complete":
              return "progress-bar-success";
            default:
              return "";
          }
        };
  
        // gets the progress in percentages
        scope.getProgress = function(d) {
          var percentage = 0;
          if (d.verifiedLength) percentage = (d.verifiedLength / d.totalLength) * 100 || 0;
          else percentage = (d.completedLength / d.totalLength) * 100 || 0;
          percentage = percentage.toFixed(2);
          if (!percentage) percentage = 0;
  
          return percentage;
        };
  
        // gets the upload ratio
        scope.getRatio = function(d) {
          var ratio = 0;
          ratio = d.uploadLength / d.completedLength || 0;
          ratio = ratio.toFixed(2);
          if (!ratio) ratio = 0;
  
          return ratio;
        };
  
        // gets the type for the download as classified by the aria2 rpc calls
        scope.getType = function(d) {
          var type = d.status;
          if (type == "paused") type = "waiting";
          if (["error", "removed", "complete"].indexOf(type) != -1) type = "stopped";
          return type;
        };
  
        scope.selectFiles = function(d) {
          console.log("got back files for the torrent ...");
          modals.invoke("selectFiles", d.files, function(files) {
            var indexes = "";
            _.forEach(files, function(f) {
              if (f.selected) {
                indexes += "," + f.index;
              }
            });
  
            indexes = indexes.slice(1);
            rpc.once("changeOption", [d.gid, { "select-file": indexes }], function(res) {
              console.log("changed indexes to:", indexes, res);
            });
          });
        };
  
        scope.showSettings = function(d) {
          var type = scope.getType(d),
            settings = {};
  
          rpc.once("getOption", [d.gid], function(data) {
            var vals = data[0];
  
            var sets = _.cloneDeep(fsettings);
            for (var i in sets) {
              if (type == "active" && activeInclude.indexOf(i) == -1) continue;
  
              if (type == "waiting" && waitingExclude.indexOf(i) != -1) continue;
  
              settings[i] = sets[i];
              settings[i].val = vals[i] || settings[i].val;
            }
            modals.invoke("settings", settings, d.name + " settings", "Change", function(settings) {
              var sets = {};
              for (var i in settings) {
                sets[i] = settings[i].val;
              }
  
              rpc.once("changeOption", [d.gid, sets]);
            });
          });
  
          return false;
        };
        scope.moveDown = function(d) {
          rpc.once("changePosition", [d.gid, 1, "POS_CUR"]);
        };
        scope.moveUp = function(d) {
          rpc.once("changePosition", [d.gid, -1, "POS_CUR"]);
        };
      }
    ])
    .filter("objFilter", function() {
      return function(input, filter) {
        input = input || {};
        var out = {};
  
        for (var key in input) {
          if (key.startsWith(filter)) {
            out[key] = input[key];
          }
        }
  
        return out;
      };
    }).name;
  