Explorar el Código

Улучшено скачивание файлов - добавлено читабельное имя файла

Book Pauk hace 2 años
padre
commit
afef0ed04c

+ 4 - 4
client/components/Api/Api.vue

@@ -124,9 +124,9 @@ class Api {
         }
     }
 
-    async request(params) {
+    async request(params, timeoutSecs = 10) {
         while (1) {// eslint-disable-line
-            const response = await wsc.message(await wsc.send(params));
+            const response = await wsc.message(await wsc.send(params), timeoutSecs);
 
             if (response && response.error == 'server_busy') {
                 await this.showBusyDialog();
@@ -166,8 +166,8 @@ class Api {
         return response;
     }    
 
-    async getBookLink(bookPath) {
-        const response = await this.request({action: 'get-book-link', bookPath});
+    async getBookLink(params) {
+        const response = await this.request(Object.assign({action: 'get-book-link'}, params), 120);
 
         if (response.error) {
             throw new Error(response.error);

+ 32 - 7
client/components/Search/Search.vue

@@ -555,26 +555,51 @@ class Search {
     }
 
     async download(book, copy = false) {
-        let downloadFlag = true;
+        if (this.downloadFlag)
+            return;
+
+        this.downloadFlag = true;
         (async() => {
             await utils.sleep(200);
-            if (downloadFlag)
+            if (this.downloadFlag)
                 this.loadingMessage2 = 'Подготовка файла...';
         })();
 
-        try {            
-            const bookPath = `${book.folder}/${book.file}.${book.ext}`;
+        try {
+            const makeValidFilenameOrEmpty = (s) => {
+                try {
+                    return utils.makeValidFilename(s);
+                } catch(e) {
+                    return '';
+                }
+            };
 
-            const response = await this.api.getBookLink(bookPath);
+            //имя файла
+            let downFileName = 'default-name';
+            const author = book.author.split(',');
+            const at = [author[0], book.title];
+            downFileName = makeValidFilenameOrEmpty(at.filter(r => r).join(' - '))
+                || makeValidFilenameOrEmpty(at[0])
+                || makeValidFilenameOrEmpty(at[1])
+                || downFileName;
+            downFileName = `${downFileName.substring(0, 100)}.${book.ext}`;
+
+            const bookPath = `${book.folder}/${book.file}.${book.ext}`;
+            //подготовка
+            const response = await this.api.getBookLink({bookPath, downFileName});
             
-            const href = `${window.location.origin}${response.link}`;
+            const link = response.link;
+            const href = `${window.location.origin}${link}`;
 
             if (!copy) {
+                //скачивание
                 const d = this.$refs.download;
                 d.href = href;
+                d.download = downFileName;
 
                 d.click();
             } else {
+                //копирование ссылки
                 if (utils.copyTextToClipboard(href))
                     this.$root.notify.success('Ссылка успешно скопирована');
                 else
@@ -583,7 +608,7 @@ class Search {
         } catch(e) {
             this.$root.stdDialog.alert(e.message, 'Ошибка');
         } finally {
-            downloadFlag = false;
+            this.downloadFlag = false;
             this.loadingMessage2 = '';
         }
     }

+ 13 - 0
client/share/utils.js

@@ -54,3 +54,16 @@ export async function copyTextToClipboard(text) {
 
     return result;
 }
+
+export function makeValidFilename(filename, repl = '_') {
+    let f = filename.replace(/[\x00\\/:*"<>|]/g, repl); // eslint-disable-line no-control-regex
+    f = f.trim();
+    while (f.length && (f[f.length - 1] == '.' || f[f.length - 1] == '_')) {
+        f = f.substring(0, f.length - 1);
+    }
+
+    if (f)
+        return f;
+    else
+        throw new Error('Invalid filename');
+}

+ 3 - 1
server/controllers/WebSocketController.js

@@ -147,8 +147,10 @@ class WebSocketController {
     async getBookLink(req, ws) {
         if (!utils.hasProp(req, 'bookPath'))
             throw new Error(`bookPath is empty`);
+        if (!utils.hasProp(req, 'downFileName'))
+            throw new Error(`downFileName is empty`);    
 
-        const result = await this.webWorker.getBookLink(req.bookPath);
+        const result = await this.webWorker.getBookLink({bookPath: req.bookPath, downFileName: req.downFileName});
 
         this.send(result, req, ws);
     }

+ 25 - 8
server/core/WebWorker.js

@@ -307,7 +307,7 @@ class WebWorker {
         });
     }
 
-    async restoreBook(bookPath) {
+    async restoreBook(bookPath, downFileName) {
         const db = this.db;
 
         const extractedFile = await this.extractBook(bookPath);
@@ -333,16 +333,18 @@ class WebWorker {
             replace: true,
             rows: [
                 {id: bookPath, hash},
-                {id: hash, bookPath}
+                {id: hash, bookPath, downFileName}
             ]
         });
 
         return link;
     }
 
-    async getBookLink(bookPath) {
+    async getBookLink(params) {
         this.checkMyState();
 
+        const {bookPath, downFileName} = params;
+
         try {
             const db = this.db;
             let link = '';
@@ -360,7 +362,7 @@ class WebWorker {
             }
 
             if (!link) {
-                link = await this.restoreBook(bookPath)
+                link = await this.restoreBook(bookPath, downFileName)
             }
 
             if (!link)
@@ -380,11 +382,13 @@ class WebWorker {
             const db = this.db;
             const hash = path.basename(publicPath);
 
-            //найдем bookPath
+            //найдем bookPath и downFileName
             const rows = await db.select({table: 'file_hash', where: `@@id(${db.esc(hash)})`});        
-            if (rows.length) {//bookPath найден по хешу
-                const bookPath = rows[0].bookPath;
-                await this.restoreBook(bookPath);
+            if (rows.length) {//нашли по хешу
+                const rec = rows[0];
+                await this.restoreBook(rec.bookPath, rec.downFileName);
+                
+                return rec.downFileName;
             } else {//bookPath не найден
                 throw new Error('404 Файл не найден');
             }
@@ -396,6 +400,19 @@ class WebWorker {
         }
     }
 
+    async getDownFileName(publicPath) {
+        const db = this.db;
+        const hash = path.basename(publicPath);
+
+        //найдем downFileName
+        const rows = await db.select({table: 'file_hash', where: `@@id(${db.esc(hash)})`});        
+        if (rows.length) {//downFileName найден по хешу
+            return rows[0].downFileName;
+        } else {//bookPath не найден
+            throw new Error('404 Файл не найден');
+        }
+    }
+
     async logServerStats() {
         while (1) {// eslint-disable-line
             try {

+ 10 - 1
server/index.js

@@ -163,15 +163,21 @@ function initStatic(app, config) {
 
         const publicPath = `${config.publicDir}${req.path}`;
 
+        let downFileName = '';
         //восстановим
         try {
             if (!await fs.pathExists(publicPath)) {
-                await webWorker.restoreBookFile(publicPath);
+                downFileName = await webWorker.restoreBookFile(publicPath);
+            } else {
+                downFileName = await webWorker.getDownFileName(publicPath);                    
             }
         } catch(e) {
             //quiet
         }
 
+        if (downFileName)
+            res.downFileName = downFileName;
+
         return next();
     });
 
@@ -183,6 +189,9 @@ function initStatic(app, config) {
         setHeaders: (res, filePath) => {
             if (path.dirname(filePath) == filesDir) {
                 res.set('Content-Encoding', 'gzip');
+
+                if (res.downFileName)
+                    res.set('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(res.downFileName)}`);
             }
         },
     }));