26 Commits

Author SHA1 Message Date
e4b0629dee Version 4.0.9 2019-09-05 09:18:08 +02:00
5c2700ca36 Tooltip improvements 2019-09-05 09:03:50 +02:00
639a582804 Add small animation when opening files 2019-09-05 08:55:11 +02:00
2d1f185fcd Disable shelf in chrome
We cannot just disable it for our downloads (reliably)
so disable it completely while we're running.
2019-09-05 07:40:17 +02:00
38735ed0ae Make progress bar a little round 2019-09-05 07:39:19 +02:00
216bc590da Do not forget about 405 - Method not allowed 2019-09-04 21:34:52 +02:00
1c10d8005a Improve PREROLL based on user feedback 2019-09-04 21:27:03 +02:00
1fcfbe5360 Adjust default widths a bit 2019-09-04 21:15:32 +02:00
8d3dda1cec Bold buttons 2019-09-04 14:56:37 +02:00
be18f667d9 Trigger the saveQueue before reload 2019-09-04 14:48:35 +02:00
027b2c4fb1 Saving after preroll may have cause dupes 2019-09-04 14:48:35 +02:00
4ed92878be Update of zh_CN locale (#56) 2019-09-04 14:20:13 +02:00
a6930f309e Update ru locale 2019-09-04 14:15:06 +02:00
fdcdae0412 Use promises in content scripts 2019-09-04 14:15:06 +02:00
2c18ddaaa8 Increase shelf timeout 2019-09-04 14:15:06 +02:00
994e7ad0a6 Do not wait on downloads to finish prerollling 2019-09-04 14:15:06 +02:00
95536b36be Do not attempt to restart bg complete 2019-09-04 14:15:06 +02:00
9c159d5d24 Do not wait for the scheduler on startup 2019-09-04 14:15:06 +02:00
42ccfd5dc5 Do not abuse serverName to store browserName 2019-09-04 14:15:05 +02:00
dabf7f8a28 Prerolling and mime detection for some downloads
Only attempt this for a limited subset of downloads for now.
Related #45
2019-09-04 14:15:05 +02:00
9cac48f439 Make all tabs work again 2019-09-04 14:15:05 +02:00
ef6bc840d8 Do not trace 2019-09-04 14:15:00 +02:00
1c38ec1357 Add zh_TW locale (#62) 2019-09-04 12:03:50 +02:00
a4436bd6c8 Force Start should apply to Queued downloads
Closes #57
2019-09-03 18:18:07 +02:00
5a4b8143b2 Remove some any-types 2019-09-03 18:18:07 +02:00
00a5712427 Update of lt locale (#55) 2019-09-03 15:08:24 +02:00
36 changed files with 2923 additions and 654 deletions

View File

@ -77,3 +77,7 @@ Licensed under the Mozilla Public License 2.0.
The list itself is licensed under the Mozilla Public License 2.0.
The javascript library accessing it is licensed under the MIT license.
## whatwg-mimetype
Licensed under MIT

View File

@ -13,5 +13,6 @@
"pl": "Polski (PL) [pl]",
"pt": "Português (Brasil) [pt]",
"ru": "Русский [ru]",
"zh_CN": "简体中文 [zh_CN]"
"zh_CN": "简体中文 [zh_CN]",
"zh_TW": "正體中文 [zh_TW]"
}

View File

@ -684,7 +684,7 @@
"description": "Preferences/General"
},
"pref_hide_context": {
"message": "Nerodyti bendrųjų kontekstinio meniu elementų",
"message": "Kontekstiniame meniu nerodyti bendrųjų elementų",
"description": "Preferences/General"
},
"pref_manager_tooltip": {

View File

@ -12,7 +12,7 @@
"description": "Action: Add paused"
},
"add_download": {
"message": "добавить закачку",
"message": "Добавить закачку",
"description": "Action for adding a download"
},
"add_new": {
@ -244,7 +244,7 @@
"description": "OneClick! action; Menu text"
},
"dta_turbo_all": {
"message": "ОднимКликом! - Все вкладки",
"message": "OneClick! - Все вкладки",
"description": "Menu text"
},
"dta_turbo_image": {

File diff suppressed because it is too large Load Diff

1170
_locales/zh_TW/messages.json Normal file

File diff suppressed because it is too large Load Diff

396
data/mime.json Normal file
View File

@ -0,0 +1,396 @@
{
"e": {
"3gpp": "3gp",
"asx": "asf",
"gz": "gzip",
"heic": "heif",
"html": [
"htm",
"shtml",
"php"
],
"jar": [
"war",
"ear"
],
"jpg": [
"jpeg",
"jpe",
"jfif"
],
"js": "jsx",
"mid": [
"midi",
"kar"
],
"mkv": [
"mk3d",
"mks"
],
"mov": [
"qt",
"moov"
],
"mpg": [
"mpe",
"mpeg"
],
"pem": [
"crt",
"der"
],
"pl": "pm",
"prc": "pdb",
"ps": [
"eps",
"ai"
],
"svg": "svgz",
"tcl": "tk",
"tif": "tiff"
},
"m": {
"application/7z": "7z",
"application/7z-compressed": "7z",
"application/ai": "ps",
"application/atom": "atom",
"application/atom+xml": "atom",
"application/bz2": "bz2",
"application/bzip2": "bz2",
"application/cco": "cco",
"application/cocoa": "cco",
"application/compressed": "gz",
"application/crt": "pem",
"application/der": "pem",
"application/doc": "doc",
"application/ear": "jar",
"application/eot": "eot",
"application/eps": "ps",
"application/gz": "gz",
"application/gzip": "gz",
"application/hqx": "hqx",
"application/jar": "jar",
"application/jardiff": "jardiff",
"application/java-archive": "jar",
"application/java-archive-diff": "jardiff",
"application/java-jnlp-file": "jnlp",
"application/javascript": "js",
"application/jnlp": "jnlp",
"application/js": "js",
"application/json": "json",
"application/jsx": "js",
"application/kml": "kml",
"application/kmz": "kmz",
"application/m3u8": "m3u8",
"application/mac-binhex40": "hqx",
"application/makeself": "run",
"application/msword": "doc",
"application/odg": "odg",
"application/odp": "odp",
"application/ods": "ods",
"application/odt": "odt",
"application/pdb": "prc",
"application/pdf": "pdf",
"application/pem": "pem",
"application/perl": "pl",
"application/pilot": "prc",
"application/pl": "pl",
"application/pm": "pl",
"application/postscript": "ps",
"application/ppt": "ppt",
"application/prc": "prc",
"application/ps": "ps",
"application/rar": "rar",
"application/rar-compressed": "rar",
"application/redhat-package-manager": "rpm",
"application/rpm": "rpm",
"application/rss": "rss",
"application/rss+xml": "rss",
"application/rtf": "rtf",
"application/run": "run",
"application/sea": "sea",
"application/shockwave-flash": "swf",
"application/sit": "sit",
"application/stuffit": "sit",
"application/swf": "swf",
"application/tar": "tar",
"application/tcl": "tcl",
"application/tk": "tcl",
"application/vnd.apple.mpegurl": "m3u8",
"application/vnd.google-earth.kml+xml": "kml",
"application/vnd.google-earth.kmz": "kmz",
"application/vnd.ms-excel": "xls",
"application/vnd.ms-fontobject": "eot",
"application/vnd.ms-powerpoint": "ppt",
"application/vnd.oasis.opendocument.graphics": "odg",
"application/vnd.oasis.opendocument.presentation": "odp",
"application/vnd.oasis.opendocument.spreadsheet": "ods",
"application/vnd.oasis.opendocument.text": "odt",
"application/vnd.wap.wmlc": "wmlc",
"application/war": "jar",
"application/wmlc": "wmlc",
"application/x-7z": "7z",
"application/x-7z-compressed": "7z",
"application/x-ai": "ps",
"application/x-atom": "atom",
"application/x-atom+xml": "atom",
"application/x-bz2": "bz2",
"application/x-bzip2": "bz2",
"application/x-cco": "cco",
"application/x-cocoa": "cco",
"application/x-compressed": "gz",
"application/x-crt": "pem",
"application/x-der": "pem",
"application/x-doc": "doc",
"application/x-ear": "jar",
"application/x-eot": "eot",
"application/x-eps": "ps",
"application/x-gz": "gz",
"application/x-gzip": "gz",
"application/x-hqx": "hqx",
"application/x-jar": "jar",
"application/x-jardiff": "jardiff",
"application/x-java-archive": "jar",
"application/x-java-archive-diff": "jardiff",
"application/x-java-jnlp-file": "jnlp",
"application/x-javascript": "js",
"application/x-jnlp": "jnlp",
"application/x-js": "js",
"application/x-json": "json",
"application/x-jsx": "js",
"application/x-kml": "kml",
"application/x-kmz": "kmz",
"application/x-m3u8": "m3u8",
"application/x-mac-binhex40": "hqx",
"application/x-makeself": "run",
"application/x-msword": "doc",
"application/x-odg": "odg",
"application/x-odp": "odp",
"application/x-ods": "ods",
"application/x-odt": "odt",
"application/x-pdb": "prc",
"application/x-pdf": "pdf",
"application/x-pem": "pem",
"application/x-perl": "pl",
"application/x-pilot": "prc",
"application/x-pl": "pl",
"application/x-pm": "pl",
"application/x-postscript": "ps",
"application/x-ppt": "ppt",
"application/x-prc": "prc",
"application/x-ps": "ps",
"application/x-rar": "rar",
"application/x-rar-compressed": "rar",
"application/x-redhat-package-manager": "rpm",
"application/x-rpm": "rpm",
"application/x-rss": "rss",
"application/x-rss+xml": "rss",
"application/x-rtf": "rtf",
"application/x-run": "run",
"application/x-sea": "sea",
"application/x-shockwave-flash": "swf",
"application/x-sit": "sit",
"application/x-stuffit": "sit",
"application/x-swf": "swf",
"application/x-tar": "tar",
"application/x-tcl": "tcl",
"application/x-tk": "tcl",
"application/x-vnd.apple.mpegurl": "m3u8",
"application/x-vnd.google-earth.kml+xml": "kml",
"application/x-vnd.google-earth.kmz": "kmz",
"application/x-vnd.ms-excel": "xls",
"application/x-vnd.ms-fontobject": "eot",
"application/x-vnd.ms-powerpoint": "ppt",
"application/x-vnd.oasis.opendocument.graphics": "odg",
"application/x-vnd.oasis.opendocument.presentation": "odp",
"application/x-vnd.oasis.opendocument.spreadsheet": "ods",
"application/x-vnd.oasis.opendocument.text": "odt",
"application/x-vnd.wap.wmlc": "wmlc",
"application/x-war": "jar",
"application/x-wmlc": "wmlc",
"application/x-x509-ca-cert": "pem",
"application/x-xhtml": "xhtml",
"application/x-xhtml+xml": "xhtml",
"application/x-xls": "xls",
"application/x-xpi": "xpi",
"application/x-xpinstall": "xpi",
"application/x-xspf": "xspf",
"application/x-xspf+xml": "xspf",
"application/x-xz": "xz",
"application/x-zip": "zip",
"application/x509-ca-cert": "pem",
"application/xhtml": "xhtml",
"application/xhtml+xml": "xhtml",
"application/xls": "xls",
"application/xpi": "xpi",
"application/xpinstall": "xpi",
"application/xspf": "xspf",
"application/xspf+xml": "xspf",
"application/xz": "xz",
"application/zip": "zip",
"audio/kar": "mid",
"audio/m4a": "m4a",
"audio/matroska": "mka",
"audio/mid": "mid",
"audio/midi": "mid",
"audio/mka": "mka",
"audio/mp3": "mp3",
"audio/mpeg": "mp3",
"audio/ogg": "ogg",
"audio/ra": "ra",
"audio/realaudio": "ra",
"audio/x-kar": "mid",
"audio/x-m4a": "m4a",
"audio/x-matroska": "mka",
"audio/x-mid": "mid",
"audio/x-midi": "mid",
"audio/x-mka": "mka",
"audio/x-mp3": "mp3",
"audio/x-mpeg": "mp3",
"audio/x-ogg": "ogg",
"audio/x-ra": "ra",
"audio/x-realaudio": "ra",
"font/woff": "woff",
"font/woff2": "woff2",
"font/x-woff": "woff",
"font/x-woff2": "woff2",
"image/bmp": "bmp",
"image/gif": "gif",
"image/heic": "heic",
"image/heif": "heic",
"image/heif-sequence": "heic",
"image/ico": "ico",
"image/icon": "ico",
"image/jfif": "jpg",
"image/jng": "jng",
"image/jpe": "jpg",
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/ms-bmp": "bmp",
"image/png": "png",
"image/svg": "svg",
"image/svg+xml": "svg",
"image/svgz": "svg",
"image/tif": "tif",
"image/tiff": "tif",
"image/vnd.wap.wbmp": "wbmp",
"image/wbmp": "wbmp",
"image/webp": "webp",
"image/x-bmp": "bmp",
"image/x-gif": "gif",
"image/x-heic": "heic",
"image/x-heif": "heic",
"image/x-heif-sequence": "heic",
"image/x-ico": "ico",
"image/x-icon": "ico",
"image/x-jfif": "jpg",
"image/x-jng": "jng",
"image/x-jpe": "jpg",
"image/x-jpeg": "jpg",
"image/x-jpg": "jpg",
"image/x-ms-bmp": "bmp",
"image/x-png": "png",
"image/x-svg": "svg",
"image/x-svg+xml": "svg",
"image/x-svgz": "svg",
"image/x-tif": "tif",
"image/x-tiff": "tif",
"image/x-vnd.wap.wbmp": "wbmp",
"image/x-wbmp": "wbmp",
"image/x-webp": "webp",
"text/component": "htc",
"text/css": "css",
"text/htc": "htc",
"text/htm": "html",
"text/html": "html",
"text/jad": "jad",
"text/javascript": "js",
"text/js": "js",
"text/jsx": "js",
"text/mathml": "mml",
"text/mml": "mml",
"text/php": "html",
"text/plain": "txt",
"text/shtml": "html",
"text/txt": "txt",
"text/vnd.sun.j2me.app-descriptor": "jad",
"text/vnd.wap.wml": "wml",
"text/wml": "wml",
"text/x-component": "htc",
"text/x-css": "css",
"text/x-htc": "htc",
"text/x-htm": "html",
"text/x-html": "html",
"text/x-jad": "jad",
"text/x-javascript": "js",
"text/x-js": "js",
"text/x-jsx": "js",
"text/x-mathml": "mml",
"text/x-mml": "mml",
"text/x-php": "html",
"text/x-plain": "txt",
"text/x-shtml": "html",
"text/x-txt": "txt",
"text/x-vnd.sun.j2me.app-descriptor": "jad",
"text/x-vnd.wap.wml": "wml",
"text/x-wml": "wml",
"text/x-xml": "xml",
"text/xml": "xml",
"video/3gp": "3gpp",
"video/3gpp": "3gpp",
"video/asf": "asx",
"video/asx": "asx",
"video/avi": "avi",
"video/flv": "flv",
"video/m4v": "m4v",
"video/matroska": "mkv",
"video/mk3d": "mkv",
"video/mks": "mkv",
"video/mkv": "mkv",
"video/mng": "mng",
"video/moov": "mov",
"video/mov": "mov",
"video/mp2t": "ts",
"video/mp4": "mp4",
"video/mpe": "mpg",
"video/mpeg": "mpg",
"video/mpg": "mpg",
"video/ms-asf": "asx",
"video/ms-wmv": "wmv",
"video/msvideo": "avi",
"video/opus": "opus",
"video/qt": "mov",
"video/quicktime": "mov",
"video/ts": "ts",
"video/webm": "webm",
"video/wmv": "wmv",
"video/x-3gp": "3gpp",
"video/x-3gpp": "3gpp",
"video/x-asf": "asx",
"video/x-asx": "asx",
"video/x-avi": "avi",
"video/x-flv": "flv",
"video/x-m4v": "m4v",
"video/x-matroska": "mkv",
"video/x-mk3d": "mkv",
"video/x-mks": "mkv",
"video/x-mkv": "mkv",
"video/x-mng": "mng",
"video/x-moov": "mov",
"video/x-mov": "mov",
"video/x-mp2t": "ts",
"video/x-mp4": "mp4",
"video/x-mpe": "mpg",
"video/x-mpeg": "mpg",
"video/x-mpg": "mpg",
"video/x-ms-asf": "asx",
"video/x-ms-wmv": "wmv",
"video/x-msvideo": "avi",
"video/x-opus": "opus",
"video/x-qt": "mov",
"video/x-quicktime": "mov",
"video/x-ts": "ts",
"video/x-webm": "webm",
"video/x-wmv": "wmv"
}
}

View File

@ -103,7 +103,7 @@ class Handler {
discarded: false,
};
if (!CHROME) {
toptions.hidden = true;
toptions.hidden = false;
}
const selectedTabs = options.allTabs ?
await tabs.query(toptions) as any[] :

View File

@ -49,6 +49,7 @@ export const {runtime} = polyfill;
export const {storage} = polyfill;
export const {tabs} = polyfill;
export const {webNavigation} = polyfill;
export const {webRequest} = polyfill;
export const {windows} = polyfill;
export const CHROME = navigator.appVersion.includes("Chrome/");

View File

@ -9,7 +9,7 @@ import { EventEmitter } from "./events";
import { TYPE_LINK, TYPE_MEDIA, TYPE_ALL } from "./constants";
// eslint-disable-next-line no-unused-vars
import { Overlayable } from "./objectoverlay";
import * as DEFAULT_FILTERS from "../data/filters.json";
import DEFAULT_FILTERS from "../data/filters.json";
import { FASTFILTER } from "./recentlist";
import { _, locale } from "./i18n";
// eslint-disable-next-line no-unused-vars

View File

@ -2,7 +2,7 @@
// License: MIT
import {memoize} from "./memoize";
import * as langs from "../_locales/all.json";
import langs from "../_locales/all.json";
import { sorted, naturalCaseCompare } from "./sorting";

View File

@ -27,6 +27,9 @@ const SAVEDPROPS = [
"written",
// server stuff
"serverName",
"browserName",
"mime",
"prerolled",
// other options
"private",
// db
@ -39,10 +42,13 @@ const DEFAULTS = {
state: QUEUED,
error: "",
serverName: "",
browserName: "",
fileName: "",
totalSize: 0,
written: 0,
manId: 0,
mime: "",
prerolled: false
};
let sessionId = 0;
@ -59,14 +65,26 @@ export class BaseDownload {
public url: string;
public usable: string;
public uReferrer: URLd;
public referrer: string;
public usableReferrer: string;
public startDate: Date;
public fileName: string;
public description?: string;
public title?: string;
public batch: number;
public idx: number;
public error: string;
public postData: any;
@ -79,8 +97,13 @@ export class BaseDownload {
public serverName: string;
public browserName: string;
public mime: string;
public mask: string;
public prerolled: boolean;
constructor(options: any) {
Object.assign(this, DEFAULTS);
@ -115,6 +138,10 @@ export class BaseDownload {
return this.serverName || this.fileName || this.urlName || "index.html";
}
get currentName() {
return this.browserName || this.dest.name || this.finalName;
}
get urlName() {
const path = parsePath(this.uURL);
if (path.name) {
@ -152,6 +179,7 @@ export class BaseDownload {
rv.destName = dest.name;
rv.destPath = dest.path;
rv.destFull = dest.full;
rv.currentName = this.browserName || rv.destName || rv.finalName;
rv.error = this.error;
rv.ext = this.renamer.p_ext;
return rv;

View File

@ -1,25 +1,70 @@
"use strict";
// License: MIT
import MimeType from "whatwg-mimetype";
import { CHROME, downloads, webRequest } from "../browser";
import { Prefs } from "../prefs";
import { parsePath, filterInSitu } from "../util";
import {
QUEUED, RUNNING, CANCELED, PAUSED, MISSING, DONE,
FORCABLE, PAUSABLE, CANCELABLE,
} from "./state";
import { BaseDownload } from "./basedownload";
import { PromiseSerializer } from "../pserializer";
import { filterInSitu, parsePath, sanitizePath } from "../util";
import { BaseDownload } from "./basedownload";
// eslint-disable-next-line no-unused-vars
import { Manager } from "./man";
import { downloads, CHROME } from "../browser";
import { debounce } from "../../uikit/lib/util";
import Renamer from "./renamer";
import {
CANCELABLE,
CANCELED,
DONE,
FORCABLE,
MISSING,
PAUSABLE,
PAUSED,
QUEUED,
RUNNING
} from "./state";
const PREROLL_HEURISTICS = /dl|attach|download|name|file|get|retr|^n$|\.(php|asp|py|pl|action|htm|shtm)/i;
const PREROLL_HOSTS = /4cdn|chan/;
const PREROLL_TIMEOUT = 10000;
const PREROLL_NOPE = new Set<string>();
const setShelfEnabled = downloads.setShelfEnabled || function() {
// ignored
};
function parseDisposition(disp: MimeType) {
if (!disp) {
return "";
}
let encoding = (disp.parameters.get("charset") || "utf-8").trim();
let file = (disp.parameters.get("filename") || "").trim().replace(/^(["'])(.*)\1$/, "$2");
if (!file) {
const encoded = disp.parameters.get("filename*");
if (!encoded) {
return "";
}
const pieces = encoded.split("'", 3);
if (pieces.length !== 3) {
return "";
}
encoding = pieces[0].trim() || encoding;
file = (pieces[3] || "").trim().replace(/^(["'])(.*)\1$/, "$2");
}
file = file.trim();
if (!file) {
return "";
}
const reenableShelf = debounce(() => setShelfEnabled(true), 1000, true);
try {
// And now for the tricky part...
// First unescape the string, to get the raw bytes
// not utf-8-interpreted bytes
// Then convert the string into an uint8[]
// Then decode
return new TextDecoder(encoding).decode(
new Uint8Array(unescape(file).split("").map(e => e.charCodeAt(0)))
);
}
catch (ex) {
console.error("Cannot decode", encoding, file, ex);
}
return "";
}
type Header = {name: string; value: string};
interface Options {
@ -53,6 +98,7 @@ export class Download extends BaseDownload {
}
markDirty() {
this.renamer = new Renamer(this);
this.manager.setDirty(this);
}
@ -80,6 +126,11 @@ export class Download extends BaseDownload {
this.updateStateFromBrowser();
return;
}
if (state[0].state === "complete") {
this.changeState(DONE);
this.updateStateFromBrowser();
return;
}
if (!state[0].canResume) {
throw new Error("Cannot resume");
}
@ -97,9 +148,22 @@ export class Download extends BaseDownload {
if (this.state !== QUEUED) {
throw new Error("invalid state");
}
console.trace("starting", this.toString(), this.toMsg());
console.log("starting", this.toString(), this.toMsg());
this.changeState(RUNNING);
// Do NOT await
this.reallyStart();
}
private async reallyStart() {
try {
if (!this.prerolled) {
await this.maybePreroll();
if (this.state !== RUNNING) {
// Aborted by preroll
return;
}
}
const options: Options = {
conflictAction: await Prefs.get("conflict-action"),
filename: this.dest.full,
@ -124,24 +188,18 @@ export class Download extends BaseDownload {
this.manager.removeManId(this.manId);
}
setShelfEnabled(false);
try {
try {
this.manager.addManId(
this.manId = await downloads.download(options), this);
}
catch (ex) {
if (!this.referrer) {
throw ex;
}
// Re-attempt without referrer
filterInSitu(options.headers, h => h.name !== "Referer");
this.manager.addManId(
this.manId = await downloads.download(options), this);
}
this.manager.addManId(
this.manId = await downloads.download(options), this);
}
finally {
reenableShelf();
catch (ex) {
if (!this.referrer) {
throw ex;
}
// Re-attempt without referrer
filterInSitu(options.headers, h => h.name !== "Referer");
this.manager.addManId(
this.manId = await downloads.download(options), this);
}
this.markDirty();
}
@ -152,6 +210,146 @@ export class Download extends BaseDownload {
}
}
private get shouldPreroll() {
const {pathname, search, host} = this.uURL;
if (PREROLL_NOPE.has(host)) {
return false;
}
if (!this.renamer.p_ext) {
return true;
}
if (search.length) {
return true;
}
if (this.uURL.pathname.endsWith("/")) {
return true;
}
if (PREROLL_HEURISTICS.test(pathname)) {
return true;
}
if (PREROLL_HOSTS.test(host)) {
return true;
}
return false;
}
private async maybePreroll() {
try {
if (this.prerolled) {
// Check again, just in case, async and all
return;
}
if (!this.shouldPreroll) {
return;
}
await (CHROME ? this.prerollChrome() : this.prerollFirefox());
}
catch (ex) {
console.error("Failed to preroll", this, ex.toString(), ex.stack, ex);
}
finally {
if (this.state === RUNNING) {
this.prerolled = true;
this.markDirty();
}
}
}
private async prerollFirefox() {
const controller = new AbortController();
const {signal} = controller;
const res = await fetch(this.uURL.toString(), {
method: "HEAD",
mode: "same-origin",
signal,
});
controller.abort();
const {headers} = res;
this.prerollFinialize(headers, res);
}
async prerollChrome() {
let rid = "";
const rurl = this.uURL.toString();
let listener: any;
const wr = new Promise<any[]>(resolve => {
listener = (details: any) => {
const {url, requestId, statusCode} = details;
if (rid !== requestId && url !== rurl) {
return;
}
// eslint-disable-next-line no-magic-numbers
if (statusCode >= 300 && statusCode < 400) {
// Redirect, continue tracking;
rid = requestId;
return;
}
resolve(details.responseHeaders);
};
webRequest.onHeadersReceived.addListener(
listener, {urls: ["<all_urls>"]}, ["responseHeaders"]);
});
const p = Promise.race([
wr,
new Promise<any[]>((_, reject) =>
setTimeout(() => reject(new Error("timeout")), PREROLL_TIMEOUT))
]);
p.finally(() => {
webRequest.onHeadersReceived.removeListener(listener);
});
const controller = new AbortController();
const {signal} = controller;
const res = await fetch(rurl, {
method: "HEAD",
signal,
});
controller.abort();
const headers = await p;
this.prerollFinialize(
new Headers(headers.map(i => [i.name, i.value])), res);
}
private prerollFinialize(headers: Headers, res: Response) {
const type = MimeType.parse(headers.get("content-type") || "");
const dispHeader = headers.get("content-disposition");
let file = "";
if (dispHeader) {
const disp = new MimeType(`${type && type.toString() || "application/octet-stream"}; ${dispHeader}`);
file = parseDisposition(disp);
// Sanitize
file = sanitizePath(file.replace(/[/\\]+/g, "-"));
}
if (type) {
this.mime = type.essence;
}
this.serverName = file;
this.markDirty();
const {status} = res;
/* eslint-disable no-magic-numbers */
if (status === 404) {
this.cancel();
this.error = "SERVER_BAD_CONTENT";
}
else if (status === 403) {
this.cancel();
this.error = "SERVER_FORBIDDEN";
}
else if (status === 402 || status === 407) {
this.cancel();
this.error = "SERVER_UNAUTHORIZED";
}
else if (status === 400 || status === 405) {
PREROLL_NOPE.add(this.uURL.host);
}
else if (status > 400 && status < 500) {
this.cancel();
this.error = "SERVER_FAILED";
}
/* eslint-enable no-magic-numbers */
}
resume(forced = false) {
if (!(FORCABLE & this.state)) {
return;
@ -181,9 +379,10 @@ export class Download extends BaseDownload {
}
reset() {
this.prerolled = false;
this.manId = 0;
this.written = this.totalSize = 0;
this.serverName = "";
this.mime = this.serverName = this.browserName = "";
}
async removeFromBrowser() {
@ -260,8 +459,11 @@ export class Download extends BaseDownload {
const state = (await downloads.search({id: this.manId})).pop();
const {filename, error} = state;
const path = parsePath(filename);
this.serverName = path.name;
this.browserName = path.name;
this.adoptSize(state);
if (!this.mime && state.mime) {
this.mime = state.mime;
}
this.markDirty();
switch (state.state) {
case "in_progress":

View File

@ -25,11 +25,14 @@ const DIRTY_TIMEOUT = 100;
const MISSING_TIMEOUT = 12 * 1000;
const RELOAD_TIMEOUT = 10 * 1000;
const setShelfEnabled = downloads.setShelfEnabled || function() {
// ignored
};
export class Manager extends EventEmitter {
private items: Download[];
private active: boolean;
public active: boolean;
private notifiedFinished: boolean;
@ -93,7 +96,10 @@ export class Manager extends EventEmitter {
}
this.items.push(rv);
});
await this.resetScheduler();
// Do not wait for the scheduler
this.resetScheduler();
this.emit("inited");
setTimeout(() => this.checkMissing(), MISSING_TIMEOUT);
runtime.onUpdateAvailable.addListener(() => {
@ -148,7 +154,7 @@ export class Manager extends EventEmitter {
}
const next = await this.scheduler.next(this.running);
if (!next) {
this.maybeNotifyFinished();
this.maybeRunFinishActions();
break;
}
if (this.running.has(next) || next.state !== QUEUED) {
@ -168,10 +174,31 @@ export class Manager extends EventEmitter {
async startDownload(download: Download) {
// Add to running first, so we don't confuse the scheduler and other parts
this.running.add(download);
setShelfEnabled(false);
await download.start();
this.notifiedFinished = false;
}
async maybeRunFinishActions() {
if (this.running.size) {
return;
}
await this.maybeNotifyFinished();
if (this.running.size) {
return;
}
if (this.shouldReload) {
this.saveQueue.trigger();
setTimeout(() => {
if (this.running.size) {
return;
}
runtime.reload();
}, RELOAD_TIMEOUT);
}
setShelfEnabled(true);
}
async maybeNotifyFinished() {
if (!(await Prefs.get("finish-notification"))) {
return;
@ -181,14 +208,6 @@ export class Manager extends EventEmitter {
}
this.notifiedFinished = true;
new Notification(null, _("queue-finished"));
if (this.shouldReload) {
setTimeout(() => {
if (this.running.size) {
return;
}
runtime.reload();
}, RELOAD_TIMEOUT);
}
}
addManId(id: number, download: Download) {
@ -236,7 +255,7 @@ export class Manager extends EventEmitter {
this.emit("dirty", items);
}
save(items: Download[]) {
private save(items: Download[]) {
DB.saveItems(items.filter(i => !i.removed)).
catch(console.error);
}
@ -361,6 +380,10 @@ export class Manager extends EventEmitter {
}
this.emit("active", this.active);
}
getMsgItems() {
return this.items.map(e => e.toMsg());
}
}
let inited: Promise<Manager>;

View File

@ -5,6 +5,10 @@ import { donate, openPrefs } from "../windowutils";
import { API } from "../api";
// eslint-disable-next-line no-unused-vars
import { BaseDownload } from "./basedownload";
// eslint-disable-next-line no-unused-vars
import { Manager } from "./man";
// eslint-disable-next-line no-unused-vars
import { Port } from "../bus";
type SID = {sid: number};
type SIDS = {
@ -13,9 +17,9 @@ type SIDS = {
};
export class ManagerPort {
private manager: any;
private manager: Manager;
private port: any;
private port: Port;
constructor(manager: any, port: any) {
this.manager = manager;
@ -79,7 +83,6 @@ export class ManagerPort {
}
sendAll() {
this.port.post(
"all", this.manager.items.map((e: BaseDownload) => e.toMsg()));
this.port.post("all", this.manager.getMsgItems());
}
}

View File

@ -2,8 +2,12 @@
"use strict";
// License: MIT
import { parsePath, sanitizePath } from "../util";
import { _ } from "../i18n";
import { MimeDB } from "../mime";
// eslint-disable-next-line no-unused-vars
import { parsePath, PathInfo, sanitizePath } from "../util";
// eslint-disable-next-line no-unused-vars
import { BaseDownload } from "./basedownload";
const REPLACE_EXPR = /\*\w+\*/gi;
@ -22,21 +26,41 @@ const DATE_FORMATTER = new Intl.NumberFormat(undefined, {
});
export default class Renamer {
private readonly d: any;
private readonly d: BaseDownload;
constructor(download: any) {
private readonly nameinfo: PathInfo;
constructor(download: BaseDownload) {
this.d = download;
const info = parsePath(this.d.finalName);
this.nameinfo = this.fixupExtension(info);
}
get nameinfo() {
return parsePath(this.d.finalName);
private fixupExtension(info: PathInfo): PathInfo {
if (!this.d.mime) {
return info;
}
const mime = MimeDB.getMime(this.d.mime);
if (!mime) {
return info;
}
const {ext} = info;
if (mime.major === "image" || mime.major === "video") {
if (ext && mime.extensions.has(ext.toLowerCase())) {
return info;
}
return new PathInfo(info.base, mime.primary, info.path);
}
if (ext) {
return info;
}
return new PathInfo(info.base, mime.primary, info.path);
}
get ref() {
return this.d.uReferrer;
}
get p_name() {
return this.nameinfo.base;
}
@ -184,7 +208,7 @@ export default class Renamer {
(self[prop] || "").trim() :
type;
if (flat) {
return rv.replace(/\/+/g, "-");
return rv.replace(/[/\\]+/g, "-");
}
return rv;
}));

55
lib/mime.ts Normal file
View File

@ -0,0 +1,55 @@
"use strict";
// License: MIT
import mime from "../data/mime.json";
export class MimeInfo {
public readonly type: string;
public readonly extensions: Set<string>;
public readonly major: string;
public readonly minor: string;
public readonly primary: string;
constructor(type: string, extensions: string[]) {
this.type = type;
const [major, minor] = type.split("/", 2);
this.major = major;
this.minor = minor;
[this.primary] = extensions;
this.extensions = new Set(extensions);
Object.freeze(this);
}
}
export const MimeDB = new class {
private readonly mimeToExts: Map<string, MimeInfo>;
constructor() {
const exts = new Map<string, string[]>();
for (const [prim, more] of Object.entries(mime.e)) {
let toadd = more;
if (!Array.isArray(toadd)) {
toadd = [toadd];
}
toadd.unshift(prim);
exts.set(prim, toadd);
}
this.mimeToExts = new Map(Array.from(
Object.entries(mime.m),
([mime, prim]) => [mime, new MimeInfo(mime, exts.get(prim) || [prim])]
));
}
getPrimary(mime: string) {
const info = this.mimeToExts.get(mime.trim().toLocaleLowerCase());
return info ? info.primary : "";
}
getMime(mime: string) {
return this.mimeToExts.get(mime.trim().toLocaleLowerCase());
}
}();

View File

@ -1,9 +1,9 @@
"use strict";
// License: MIT
import * as DEFAULT_PREFS from "../data/prefs.json";
import DEFAULT_PREFS from "../data/prefs.json";
import { EventEmitter } from "./events";
import {loadOverlay} from "./objectoverlay";
import { loadOverlay } from "./objectoverlay";
import { storage } from "./browser";
const PREFS = Symbol("PREFS");

View File

@ -2,8 +2,8 @@
// License: MIT
import * as psl from "psl";
import {memoize, identity} from "./memoize";
export {debounce} from "../uikit/lib/util";
import { identity, memoize } from "./memoize";
export { debounce } from "../uikit/lib/util";
export class Promised {
private promise: Promise<any>;
@ -96,8 +96,72 @@ export const IS_WIN = typeof navigator !== "undefined" &&
export const sanitizePath = identity(
IS_WIN ? sanitizePathWindows : sanitizePathGeneric);
export class PathInfo {
private baseField: string;
private extField: string;
private pathField: string;
private nameField: string;
private fullField: string;
constructor(base: string, ext: string, path: string) {
this.baseField = base;
this.extField = ext;
this.pathField = path;
this.update();
}
get base() {
return this.baseField;
}
set base(nv) {
this.baseField = sanitizePath(nv);
this.update();
}
get ext() {
return this.extField;
}
set ext(nv) {
this.extField = sanitizePath(nv);
this.update();
}
get name() {
return this.nameField;
}
get path() {
return this.pathField;
}
set path(nv) {
this.pathField = sanitizePath(nv);
this.update();
}
get full() {
return this.fullField;
}
private update() {
this.nameField = this.extField ? `${this.baseField}.${this.extField}` : this.baseField;
this.fullField = this.pathField ? `${this.pathField}/${this.nameField}` : this.nameField;
}
clone() {
return new PathInfo(this.baseField, this.extField, this.pathField);
}
}
// XXX cleanup + test
export const parsePath = memoize(function parsePath(path: string | URL) {
export const parsePath = memoize(function parsePath(
path: string | URL): PathInfo {
if (path instanceof URL) {
path = decodeURIComponent(path.pathname);
}
@ -127,13 +191,7 @@ export const parsePath = memoize(function parsePath(path: string | URL) {
}
path = pieces.join("/");
return {
path,
name,
base,
ext,
full: path ? `${path}/${name}` : name
};
return new PathInfo(base, ext, path);
});
export class CoalescedUpdate<T> extends Set<T> {

View File

@ -3,7 +3,7 @@
import { windows, tabs, runtime } from "../lib/browser";
import {getManager} from "./manager/man";
import * as DEFAULT_ICONS from "../data/icons.json";
import DEFAULT_ICONS from "../data/icons.json";
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
const MANAGER_URL = "/windows/manager.html";

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "DownThemAll!",
"version": "4.0.8",
"version": "4.0.9",
"description": "__MSG_extensionDescription__",
"homepage_url": "https://downthemall.org/",
@ -9,7 +9,7 @@
"default_locale": "en",
"content_security_policy": "script-src 'self'; style-src 'self' 'unsafe-inline'; img-src data: blob: 'self'; connect-src data: blob: 'self'; default-src 'self'",
"content_security_policy": "script-src 'self'; style-src 'self' 'unsafe-inline'; img-src data: blob: 'self'; connect-src data: blob: http: https: 'self'; default-src 'self'",
"icons": {
"16": "style/icon16.png",
@ -31,7 +31,8 @@
"notifications",
"storage",
"tabs",
"webNavigation"
"webNavigation",
"webRequest"
],
"background": {

View File

@ -33,7 +33,9 @@
},
"dependencies": {
"@types/psl": "^1.1.0",
"@types/whatwg-mimetype": "^2.1.0",
"psl": "^1.3.0",
"webextension-polyfill": "^0.4.0"
"webextension-polyfill": "^0.4.0",
"whatwg-mimetype": "^2.3.0"
}
}

View File

@ -295,7 +295,7 @@ class Gatherer {
function gather(msg: any, sender: any, callback: Function) {
try {
if (!msg || msg.type !== "DTA:gather" || !callback) {
return;
return Promise.resolve(null);
}
const gatherer = new Gatherer(msg);
const result = {
@ -313,10 +313,11 @@ function gather(msg: any, sender: any, callback: Function) {
),
};
urlToUsable(result, result.baseURL);
callback(result);
return Promise.resolve(result);
}
catch (ex) {
console.error(ex.toString(), ex.stack, ex);
return Promise.resolve(null);
}
}

View File

@ -18,6 +18,7 @@
--folder-color: rgb(214, 165, 4);
--maskbutton-color: rgb(236, 185, 16);
--missing-color: rgb(0, 82, 204);
--open-color: rgba(236, 185, 16, 0.8);
}
html[data-platform="mac"] {

View File

@ -108,11 +108,11 @@ body > * {
}
#colURL {
width: 38%;
width: 42%;
}
#colPercent {
width: 3em;
width: 4em;
min-width: 3em;
}
@ -121,11 +121,11 @@ body > * {
}
#colSize {
width: 15em;
width: 14em;
}
#colSpeed {
width: 6em;
width: 7em;
}
#colDomain,
@ -154,6 +154,14 @@ body > * {
height: 26px;
}
.virtualtable-row.opening {
background: var(--open-color) !important;
}
.virtualtable-progress-container {
border-radius: 2px;
}
.virtualtable-progress-bar {
height: 14px;
}
@ -262,6 +270,7 @@ body > * {
}
.virtualtable-column-6,
.virtualtable-column-4,
.virtualtable-column-3 {
text-align: right;
}
@ -430,6 +439,8 @@ body > * {
justify-items: stretch;
border-radius: 4px;
box-shadow: 2px 2px 6px black;
-webkit-user-select: none;
user-select: none;
}
#tooltip-infos {

View File

@ -232,3 +232,7 @@ body > * {
#maskButton {
justify-self: flex-start;
}
#btnDownload {
font-weight: bold;
}

View File

@ -81,3 +81,7 @@ h3 {
font-weight: normal;
font-style: italic;
}
#btnDownload {
font-weight: bold;
}

View File

@ -19,7 +19,7 @@ const OPTS = {
state: DownloadState.QUEUED,
batch: 42,
idx: 23,
mask: "*name*.*ext",
mask: "*name*.*ext*",
description: "desc / ript.ion .",
title: " *** TITLE *** ",
};
@ -57,6 +57,49 @@ describe("Renamer", function() {
expect(dest.path).to.equal("");
});
it("*name*.*ext* (mime override)", function() {
const {dest} = new BaseDownload(
Object.assign({}, OPTS, {
mask: "*name* *batch*.*ext*",
mime: "image/jpeg"
}));
expect(dest.full).to.equal("filenäme 042.jpg");
expect(dest.name).to.equal("filenäme 042.jpg");
expect(dest.base).to.equal("filenäme 042");
expect(dest.ext).to.equal("jpg");
expect(dest.path).to.equal("");
});
it("*name*.*ext* (mime no override)", function() {
const {dest} = new BaseDownload(
Object.assign({}, OPTS, {
mask: "*name* *batch*.*ext*",
mime: "image/jpeg",
url: "https://www.example.co.uk/filen%C3%A4me.JPe",
usable: "https://www.example.co.uk/filenäme.JPe",
}));
expect(dest.full).to.equal("filenäme 042.JPe");
expect(dest.name).to.equal("filenäme 042.JPe");
expect(dest.base).to.equal("filenäme 042");
expect(dest.ext).to.equal("JPe");
expect(dest.path).to.equal("");
});
it("*name*.*ext* (mime override; missing ext)", function() {
const {dest} = new BaseDownload(
Object.assign({}, OPTS, {
mask: "*name* *batch*.*ext*",
mime: "application/json",
url: "https://www.example.co.uk/filen%C3%A4me",
usable: "https://www.example.co.uk/filenäme",
}));
expect(dest.full).to.equal("filenäme 042.json");
expect(dest.name).to.equal("filenäme 042.json");
expect(dest.base).to.equal("filenäme 042");
expect(dest.ext).to.equal("json");
expect(dest.path).to.equal("");
});
it("*text*", function() {
const dest = makeOne("*text*");
expect(dest.full).to.equal("desc/ript.ion");

View File

@ -12,6 +12,8 @@
"noImplicitReturns": true,
"noUnusedLocals": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"importHelpers": true,
"sourceMap": true
}
}

23
util/additional.types Normal file
View File

@ -0,0 +1,23 @@
types {
application/x-x509-ca-cert pem crt der;
application/javascript js jsx;
audio/x-matroska mka;
image/bmp bmp;
image/heic heic heif;
image/heic heic heif;
image/heif-sequence heic heif;
image/heif-sequence heic heif;
image/jpeg jpg jpeg jpe jfif;
image/webp webp;
text/html html htm shtml php;
text/javascript js jsx;
video/mpeg mpg mpe mpeg mpg;
video/opus opus;
video/x-matroska mkv mk3d mks;
video/quicktime mov qt moov;
application/x-compressed gz;
application/x-gzip gz gzip;
application/x-bzip2 bz2;
application/x-tar tar;
application/x-xz xz;
}

View File

@ -26,7 +26,7 @@ UNCOMPRESSABLE = set((".png", ".jpg", ".zip", ".woff2"))
LICENSED = set((".css", ".html", ".js", "*.ts"))
IGNORED = set((".DS_Store", "Thumbs.db"))
PERM_IGNORED_FX = set(("downloads.shelf",))
PERM_IGNORED_FX = set(("downloads.shelf", "webRequest"))
PERM_IGNORED_CHROME = set(("menus",))
SCRIPTS = [

98
util/mime.types Normal file
View File

@ -0,0 +1,98 @@
https://github.com/nginx/nginx/raw/master/conf/mime.types
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

76
util/seed_mime.py Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env python3
import json
import re
import sys
from collections import OrderedDict
def unique(seq):
return list(OrderedDict([i, None] for i in seq if i))
def generate(major, minor, exts):
exts = exts[:]
yield f"{major}/{minor}", exts
if (minor.startswith("x-")):
yield f"{major}/{minor[2:]}", exts
else:
yield f"{major}/x-{minor}", exts
for ext in exts:
yield f"{major}/{ext}", exts
yield f"{major}/x-{ext}", exts
def make(text, final):
lines = "".join([
line.strip()
for line in re.search(r"\{(.*)\}", text, re.S).group(1).split("\n")
if line.strip() and not line.strip().startswith("#")
]).split(";")
additional = []
for line in lines:
if not line:
continue
m = re.match(r"([a-z1-9]+)/([^\s]+)\s+(.+?)$", line)
if not m:
continue
[major, minor, exts] = m.groups()
exts = unique(e.lower().strip() for e in exts.split(" ") if e.strip())
mime = f"{major}/{minor}"
if mime == "application/octet-stream":
continue
if mime in final:
final[mime] += exts
continue
final[mime] = exts
additional += (major, minor, exts),
for [major, minor, exts] in additional:
for [mime, exts] in generate(major, minor, exts):
if mime in final:
continue
final[mime] = exts
final = OrderedDict()
for file in sys.argv[1:]:
with open(file, "r") as fp:
make(fp.read(), final)
multi = dict()
for [mime, exts] in list(final.items()):
exts = unique(exts)
prim = exts[0]
final[mime] = prim
if len(exts) == 1:
continue
exts = exts[1:]
if len(exts) == 1:
multi[prim] = exts[0]
else:
multi[prim] = exts
final = OrderedDict(sorted(final.items()))
multi = OrderedDict(sorted(multi.items()))
print(json.dumps(dict(e=multi, m=final), indent=2))
print("generated", len(final), "mimes", "with", len(multi), "multis", file=sys.stderr)

View File

@ -94,8 +94,10 @@ export class TextFilter extends ItemFilter {
}
allow(item: DownloadItem) {
return this.expr.test(
[item.usable, item.description, item.finalName].join(" "));
const {expr} = this;
return expr.test(item.currentName) ||
expr.test(item.usable) ||
expr.test(item.description);
}
}

View File

@ -116,7 +116,7 @@ export class DownloadItem extends EventEmitter {
public error: string;
public finalName: string;
public currentName: string;
public ext?: string;
@ -144,6 +144,8 @@ export class DownloadItem extends EventEmitter {
private largeIconField?: string;
public opening: boolean;
constructor(owner: DownloadTable, raw: any, stats?: Stats) {
super();
Object.assign(this, raw);
@ -159,7 +161,7 @@ export class DownloadItem extends EventEmitter {
return this.iconField;
}
this.iconField = this.owner.icons.get(
iconForPath(this.finalName, ICON_BASE_SIZE));
iconForPath(this.currentName, ICON_BASE_SIZE));
if (this.ext) {
IconCache.get(this.ext, ICON_REAL_SIZE).then(icon => {
if (icon) {
@ -178,7 +180,7 @@ export class DownloadItem extends EventEmitter {
return this.largeIconField;
}
this.largeIconField = this.owner.icons.get(
iconForPath(this.finalName, LARGE_ICON_BASE_SIZE));
iconForPath(this.currentName, LARGE_ICON_BASE_SIZE));
if (this.ext) {
IconCache.get(this.ext, LARGE_ICON_REAL_SIZE).then(icon => {
if (icon) {
@ -217,7 +219,7 @@ export class DownloadItem extends EventEmitter {
if (this.owner.showUrls.value) {
return this.usable;
}
return this.finalName;
return this.currentName;
}
get fmtSize() {
@ -607,8 +609,12 @@ export class DownloadTable extends VirtualTable {
this.selection.clear();
this.tooltip = null;
this.on("hover", async info => {
if (!(await Prefs.get("tooltip"))) {
const tooltipWatcher = new PrefWatcher("tooltip", true);
this.on("hover", info => {
if (!document.hasFocus()) {
return;
}
if (!tooltipWatcher.value) {
return;
}
const item = this.downloads.filtered[info.rowid];
@ -777,7 +783,8 @@ export class DownloadTable extends VirtualTable {
}
resumeDownloads(forced = false) {
const sids = this.getSelectedSids(DownloadState.RESUMABLE);
const sids = this.getSelectedSids(
forced ? DownloadState.FORCABLE : DownloadState.RESUMABLE);
if (!sids.length) {
return;
}
@ -801,20 +808,30 @@ export class DownloadTable extends VirtualTable {
}
async openFile() {
if (this.focusRow < 0) {
this.dismissTooltip();
const {focusRow} = this;
if (focusRow < 0) {
return;
}
const item = this.downloads.filtered[this.focusRow];
const item = this.downloads.filtered[focusRow];
if (!item || !item.manId || item.state !== DownloadState.DONE) {
return;
}
item.opening = true;
try {
this.invalidateRow(focusRow);
await downloads.open(item.manId);
}
catch (ex) {
console.error(ex, ex.toString(), ex);
PORT.post("missing", {sid: item.sessionId});
}
finally {
setTimeout(() => {
item.opening = false;
this.invalidateRow(focusRow);
}, 500);
}
}
async openDirectory() {
@ -1111,7 +1128,16 @@ export class DownloadTable extends VirtualTable {
if (!item) {
return null;
}
if (item.opening) {
return ["opening"];
}
const cls = StateClasses.get(item.state);
if (cls && item.opening) {
return [cls, "opening"];
}
if (item.opening) {
return ["opening"];
}
return cls && [cls] || null;
}

View File

@ -46,6 +46,11 @@
resolved "https://registry.yarnpkg.com/@types/psl/-/psl-1.1.0.tgz#390c5df1613b166ce3c3eb9fda4d93dc3eeec7b5"
integrity sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==
"@types/whatwg-mimetype@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f981bbdf1813a75820a6ec3a7fdfa0d452552cc7"
integrity sha512-bJ/bZ+pA69lm+Ll8JJRoAD9saH7unIMfxPQQpl7bxa00qNqvUXSyk3xvoRMea1uCpAOxweI7CzjWx48ysX6yug==
"@typescript-eslint/eslint-plugin@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.0.0.tgz#609a5d7b00ce21a6f94d7ef282eba9da57ca1e42"
@ -3718,6 +3723,11 @@ webpack@^4.39.3:
watchpack "^1.6.0"
webpack-sources "^1.4.1"
whatwg-mimetype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"