From b0fec28e4fad0976d68c91db010e227292e6b41d Mon Sep 17 00:00:00 2001 From: nenge123 Date: Wed, 31 Jul 2024 03:06:29 +0800 Subject: [PATCH] [Chinese Simplified]add a quick use for woker ### Chinese Simplified example - [-] Quick and easy to query data - [-]Quick and easy one-time data updates - [-]Data downloads --- examples/ForWorker/README_CN.MD | 102 +++++++ examples/ForWorker/SQLite3.js | 165 +++++++++++ examples/ForWorker/WorkerApp.js | 402 ++++++++++++++++++++++++++ examples/ForWorker/WorkerAppSQLite.js | 165 +++++++++++ examples/ForWorker/WorkerAppZip.js | 212 ++++++++++++++ examples/ForWorker/changeWorker.js | 85 ++++++ 6 files changed, 1131 insertions(+) create mode 100644 examples/ForWorker/README_CN.MD create mode 100644 examples/ForWorker/SQLite3.js create mode 100644 examples/ForWorker/WorkerApp.js create mode 100644 examples/ForWorker/WorkerAppSQLite.js create mode 100644 examples/ForWorker/WorkerAppZip.js create mode 100644 examples/ForWorker/changeWorker.js diff --git a/examples/ForWorker/README_CN.MD b/examples/ForWorker/README_CN.MD new file mode 100644 index 00000000..6e170089 --- /dev/null +++ b/examples/ForWorker/README_CN.MD @@ -0,0 +1,102 @@ +### Worker快速使用 ### +> cdn使用阿里云 https://npmmirror.com/ +> IOS版本要求15++ +> 如果版本更新修改 SQLite3.js +> 自定义响应方法修改 WorkerAppSQLite.js 中的 `methods = new Map(...);` +> 推荐使用serviceWorker缓存cdn文件,避免长时间加载 +> 推荐使用场景如`JQ-easyui`这种仿生App,异步构建页面.如果直接显示则需要在serviceWorker中使用,缺点你也懂得,serviceWorker中不能垃圾回收,必须常驻加载SQLite. +```javascript +import './changeWorker.js'; +//创建表结构 +const tablelist = { + data: { + id: 'integer primary key autoincrement', + title: 'char', + type: 'char', + url: 'char', + img: 'char', + time:'char', + source: 'char', + }, + tag: { + name: 'char', + num: 'int' + } +}; +//设置 保存文件名 +const sqlfile = 'my.sqlite3'; + +async openSQL() { + const worker = await (new Worker(self.jspath + 'Worker/WorkerAppSQLite.js', { name: 'SQLite-worker' }).initMessage()); + await worker.postMethod('setFile',sqlfile); + if (!(await worker.postMethod('install', true))) { + await worker.postMethod('createList',this.tablelist); + await worker.postMethod('savedata') + } + await worker.publicMethod(); + return worker; +} + +const worker = await this.openSQL(); // 创建并初始化 +console.log(worker.methods.key()); //显示所有公共方法 具体请看SQLite3.js +console.log(await worker.callMethod('selectSQL','data')); //输出所有data数据 + +await worker.getFeedback({ + method:'importFile', + result:file,//导入数据文件 SQLite3文件 或者 json 数据 支持zip压缩文件 + password:undefined,//可选 zip解压密码 + mode:0,//or 1 + tablelist, +}); +worker.postMethod('exitworker'); //关闭并退出worker + +``` + +### 关于分享/下载下载 ### +> 因为是以caches方式保存,如果使用了serviceWorker,可以实现虚拟下载 + +```javascript +//前台 + +function postdown(action,param){ + let elm = document.createElement('form'); + elm.hidden = true; + elm.action = action; + elm.name = 'postdown'; + elm.method = 'POST'; + elm.enctype = 'text/plain'; + //elm.target = '_blank'; + elm.innerHTML = Object.entries(param).map(entry=>``).join(''); + document.body.appendChild(elm); + elm.submit(); + //elm.remove(); +} +postdown(sqlfile,{cache_name:'cache-worker'}); + +//serviceWorker.js拦截 +//self.addEventListener('fetch',function(event){ +const request = event.request; +if(request.method=='POST'){ + if(request.headers.get('content-type') == 'text/plain'){ + return event.respondWith( + new Promise(async back=>{ + const response = request.clone(); + const text = new URLSearchParams((await response.text()).replace(/[\r\n]+/,'&')); + const cachename = text.get('cache_name'); + if(cachename){ + if(await caches.has(cachename)){ + const cache = await caches.open(cachename); + const response = await cache.match(request.url); + if(response){ + back(response); + } + } + back(new Response(undefined,{status:404})); + } + back(return fetch(request)); + }); + ); + } +} +//}); +``` \ No newline at end of file diff --git a/examples/ForWorker/SQLite3.js b/examples/ForWorker/SQLite3.js new file mode 100644 index 00000000..4130a084 --- /dev/null +++ b/examples/ForWorker/SQLite3.js @@ -0,0 +1,165 @@ + +importScripts('https://registry.npmmirror.com/sql.js/1.11.0/files/dist/sql-wasm.js'); +initSqlJs({ + locateFile(){ + return 'https://registry.npmmirror.com/sql.js/1.11.0/files/dist/sql-wasm.wasm'; + } +}); +self.SQLite3Ready = new Promise(async back=>{ + const sqlite3 = await initSqlJsPromise; + delete sqlite3.wasmBinary; + class SQLite3 extends sqlite3.Database { + constructor(data) { + super((data instanceof Uint8Array) && data.byteLength > 0 ? data : undefined); + } + All(sql, params, limit) { + return this.fetchAll(sql,params,limit); + } + One(sql, params) { + return this.fetch(sql,params); + } + fetchAll(sql,params,limit){ + let result = this.exec(sql, params); + if (result[0]) { + let data = []; + let index = 0; + for (let value of result[0].values) { + data.push(Object.fromEntries(value.map((v, k) => [result[0].columns[k], v]))); + index+=1; + if (limit===index) break; + } + if(limit==1) return data[0]; + return data; + } + } + fetch(sql,params){ + return this.fetchAll(sql, params, 1); + } + fetchColumn(sql,params,index){ + let result = Object.values(this.fetch(sql, params) || []); + if (result.length) { + return result[index || 0]; + } + } + resultFirst(sql, params) { + return this.fetchColumn(sql, params, 0); + } + str_select(table,column){ + column = column?column:'*'; + if(column instanceof Array)column = this.str_qoute(column); + return 'SELECT '+column+' FROM `'+table+'` '; + } + str_where(keys) { + return keys.length ? ' WHERE ' +this.str_set(keys,' AND '): ''; + } + str_param(keys){ + return keys.length?this.str_set(keys,' , '):''; + } + str_qoute(keys){ + if(keys instanceof Array){ + return Array.from(keys,key=>this.str_qoute(key)).join(' , '); + } + return '`'+keys+'`'; + } + str_set(keys,sp){ + if(keys instanceof Array&&keys.length){ + return Array.from(keys,key=>this.str_set(key)).join(sp||' , '); + }else if(typeof keys === 'string'){ + return this.str_qoute(keys)+' = ? '; + } + return ''; + } + str_var(size){ + return Array.from(''.padEnd(size,'?')).join(','); + } + str_insert(table){ + return 'INSERT INTO '+this.str_qoute(table)+' '; + } + str_delete(table){ + return 'DELETE FROM '+this.str_qoute(table)+' '; + } + str_update(table){ + return 'UPDATE '+this.str_qoute(table)+' SET '; + } + selectAll(table, where,limit,column) { + const keys = where&&Object.keys(where)||[]; + const values = where&&Object.values(where)||[]; + return this.fetchAll(this.str_select(table,column)+ this.str_where(keys),values,limit); + } + selectOne(table, where,column) { + return this.selectAll(table,where,1,column); + } + selectCount(table, where) { + const result = this.selectOne(table,where,'count(*)'); + return result ? Object.values(result)[0]:0; + } + selectSQL(table,sql,param,column){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.fetchAll(this.str_select(table,column)+sql,param); + } + fetchSQL(table,sql,param,column){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.fetch(this.str_select(table,column)+sql,param); + } + selectColumnSQL(table,sql,param,column,index){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.fetchColumn(this.str_select(table,column)+sql,param,index); + } + selectCountSQL(table,sql,param){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.fetchColumn(this.str_select(table,'count(*)')+sql,param); + } + deleteSQL(table,sql,param){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.exec(this.str_delete(table)+sql,param); + } + insertSQL(table,sql,param){ + sql = sql?sql:''; + param = param instanceof Array?param:[]; + return this.exec(this.str_insert(table)+sql,param); + } + delete(table, where) { + const keys = where&&Object.keys(where); + const values = where&&Object.values(where)||[]; + this.exec(this.str_delete(table) + this.str_where(keys) + ';',values); + } + updateJson(table, data, where) { + const keys = where&&Object.keys(where); + const values = where&&Object.values(where)||[]; + const param = Object.values(data); + if(values.length)param.push(...values); + return this.exec(this.str_update(table)+this.str_param(Object.keys(data)) + this.str_where(keys) + ' ;', param); + } + insertJson(table, data, where) { + if(where)this.delete(table,where); + const param = Object.values(data); + return this.exec(this.str_insert(table)+' ( '+this.str_qoute(Object.keys(data))+' ) VALUES ( ' + this.str_var(param.length)+ ' ) ',param); + } + createTable(table,list) { + let keys = typeof list === 'string' ? list : Array.from(Object.entries(list), s => '`' + s[0] + '` ' + s[1]).join(','); + return this.exec(`CREATE TABLE IF NOT EXISTS \`${table}\` (${keys});`); + } + createList(tablelist) { + return Array.from(Object.entries(tablelist) || [], e =>this.createTable(e[0],e[1])); + } + resultTable() { + let result = this.exec(this.str_select('sqlite_master','`name`')+this.str_where(['type']), ['table']); + if (result[0]) { + return Array.from(result[0].values, e => e[0]); + } + } + resultSql() { + let result = this.exec(this.str_select('sqlite_master',['name','sql'])+this.str_where(['type']), ['table']); + if (result[0]) { + return Object.fromEntries(result[0].values) + } + } + } + self.SQLite3 = SQLite3; + back(SQLite3); +}); \ No newline at end of file diff --git a/examples/ForWorker/WorkerApp.js b/examples/ForWorker/WorkerApp.js new file mode 100644 index 00000000..1edf489b --- /dev/null +++ b/examples/ForWorker/WorkerApp.js @@ -0,0 +1,402 @@ +class WorkerApp { + feedback = new Map; + methods = new Map; + shareports = []; + isLocal = /(127\.0\.0\.1|localhost|local\.nenge\.net)/.test(location.host) || /(cdn|npm)/.test(location.host); + cache_name = 'cache-worker'; + idb_name = 'worker-datas'; + constructor(name) { + this.idb_table = name || 'files'; + this.callFunc('get_root'); + } + isOrigin(url) { + return url.indexOf(location.origin) !== -1 + } + isMethod(method) { + return this.methods && this.methods.has(method); + } + callMethod(method, ...arg) { + if (this.isMethod(method)) { + return this.methods.get(method).apply(this, arg); + } + } + isFeedback(method) { + return this.feedback && this.feedback.has(method); + } + async callFeedback(method, ...arg) { + if (this.isFeedback(method)) { + let result = this.feedback.get(method).apply(this, arg); + if (result instanceof Promise) { + result = await result; + } + this.feedback.delete(workerId); + return result; + } + } + callFunc(method, ...arg) { + const func = this.functions.get(method); + if (func instanceof Function) { + return func.apply(this, arg); + } + return func; + } + get state() { + return true; + } + set state(bool) { + + } + onRun() { + if (self.postMessage) { + self.addEventListener('message', e => this.onMessage(e)); + this.callFunc('onInitialized',()=>this.callFunc('complete',self)); + } else { + self.addEventListener('connect',e => this.callFunc('set_share_port', e)); + } + } + /** + * @param {MessageEvent} e + * @returns + */ + async onMessage(e) { + const data = e.data; + const port = e.source || e.target; + if (data && data.constructor === Object) { + if (await this.onMethodBack(data, port)) return; + if (this.onFeedBack(data, port)) return; + } + if (this.onMedthod instanceof Function) return this.onMedthod(data, port); + } + onFeedBack(data, port) { + const id = data.workerId || data.id; + if (this.isFeedback(id)) { + this.callFeedback(id, data, port); + return true; + } + } + async onMethodBack(data, port) { + const method = data.method; + if (this.isMethod(method)) { + const result = await this.callMethod(method, data, port); + const transf = []; + if (result !== undefined) { + if (result.byteLength) { + transf.push(result.buffer || result); + } + if (data.id) { + port.postMessage({ id: data.id, result }, transf); + } + if (data.workerId) { + port.postMessage({ workerId: data.workerId, result }, transf); + } + } + return true; + } + } + async getMessage(port, result, method) { + return await this.getFeedback(port, { result, method }); + } + addFeedback(id, back, error) { + this.feedback.set(id, function (data) { + if (data.error && error instanceof Function) return error(data.error); + if (back instanceof Function) return back(data.result); + }); + } + async getFeedback(port, result, transf) { + return new Promise((back, error) => { + const workerId = this.callFunc('uuid'); + this.addFeedback(workerId, back, error); + result.workerId = workerId; + port.postMessage(result, transf); + }); + } + /** + * 写入内容 + * @param {string} name + * @param {*} contents + * @param {string|undefined}} mime + * @param {string|undefined} cachename + * @returns + */ + async cache_write(name, contents, mime, cachename) { + return await this.callFunc('cache_write', name, contents, mime, cachename); + } + /** + * 读取内容 + * @param {string} name + * @param {string|undefined} type + * @param {string|undefined} cachename + * @returns + */ + async cache_read(name, type, cachename) { + return await this.callFunc('cache_read', name, type, cachename); + } + async cache_has(name, cachename) { + return await this.callFunc('cache_has', name, cachename); + } + async unzip(file, password) { + if (typeof file === 'string') { + let response = await fetch(file); + if (!response || response.status != 200) { + throw response.statusText; + } + file = await response.blob(); + } + return new Promise((back, error) => { + const work = new Worker(this.worker_root + 'WorkerAppZip.js'); + work.addEventListener('message', event => { + const data = event.data; + const work = event.target; + if (data.workerId && data.password != undefined) { + work.postMessage({ result: false, workerId: data.workerId }); + } else if (data.ready === true) { + back(data.result); + } else if (data.error) { + error(data.error); + work.terminate(); + } + }); + work.postMessage({ method: 'unpack', id: 2, result: file, password, mode: true }); + }); + } + async hasItem(name) { + return await this.callFunc('idb_hasItem', name); + } + async getItem(name) { + return await this.callFunc('idb_getItem', name); + } + async setItem(name, contents) { + return await this.callFunc('idb_setItem', name, contents); + } + functions = new Map(Object.entries({ + /** + * 写入内容 + * @param {string} name + * @param {*} contents + * @param {string|undefined}} mime + * @param {string|undefined} cachename + * @returns + */ + async cache_write(name, contents, mime, cachename) { + let type; + switch (mime) { + case 'html': + case 'text': + case 'javascript': + type = 'text/' + mime; + break; + case 'png': + case 'webp': + type = 'image/' + mime; + break; + case 'sqlite3': + type = 'application/vnd.' + mime; + break; + default: + type = mime && mime.split('/').length > 1 ? mime : 'application/octet-stream'; + break; + } + return this.callFunc('cache_put', name, new File([contents], name, { type }), cachename); + }, + /** + * 读取内容 + * @param {string} name + * @param {string|undefined} type + * @param {string|undefined} cachename + * @returns + */ + async cache_read(name, type, cachename) { + let response = await this.callFunc('cache_response', name, cachename); + if (response instanceof Response) { + switch (type) { + case 'text': + return response.text(); + break; + case 'json': + return response.json(); + break; + case 'blob': + return response.blob(); + break; + case 'buffer': + return response.arrayBuffer(); + break; + default: + return new Uint8Array(await response.arrayBuffer()); + break; + } + } + return; + }, + async cache_has(name, cachename) { + return await this.callFunc('cache_response', name, cachename) instanceof Response ? true : false; + }, + + /** + * 读取Response内容 + * @param {string} name + * @param {string|undefined} cachename + * @returns + */ + async cache_response(name, cachename) { + const cache = await this.callFunc('cache_open', cachename); + return cache && cache.match(location.origin + '/' + name); + }, + /** + * 删除Response内容 + * @param {string} name + * @param {string|undefined} cachename + * @returns + */ + async cache_remove(name, cachename) { + const cache = await this.callFunc('cache_open', cachename); + return cache ? cache.delete(location.origin + '/' + name) : false; + }, + async cache_open(cachename, write) { + const cacheName = cachename || this.cache_name; + if (write || await caches.has(cacheName)) { + return caches.open(cacheName); + } + }, + /** + * 缓存储存 写入BLOB + * @param {string} name + * @param {Blob} file + * @param {string|undefined} cachename + * @returns + */ + async cache_put(name, file, cachename) { + const cache = await await this.callFunc('cache_open', cachename, !0); + return await cache.put( + location.origin + '/' + name, + new Response( + file, + { + status: 200, + statusText: 'ok', + headers: { + 'Content-Length': file.size, + 'content-type': file.type, + } + } + ) + ),true; + }, + async idb_open(version) { + if (this.idb instanceof Promise) return await this.idb; + if (!this.idb) { + this.idb = new Promise(resolve => { + let req = indexedDB.open(this.idb_name, version); + req.addEventListener("upgradeneeded", e => { + const db = req.result; + if (!db.objectStoreNames.contains(this.idb_table)) { + const store = db.createObjectStore(this.idb_table); + store.createIndex('timestamp', 'timestamp', { "unique": false }); + } + }, { once: true }); + req.addEventListener('success', async e => { + const db = req.result; + if (!db.objectStoreNames.contains(this.idb_table)) { + let version = db.version += 1; + db.close(); + return resolve(await this.callFunc('idb_open', version)); + } + return resolve(db); + }, { once: true }); + }); + } + return this.idb; + }, + async idb_selectStore(ReadMode) { + const db = await this.callFunc('idb_open'); + const transaction = db.transaction([this.idb_table], ReadMode ? 'readonly' : "readwrite"); + return transaction.objectStore(this.idb_table); + }, + async idb_readyOnly() { + return this.callFunc('idb_selectStore', !0); + }, + async idb_readyWrite() { + return this.callFunc('idb_selectStore'); + }, + async idb_readItem(name) { + return new Promise(async (resolve, reject) => { + const transaction = await this.callFunc('idb_readyOnly'); + const request = transaction.get(name); + request.onsuccess = function () { + resolve(this.result); + } + request.onerror = function (e) { + reject(this.result); + } + }); + + }, + async idb_hasItem(name) { + return new Promise(async (resolve, reject) => { + const transaction = await this.callFunc('idb_readyOnly'); + const request = transaction.getKey(name); + request.onsuccess = function () { + resolve(this.result == name); + } + request.onerror = function (e) { + reject(this.result); + } + }); + }, + async idb_getItem(name) { + const result = await this.callFunc('idb_readItem', name); + return result && result.contents || result; + }, + async idb_setItem(name, contents) { + return new Promise(async (resolve, reject) => { + const transaction = await this.callFunc('idb_readyWrite'); + const request = transaction.put({ contents, timestamp: new Date }, name); + request.onsuccess = function () { + resolve(this.result) + } + request.onerror = function (e) { + reject(this.result); + } + }); + }, + async idb_removeItem(name) { + return new Promise(async (resolve, reject) => { + const transaction = await this.callFunc('idb_readyWrite'); + const request = transaction.delete(name); + request.onsuccess = function () { + resolve(this.result == name); + } + request.onerror = function (e) { + reject(this.result); + } + }); + }, + async idb_fetch(url) { + let contents = await this.callFunc('getItem', url); + if (!contents) { + let response = await fetch(url); + if (response && response.status == 200) { + contents = /\.wasm$/.test(url) ? new Uint8Array(await response.arrayBuffer()) : await response.blob(); + this.setItem(url, contents); + } else { + throw 'file error'; + } + } + return contents; + }, + get_root() { + this.worker_root = self.location.href.split('/').slice(0, -1).join('/') + '/'; + this.js_root = self.location.href.split('/').slice(0, -2).join('/') + '/'; + }, + uuid() { + return crypto ? crypto.randomUUID() : btoa(performance.now() + Math.random()); + }, + async set_share_port(e, fn) { + e.source.onmessage = e => this.onMessage(e); + this.callFunc('onInitialized',()=>this.callFunc('complete',e.source)); + }, + complete(port){ + port.postMessage('complete'); + } + })); +} \ No newline at end of file diff --git a/examples/ForWorker/WorkerAppSQLite.js b/examples/ForWorker/WorkerAppSQLite.js new file mode 100644 index 00000000..75ec3074 --- /dev/null +++ b/examples/ForWorker/WorkerAppSQLite.js @@ -0,0 +1,165 @@ +importScripts('./WorkerApp.js'); +importScripts('./changeWorker.js'); +importScripts('./SQLite3.js'); +const AppSQL = new class WorkerAppSQLite extends WorkerApp { + constructor() { + super('sql-lite'); + const App = this; + App.datafile = 'data.sqlite3'; + App.functions.set('onInitialized', async function (back) { + await SQLite3Ready; + back(true); + }); + App.onRun(); + } + methods = new Map( + Object.entries({ + isCreate(data, port) { + return this.database instanceof self.SQLite3; + }, + async setFile(data, port) { + this.datafile = data.result; + return await this.callMethod('isFile'); + }, + async isFile() { + return await this.cache_has(this.datafile); + + }, + async install(data) { + this.callMethod('SQLite_setMethod'); + if (data.result === true) { + let u8 = await this.cache_read(this.datafile); + if (u8 instanceof Uint8Array && u8.byteLength>1) { + let mime2 = new TextDecoder().decode(u8.slice(0, 6)); + if (mime2 != 'SQLite') { + u8 = undefined; + } + }else{ + u8 = undefined; + } + this.database = new self.SQLite3(u8); + if (!u8) return false; + } else if (data.result && typeof data.result === 'string') { + let response = await fetch(data.result).catch(e => undefined); + if (response && response.status == 200) response = new Uint8Array(await response.arrayBuffer()); + this.database = new self.SQLite3(response); + return response ? true : false; + } else { + this.database = new self.SQLite3(data.result); + return data.result ? true : false; + } + return true; + }, + async reInstall(data) { + this.callMethod('database_close'); + if (data.result && data.result.byteLength) { + this.database = new self.SQLite3(data.result); + } else { + let u8 = await this.cache_read(this.datafile); + this.database = new self.SQLite3(u8); + } + return true; + }, + SQLite_setMethod() { + ['run', 'exec'].concat(Reflect.ownKeys(self.SQLite3.prototype)).forEach(v => { + if(v=>v.indexOf('_')===-1&&v!='constructor'){ + this.methods.set(v, new Function('data', 'port', 'return this.database.' + v + '.apply(this.database,data.result instanceof Array?data.result:[data.result])')); + } + }) + }, + publicMethod() { + return Array.from(this.methods.keys()).filter(v=>v.indexOf('_')===-1); + }, + async closeworker(data, port) { + port.postMessage({ + id: data.id, + result: true + }); + self.close(); + throw 'close'; + }, + async savedata(){ + return await this.cache_write(this.datafile, this.database.export(), 'sqlite3')?true:false; + }, + database_close(){ + this.database&&this.database.close(); + }, + async save2exit(data, port) { + await this.callMethod('savedata'); + this.callMethod('database_close'); + port.postMessage({ + id: data.id, + result: true + }); + self.close(); + throw 'close'; + }, + async exitworker(data, port) { + this.callMethod('database_close'); + port.postMessage({ + id: data.id, + result: true + }); + self.close(); + throw 'close'; + }, + clear2exit(data, port) { + this.callFunc('cache_remove', this.datafile); + this.callMethod('database_close'); + port.postMessage({ + id: data.id, + result: true + }); + self.close(); + throw 'close'; + }, + async importFile(data, port) { + console.log(data); + const file = data.result; + const mode = data.mode; + const tablelist = data.tablelist; + const password = data.password; + const mime = await (file.slice(0, 4).text()); + const keylist = Object.keys(tablelist.data); + if (mime == 'PK') { + const datas = await this.unzip(file,password); + if (datas && datas.size) { + for (let item of datas) { + await this.callMethod('import_sql_buf', item[1], mode, keylist); + } + } + } else { + await this.callMethod('import_sql_buf', new Uint8Array(await file.arrayBuffer()), mode, keylist); + } + return await this.callMethod('savedata'); + }, + async import_sql_buf(buf, mode, keylist) { + let mime = new TextDecoder().decode(buf.slice(0, 6)); + if (mime == 'SQLite') { + await this.cache_write(sqlfile, file, 'sqlite3'); + } else if (mime.charAt(0) == '{' || mime.charAt(0) == '[') { + let json = JSON.parse(new TextDecoder().decode(buf)); + if (json && json.constructor === Object) { + json = Object.values(json); + } + this.callMethod('import_sql_json', json, mode, keylist); + } + + }, + import_sql_json(json, mode, keylist) { + let keys = mode === 1 ? keylist.slice(1) : keylist; + let sqlstr = 'INSERT INTO `data` (' + keys.map(v => '`' + v + '`').join(',') + ') VALUES (' + (keys.map(v => '?').join(',')) + ')'; + for (let item of json) { + const sqlarr = []; + keys.forEach(v => { + sqlarr.push(item[v] || '') + }); + if (mode === 0 && item['id']) { + this.database.run('DELETE FROM `data` WHERE `id` = ?', [item['id']]); + } + this.database.run(sqlstr, sqlarr); + } + } + }) + ); +} \ No newline at end of file diff --git a/examples/ForWorker/WorkerAppZip.js b/examples/ForWorker/WorkerAppZip.js new file mode 100644 index 00000000..406f05c5 --- /dev/null +++ b/examples/ForWorker/WorkerAppZip.js @@ -0,0 +1,212 @@ +importScripts('./WorkerApp.js'); +importScripts('https://registry.npmmirror.com/@zip.js/zip.js/2.7.47/files/dist/zip.min.js'); +new class WorkerAppZip extends WorkerApp { + constructor() { + super('zip-worker'); + this.onRun(); + } + /** + * 创建GB2312编码集 + * @returns + */ + GB_byte() { + let ranges = [ + [0xA1, 0xA9, 0xA1, 0xFE], + [0xB0, 0xF7, 0xA1, 0xFE], + [0x81, 0xA0, 0x40, 0xFE], + [0xAA, 0xFE, 0x40, 0xA0], + [0xA8, 0xA9, 0x40, 0xA0], + [0xAA, 0xAF, 0xA1, 0xFE], + [0xF8, 0xFE, 0xA1, 0xFE], + [0xA1, 0xA7, 0x40, 0xA0], + ]; + let codes = new Uint16Array(23940); + let i = 0; + for (let [b1Begin, b1End, b2Begin, b2End] of ranges) { + for (let b2 = b2Begin; b2 <= b2End; b2++) { + if (b2 !== 0x7F) { + for (let b1 = b1Begin; b1 <= b1End; b1++) { + codes[i++] = b2 << 8 | b1; + } + } + } + } + let table = new Uint16Array(65536); + let gbkstr = new TextDecoder('gbk').decode(codes.buffer); + for (let i = 0; i < gbkstr.length; i++) { + table[gbkstr.codePointAt(i)] = codes[i]; + } + gbkstr = null; + codes = null; + return table; + } + /** + * 把字符变为GB2312编码二进制 + * @param {*} str + * @returns + */ + GB_encode(str) { + if (!this.gb2312) { + this.gb2312 = this.GB_byte(); + } + let buf = []; + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (code < 0x80) { + buf.push(code); + continue; + } + const gbk = this.gb2312.at(code); + if (gbk) { + buf.push(gbk, gbk >> 8); + } else if (code === 8364) { + buf.push(0x80); + } else { + buf.push(63); + } + } + return new Uint8Array(buf); + } + onMedthod(data, port) { + if (data instanceof Array || data && data.constructor === Object) { + return this.callMethod('toblob', data, port); + } else if (data && data.buffer || data instanceof Blob || data instanceof Uint8Array) { + return this.callMethod('unpack', data, port); + } + } + methods = new Map( + Object.entries({ + async unpack(data, port) { + let { result, password, id, encode, mode } = data; + const ReaderList = await new zip.ZipReader(new zip.BlobReader(result instanceof Blob ? result : new Blob([result]))).getEntries({ + decodeText(buf, encoding) { + let text = new TextDecoder('utf-8').decode(buf); + let newbuf = new TextEncoder().encode(text); + return newbuf.byteLength > buf.byteLength ? new TextDecoder('gb18030').decode(buf) : text; + } + }).catch(e => null); + if (ReaderList) { + const getData = (entry) => { + let rawPassword; + if (entry.encrypted) { + if (password) { + if (password instanceof ArrayBuffer) password = new Uint8Array(password); + rawPassword = password instanceof Uint8Array ? password : entry.filenameUTF8 == false ? this.GB_encode(password) : undefined; + } + } + return entry.getData(new zip.Uint8ArrayWriter(), { + rawPassword, + password: password && !rawPassword && entry.encrypted ? password : undefined, + onprogress: (current, total) => port.postMessage({ current, total, file: entry.filename }) + }).catch(async e => { + let msg = e.message; + if (password === false) return; + if (msg == zip.ERR_INVALID_PASSWORD || msg == zip.ERR_ENCRYPTED) { + if (password instanceof Uint8Array) password = new TextDecoder('gbk').decode(password); + password = await this.getFeedback(port, { method: 'password', password: password || '' }); + if (password) { + if (entry.filenameUTF8 == false) password = this.GB_encode(password); + return await getData(entry); + } else { + password = false; + } + } + }); + } + if (mode) { + let newresult = new Map(); + let buffers = []; + for (let entry of ReaderList) { + if (entry.directory) continue; + let data = await getData(entry); + if (data) { + newresult.set(entry.filename, data); + buffers.push(data.buffer); + } + } + port.postMessage({ + result: newresult, + ready: true, + id + }, buffers); + port.close(); + return; + } else { + for (let entry of ReaderList) { + if (entry.directory) continue; + let data = await getData(entry); + if (data) { + port.postMessage({ + result: data, + file: entry.filename, + ready: false, + id + }, [data.buffer]); + } + } + } + } + port.postMessage({ + result: false, + ready: true, + id + }); + port.close(); + }, + async toblob(data, port) { + let { result, password, id, encode, filename } = data; + if (!filename) filename = 'example'; + const zipFileWriter = new zip.BlobWriter('application/x-zip-compressed'); + const zipWriter = new zip.ZipWriter(zipFileWriter); + if (result && result.constructor === Object) result = new Map(Object.entries(result)); + await Promise.all(Array.from(result, file => { + if (file instanceof Blob) { + zipWriter.add( + file.name, + new zip.BlobReader(file), + { + onprogress: (current, total) => port.postMessage({ current, total, file: file.name, id }), + password + } + ) + } else if (file instanceof Array) { + zipWriter.add( + file[0], + new zip.BlobReader(file[1] instanceof Blob ? file[1] : new Blob([file[1]])), + { + onprogress: (current, total) => port.postMessage({ current, total, file: file[0], id }), + password + } + ) + } + })); + await zipWriter.close(); + let file = new File([await zipFileWriter.getData()], filename + '.zip'); + port.postMessage({ result: file, id }); + port.close(); + }, + async open(data, port) { + this.zipWriter = new zip.ZipWriter(new zip.BlobWriter()); + return true; + }, + async add(data, port) { + this.zipWriter = new zip.ZipWriter(new zip.BlobWriter()); + if (data.result instanceof Blob) { + await this.zipWriter.add(data.result.name, new zip.BlobReader(data.result)); + } else { + await this.zipWriter.add(data.filename, new zip.Uint8ArrayrReader(data.result)); + } + return true; + }, + async close(data, port) { + await this.zipWriter.close(); + let file = new File([await zipFileWriter.getData()], filename + '.zip', { type: 'application/x-zip-compressed' }); + port.postMessage({ result: file, id: data.id }, [file]); + }, + publicMethod() { + return ['upack', 'toblob', 'open', 'add', 'close']; + } + + }) + ); +} \ No newline at end of file diff --git a/examples/ForWorker/changeWorker.js b/examples/ForWorker/changeWorker.js new file mode 100644 index 00000000..5beaf918 --- /dev/null +++ b/examples/ForWorker/changeWorker.js @@ -0,0 +1,85 @@ +if(self.Worker){ + Object.assign(Worker.prototype,{ + methods:new Map(), + feedback:new Map(), + isFeedback(method){ + return this.feedback&&this.feedback.has(method); + }, + callFeedback(method,...arg){ + if(this.isFeedback(method)){ + return this.feedback.get(method).apply(this,arg); + } + }, + isMethod(method){ + return this.methods&&this.methods.has(method); + }, + callMethod(method,...arg){ + if(this.isMethod(method)){ + return this.methods.get(method).apply(this,arg); + } + }, + uuid(){ + return crypto?crypto.randomUUID():btoa(performance.now()+Math.random()); + }, + addFeedback(id,back,error){ + this.feedback.set(id,function(data){ + if(data.error&&error instanceof Function)return error(data.error); + if(back instanceof Function) return back(data.result); + }); + }, + async getFeedback(result,transf){ + return new Promise((back,error)=>{ + const id = this.uuid(); + this.addFeedback(id,back,error); + result.id = id; + this.postMessage(result,transf); + }); + }, + async postMethod(method,result,transf){ + return this.getFeedback({method,result},transf); + }, + async publicMethod(){ + const methods = await this.postMethod('publicMethod'); + methods&&methods.forEach(v=>{ + if(v=='constructor')return; + this.methods.set(v,new Function('...result','return this.postMethod("'+v+'",result)')) + }); + }, + async addMessage(message,transf){ + this.addEventListener('message',async function(event){ + const data = event.data; + const port = event.source || event.target; + if (data && data.constructor === Object) { + if(this.isMethod(data.method)){ + return this.callMethod(data.method,data,port); + } + const id = data.workerId||data.id; + if(this.isFeedback(id)){ + return this.callFeedback(id,data,port); + } + } + if (this.callMessage instanceof Function) return this.callMessage(data, port); + }); + if(message)this.postMessage(message,transf); + }, + setMessage(fn,message,transf){ + if(fn instanceof Function) this.callMessage = fn; + this.addMessage(message,transf); + }, + initMessage(){ + return new Promise((resolve,reject)=>{ + const error = e=>reject(e.message); + this.addEventListener('message',async function(event){ + const data = event.data; + if(data === 'complete'){ + this.removeEventListener('error',error); + this.addMessage(); + resolve(this); + } + },{once:true}); + this.addEventListener('error',error,{once:true}); + + }); + } + }); +} \ No newline at end of file