Merge branch 'dev' into release

This commit is contained in:
Kuingsmile
2024-07-07 14:27:26 +08:00
179 changed files with 27528 additions and 28978 deletions
+4
View File
@@ -3,3 +3,7 @@ test/unit/*.js
test/e2e/*.js
dist/
src/**/*.js
node_modules
dist
vue.config.js
babel.config.js
+20 -3
View File
@@ -10,7 +10,8 @@ module.exports = {
extends: [
'plugin:vue/vue3-recommended',
'@vue/standard',
'@vue/typescript'
'@vue/typescript/recommended',
'@vue/eslint-config-prettier'
],
plugins: ['@typescript-eslint'],
rules: {
@@ -20,8 +21,24 @@ module.exports = {
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/indent': ['error', 2],
'vue/no-v-html': 'off'
'@typescript-eslint/indent': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-nocheck': 'allow-with-description'
}
],
'vue/no-v-html': 'off',
'prettier/prettier': [
'error',
{},
{
usePrettierrc: true
}
]
},
parserOptions: {
parser: '@typescript-eslint/parser'
+18
View File
@@ -0,0 +1,18 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": true,
"printWidth": 120,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false,
"endOfLine": "lf"
}
-1
View File
@@ -5,7 +5,6 @@
"name": "Electron: Main",
"type": "node",
"request": "launch",
"protocol": "inspector",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
+26
View File
@@ -1,3 +1,29 @@
## :tada: 2.9.1 (2024-07-07)
### :sparkles: Features
* **custom:** add alist support ([bead8a2](https://github.com/Kuingsmile/piclist/commit/bead8a2))
* **custom:** add build in alist support and fix gallery bug ([875d034](https://github.com/Kuingsmile/piclist/commit/875d034)), closes [#218](https://github.com/Kuingsmile/piclist/issues/218)
* **custom:** optimize upload progress ([0039399](https://github.com/Kuingsmile/piclist/commit/0039399))
* **custom:** upgrade core ([6aa7311](https://github.com/Kuingsmile/piclist/commit/6aa7311))
### :bug: Bug Fixes
* **custom:** fix picbed config page css ([d7e0f56](https://github.com/Kuingsmile/piclist/commit/d7e0f56)), closes [#213](https://github.com/Kuingsmile/piclist/issues/213)
* **custom:** fix rename window bug ([0b670a8](https://github.com/Kuingsmile/piclist/commit/0b670a8)), closes [#58](https://github.com/Kuingsmile/piclist/issues/58)
* **custom:** fix s3 delete bug ([b30dc4d](https://github.com/Kuingsmile/piclist/commit/b30dc4d)), closes [#212](https://github.com/Kuingsmile/piclist/issues/212)
### :pencil: Documentation
* **custom:** add scoop-lemon as install method ([09bf847](https://github.com/Kuingsmile/piclist/commit/09bf847))
* **custom:** fix docs typo ([dc70648](https://github.com/Kuingsmile/piclist/commit/dc70648))
* **custom:** update 2.9.1 changelog ([2bdb79f](https://github.com/Kuingsmile/piclist/commit/2bdb79f))
# :tada: 2.9.0 (2024-06-14)
+7 -7
View File
@@ -5,7 +5,7 @@
1. 使用 [yarn](https://yarnpkg.com/) 安装依赖
```bash
yarn install
yarn
```
然后通过
@@ -16,18 +16,18 @@ yarn dev
启动项目。
2. 只跟 Electron 主进程相关的代码请在 `src/main` 目录下添加。只跟渲染进程相关的代码请在 `src/renderer` 目录下添加。两个进程都能使用的代码请在 `src/universal` 目录下添加。 **注意**:渲染进程没有 `Node.js` 能力,所有渲染进程需要用到 `Node.js` 模块相关的代码请在 `src/main/events/picgoCoreIPC.ts` 下添加事件进行处理。
3. 所有的跨进程事件名请在 `src/universal/events/constants.ts` 里添加。
4. 所有的全局类型定义请在 `src/universal/types/` 里添加,如果是 `enum`,请在 `src/universal/types/enum.ts` 里添加。
5. 与图床管理功能相关的代码请在 `src/main/manage``src/renderer/manage`目录下添加。
1. 只跟 Electron 主进程相关的代码请在 `src/main` 目录下添加。只跟渲染进程相关的代码请在 `src/renderer` 目录下添加。两个进程都能使用的代码请在 `src/universal` 目录下添加。
2. 所有的跨进程事件名请在 `src/universal/events/constants.ts` 里添加。
3. 所有的全局类型定义请在 `src/universal/types/` 里添加,如果是 `enum`,请在 `src/universal/types/enum.ts` 里添加。
4. 与图床管理功能相关的代码请在 `src/main/manage``src/renderer/manage`目录下添加。
## i18n
1.`public/i18n/` 下面创建一种语言的 `yml` 文件,例如 `zh-Hans.yml`。然后参考 `zh-CN.yml` 或者 `en.yml` 编写语言文件。并注意,PicList 会通过语言文件中的 `LANG_DISPLAY_LABEL` 向用户展示该语言的名称。
2.`src/universal/i18n/index.ts` 里添加一种默认语言。其中 `label` 就是语言文件中 `LANG_DISPLAY_LABEL` 的值,`value` 是语言文件名。
3. 如果是对已有语言文件进行更新,请在更新完,务必运行一遍 `yarn gen-i18n`,确保能生成正确的语言定义文件。
3. 如果是对已有语言文件进行更新,请在更新完,务必运行一遍 `yarn i18n`,确保能生成正确的语言定义文件。
## 提交代码
1. 请检查代码没有多余的注释、`console.log` 等调试代码。
2. 提交代码前,请执行命令 `git add . && yarn cz`,唤起 [代码提交规范工具](https://github.com/PicGo/bump-version)。通过该工具提交代码。
2. 提交代码前,请执行命令 `git add . && yarn cz`,唤起 [代码提交规范工具](https://github.com/Kuingsmile/node-bump-version)。通过该工具提交代码。
+7 -7
View File
@@ -5,7 +5,7 @@
1. Use [yarn](https://yarnpkg.com/) to install dependencies
```bash
yarn install
yarn
```
then pass
@@ -16,18 +16,18 @@ yarn dev
Startup project.
2. Please add code only related to the main process of Electron in the `src/main` directory. Code only related to the rendering process should be added in the `src/renderer` directory. Add code that can be used by both processes in the `src/universal` directory. **Note**: The rendering process does not have the `Node.js` capability. All rendering processes need to use `Node.js modules` related code, please add events under `src/main/events/picgoCoreIPC.ts` for processing.
3. Please add all cross-process event names in `src/universal/events/constants.ts`.
4. Please add all global type definitions in `src/universal/types/`, if it is `enum`, please add it in `src/universal/types/enum.ts`.
5. Code related to the management function of the picture bed should be added in the `src/main/manage` and `src/renderer/manage` directory.
1. Please add code only related to the main process of Electron in the `src/main` directory. Code only related to the rendering process should be added in the `src/renderer` directory. Add code that can be used by both processes in the `src/universal` directory.
2. Please add all cross-process event names in `src/universal/events/constants.ts`.
3. Please add all global type definitions in `src/universal/types/`, if it is `enum`, please add it in `src/universal/types/enum.ts`.
4. Code related to the management function of the picture bed should be added in the `src/main/manage` and `src/renderer/manage` directory.
## i18n
1. Create a language `yml` file under `public/i18n/`, for example `zh-Hans.yml`. Then refer to `zh-CN.yml` or `en.yml` to write language files. Also note that PicList will display the name of the language to the user via `LANG_DISPLAY_LABEL` in the language file.
2. Add a default language to `src/universal/i18n/index.ts`. where `label` is the value of `LANG_DISPLAY_LABEL` in the language file, and `value` is the name of the language file.
3. If you are updating an existing language file, be sure to run `yarn gen-i18n` after the update to ensure that the correct language definition file can be generated.
3. If you are updating an existing language file, be sure to run `yarn i18n` after the update to ensure that the correct language definition file can be generated.
## Submit code
1. Please check that the code has no extra comments, `console.log` and other debugging code.
2. Before submitting the code, please execute the command `git add . && yarn cz` to invoke [Code Submission Specification Tool](https://github.com/PicGo/bump-version). Submit code through this tool.
2. Before submitting the code, please execute the command `git add . && yarn cz` to invoke [Code Submission Specification Tool](https://github.com/Kuingsmile/node-bump-version). Submit code through this tool.
+4 -11
View File
@@ -75,22 +75,15 @@ To open the main window, right-click or two-finger tap the PicList icon in the t
Or right-click the PicList icon in the dock and select "Open Main Window."
## 8. 上传失败,或者是服务器出错
## 8. Upload failed, or server error
1. PicList 自带的图床都经过测试,上传出错一般都不是 PicList 自身的原因。如果你用的是 GitHub 图床请参考上面的第 7 点。
2. 检查 PicList 的日志(报错日志可以在 PicList 设置 -> 设置日志文件 -> 点击打开 后找到),看看 `[PicList Error]` 的报错信息里有什么关键信息
1. 先自行搜索 error 里的报错信息,往往你能百度或者谷歌出问题原因,不必开 issue。
2. 如果有带有 `401``403``40X` 状态码字样的,不用怀疑,就是你配置写错了,仔细检查配置,看看是否多了空格之类的。
3. 如果带有 `HttpError``RequestError``socket hang up` 等字样的说明这是网络问题,我无法帮你解决网络问题,请检查你自己的网络,是否有代理,DNS 设置是否正常等。
3. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicList 的代理设置中设置对应的HTTP代理。
4. PicList's built-in image hosting platforms have been tested, and upload errors are generally not caused by PicList itself. If you are using the GitHub image hosting platform, please refer to the 7th point above.
5. Check PicList's log (the error log can be found by clicking "Open" in PicList Settings -> Set Log File ->), and see if there is any key information in the `[PicList Error]` error message
1. PicList's built-in image hosting platforms have been tested, and upload errors are generally not caused by PicList itself. If you are using the GitHub image hosting platform, please refer to the 7th point above.
2. Check PicList's log (the error log can be found by clicking "Open" in PicList Settings -> Set Log File ->), and see if there is any key information in the `[PicList Error]` error message
1. First search for the error message in the error message by yourself, and you can often Baidu or Google the cause of the problem, so you don't need to open an issue.
2. If there are `401`, `403` and other `40X` status code words, don't doubt it, it means that your configuration is wrong, check the configuration carefully to see if there are extra spaces, etc.
3. If there are words such as `HttpError`, `RequestError`, `socket hang up`, etc., it means that this is a network problem, and I cannot help you solve the network problem. Please check your own network, whether there is a proxy, whether the DNS setting is normal, etc.
6. Usually, upload failures caused by network problems are caused by improper proxy settings. If the system proxy is turned on, it is recommended to set the corresponding HTTP proxy in the proxy settings of PicList at the same time.
3. Usually, upload failures caused by network problems are caused by improper proxy settings. If the system proxy is turned on, it is recommended to set the corresponding HTTP proxy in the proxy settings of PicList at the same time.
## 10. After installing the macOS version, there is no main interface
+10 -10
View File
@@ -135,6 +135,7 @@ docker-compose up -d
| Platform | Album cloud deletion | Cloud storage management |
| :------------------------: | :------------------: | :----------------------: |
| Built-in AList | ✔️ | ✔️ |
| SM.MS | ✔️ | ✔️ |
| Github | ✔️ | ✔️ |
| Imgur | ✔️ | ✔️ |
@@ -162,6 +163,15 @@ docker-compose up -d
[https://github.com/Kuingsmile/PicList/releases/latest](https://github.com/Kuingsmile/PicList/releases/latest)
### Scoop
Thanks to [scoop-lemon](https://github.com/hoilc/scoop-lemon), you can use Scoop to install PicList, just execute the following command:
```bash
scoop bucket add lemon https://github.com/hoilc/scoop-lemon
scoop install lemon/piclist
```
### Homebrew
MacOS users can now use Homebrew to install PicList, just execute the following command:
@@ -182,16 +192,6 @@ If the macOS system installs PicList and displays "file is damaged" or installs
After V1.4.1, all mac installation packages have been signed by my developer certificate, will not be recognized by the macOS system as "malicious software", will not appear "file is damaged" prompt.
### Mac App Store
Due to the sandbox mechanism of Mac App Store, many functions cannot be used normally, so the installation method of Mac App Store is no longer supported.
If you have purchased PicList through Mac App Store, please add my WeChat `pku_sq_ma`, I will refund you.
Thank you again for your support for PicList.
**Of course, if you don't want to pay, you can also download the free installation package through Github to install.**
## Application screenshot
![image](https://github.com/Kuingsmile/PicList/assets/96409857/1b76c0c4-753c-4d66-aa24-f805f9c2da15)
+16
View File
@@ -135,6 +135,7 @@ docker-compose up -d
| 平台 | 相册云删除 | 云存储管理 |
| :------------: | :--------: | :--------: |
| 内置AList | ✔️ | ✔️ |
| SM.MS | ✔️ | ✔️ |
| Github | ✔️ | ✔️ |
| Imgur | ✔️ | ✔️ |
@@ -162,6 +163,15 @@ docker-compose up -d
[https://github.com/Kuingsmile/PicList/releases/latest](https://github.com/Kuingsmile/PicList/releases/latest)
### Scoop
感谢[scoop-lemon](https://github.com/hoilc/scoop-lemon),你可以使用Scoop来安装PicList,只需要执行以下命令即可:
```bash
scoop bucket add lemon https://github.com/hoilc/scoop-lemon
scoop install lemon/piclist
```
### Homebrew
MacOS用户现在可以使用Homebrew来安装PicList了,只需要执行以下命令即可:
@@ -176,6 +186,12 @@ brew install piclist --cask
brew uninstall piclist
```
### Mac特殊说明
如果macOS系统安装PicList后显示“文件已损坏”或者安装后打开无响应,请升级到PicList V1.4.1或以上版本。
V1.4.1之后,所有mac安装包都已经使用我的开发者证书签名,不会被macOS系统识别为“恶意软件”,不会出现“文件已损坏”提示。
## 应用截图
![image](https://github.com/Kuingsmile/PicList/assets/96409857/1b76c0c4-753c-4d66-aa24-f805f9c2da15)
+3 -5
View File
@@ -1,6 +1,4 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
presets: ["@vue/cli-plugin-babel/preset"],
plugins: ["@babel/plugin-proposal-optional-chaining"],
};
+9 -27
View File
@@ -1,32 +1,14 @@
### ✨ Features
- 图床
- 现在`sm.ms`图床上传重复图片时,后上传的图片也支持云端删除了
- 管理
- 现在s3图床支持新建存储通
- 调整了管理页面的部分布局
- 代码高亮样式调整为stackoverflow-light
- 界面
- 现在托盘菜单会根据当前状态显示开启/关闭剪贴板监听,而不是始终显示全部菜单
- 现在托盘菜单会根据当前状态显示开启/关闭mini窗口
- 现在鼠标悬停于托盘图标时,会显示当前图床和配置名
- 移除了设置页面部分不必要的全局通知
- 优化了英文下的设置页面布局
- 性能
- 现在启动软件时会自动清空剪贴板图片缓存文件夹
- 优化了读取配置的性能表现
- 优化了多个页面的加载速度
- 升级vue等依赖至最新版本
- 新增内置AList上传支持
- 优化了设置了图片处理操作时的上传进度通知
- 优化了部分图床的i18n设置
- 修改内置兰空图床显示名,由`lsky`修改为`兰空图床`
- core版本现在使用piclist桌面版配置文件时支持设置`configName`参数
### 🐛 Bug Fixes
- 修复了从obsidan发送删除请求时,会导致软件闪退的问题
- 修复了使用`webp`插件时,上传剪贴板图片会导致软件闪退的问题
- 修复了设置了图片处理操作后,上传URL会报错的问题
- 修复了设置原始PicGo窗口大小后,重新打开设置界面时窗口大小数值没有更新的问题
- 修复了主界面菜单加载和点击时反复触发获取图床配置导致的卡顿问题
- 修复了切换语言时部分下拉框没有更新的问题
- 修复了部分翻译错误
- 管理页面
- 修复了部分图床的文件夹复制链接错误的问题
- 修复了文件浏览页面的css错误
- 修复了s3图床文件名含有特殊字符时无法删除的问题
- 修复了特定情况下开启手动重命名后会导致软件闪退的问题
- 修复了图床设置页面按钮的样式问题
- 优化了多个图床存在的重复记录错误日志的问题
+9 -27
View File
@@ -1,32 +1,14 @@
### ✨ Features
- PicBed
- Now when uploading duplicate images to `sm.ms` image bed, the later uploaded image also supports cloud deletion
- Management
- Now s3 image bed supports creating new storage channels
- Adjusted some layouts of the management page
- The code highlighting style is adjusted to stackoverflow-light
- Interface
- Now the tray menu will display the open/close clipboard monitoring according to the current status, instead of always displaying all menus
- Now the tray menu will display the open/close mini window according to the current status
- Now when the mouse hovers over the tray icon, the current image bed and configuration name will be displayed
- Removed some unnecessary global notifications on the settings page
- Optimized the layout of the settings page in English
- Performance
- Now the software will automatically clear the clipboard image cache folder when starting
- Optimized the performance of reading configuration
- Optimized the loading speed of multiple pages
- Upgrade dependencies such as vue to the latest version
- Add built-in AList upload support
- Optimize the upload progress notification when image processing operations are set
- Optimize the i18n settings of some image beds
- Modify the display name of the built-in Lan Kong image bed from `lsky` to `Lan Kong image bed`
- The core version now supports setting the `configName` parameter when using the piclist desktop version configuration file
### 🐛 Bug Fixes
- Fix the problem that the software crashes when sending a delete request from obsidan
- Fix the problem that the software crashes when uploading clipboard images using the `webp` plugin
- Fix the problem that an error occurs when uploading a URL after setting image processing operations
- Fix the problem that the window size value is not updated when the original PicGo window size is set and the setting interface is reopened
- Fix the problem that the main interface menu repeatedly triggers the lag caused by repeatedly triggering the acquisition of the image bed configuration when loading and clicking
- Fix the problem that some drop-down boxes are not updated when switching languages
- Fix some translation errors
- Management page
- Fix the problem that the folder copy link of some image beds is incorrect
- Fix the css error of the file browsing page
- Fix the problem that the s3 image bed cannot be deleted when the file name contains special characters
- Fix the problem that the software crashes under specific conditions after enabling manual renaming
- Fix the style problem of the button on the image bed setting page
- Optimize the problem of repeated recording error logs for multiple image beds
+16 -20
View File
@@ -1,6 +1,6 @@
{
"name": "piclist",
"version": "2.9.0",
"version": "2.9.1",
"author": {
"name": "Kuingsmile",
"email": "pkukuing@gmail.com"
@@ -15,23 +15,23 @@
"private": true,
"scripts": {
"build": "vue-cli-service electron:build",
"lint": "vue-cli-service lint",
"bump": "bump-version",
"cz": "git-cz",
"dev": "vue-cli-service electron:serve",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"i18n": "node ./scripts/gen-i18n-types.js",
"link": "node ./scripts/link.js",
"lint": "vue-cli-service lint",
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts",
"lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx,.vue src/",
"ncu": "node ./scripts/check-dep.js",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-r2.js",
"upload-beta": "node ./scripts/upload-beta.js",
"link": "node ./scripts/link.js",
"sha256": "node ./scripts/gen-sha256.js",
"ncu": "node ./scripts/check-dep.js",
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/background.ts"
"upload-beta": "node ./scripts/upload-beta.js",
"upload-dist": "node ./scripts/upload-dist-to-r2.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.421.0",
@@ -52,7 +52,7 @@
"cos-nodejs-sdk-v5": "^2.12.5",
"dexie": "^3.2.4",
"electron-updater": "^6.1.4",
"element-plus": "2.7.4",
"element-plus": "2.7.6",
"epipebomb": "^1.0.0",
"fast-xml-parser": "^4.3.2",
"form-data": "^4.0.0",
@@ -67,7 +67,7 @@
"multer": "^1.4.5-lts.1",
"node-ssh-no-cpu-features": "^2.0.0",
"nodejs-file-downloader": "^4.12.1",
"piclist": "^1.8.10",
"piclist": "^1.9.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"proxy-agent": "^5.0.0",
@@ -79,15 +79,14 @@
"upyun": "^3.4.6",
"uuid": "^9.0.1",
"video.js": "^8.6.1",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"vue": "^3.4.30",
"vue-router": "^4.4.0",
"vue3-lazyload": "^0.3.8",
"vue3-photo-preview": "^0.3.0",
"webdav": "^5.3.1",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
"@types/video.js": "^7.3.58",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@electron/notarize": "^2.1.0",
"@types/ali-oss": "^6.16.11",
@@ -103,32 +102,29 @@
"@types/tunnel": "^0.0.7",
"@types/upyun": "^3.4.3",
"@types/uuid": "^9.0.8",
"@types/video.js": "^7.3.58",
"@types/write-file-atomic": "^4.0.3",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-dom": "^3.4.27",
"@vue/runtime-dom": "^3.4.30",
"conventional-changelog": "^5.1.0",
"cz-customizable": "^7.0.0",
"dotenv": "^16.3.1",
"dpdm": "^3.14.0",
"electron": "^22.3.27",
"eslint": "^8.54.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.2.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"husky": "^3.1.0",
"node-bump-version": "^1.0.2",
"node-loader": "^2.0.0",
"npm-check-updates": "^16.14.20",
"prettier": "^3.3.2",
"stylus": "^0.59.0",
"stylus-loader": "^7.1.3",
"typescript": "^4.9.5",
+1 -1
View File
@@ -197,7 +197,7 @@ SETTINGS_PLUGIN_INSTALL_MIRROR: Mirror for Plugin Install
SETTINGS_CURRENT_VERSION: Current Version
SETTINGS_NEWEST_VERSION: Newest Version
SETTINGS_GETING: Getting...
SETTINGS_TIPS_HAS_NEW_VERSION: PicList has a new version, please click confirm to open download page
SETTINGS_TIPS_HAS_NEW_VERSION: PicList has a new version, please click confirm to restart
SETTINGS_LOG_FILE_PATH: Log File Path
SETTINGS_CONFIG_FILE_PATH: Config File Path
SETTINGS_LOG_FILE: Log File
+1 -1
View File
@@ -199,7 +199,7 @@ SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安装镜像
SETTINGS_CURRENT_VERSION: 当前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在获取中
SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,请点击确定打开下载页面
SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,请点击确定重启触发更新
SETTINGS_LOG_FILE_PATH: 日志文件路径
SETTINGS_CONFIG_FILE_PATH: 配置文件路径
SETTINGS_LOG_FILE: 常规日志文件
+1 -1
View File
@@ -197,7 +197,7 @@ SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安裝鏡像
SETTINGS_CURRENT_VERSION: 當前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在取得中
SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,請點擊確定開啟下載頁面
SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,請點擊確定重启應用
SETTINGS_LOG_FILE_PATH: 記錄檔案路徑
SETTINGS_CONFIG_FILE_PATH: 設定檔案路徑
SETTINGS_LOG_FILE: 記錄檔案
+1
View File
@@ -51,4 +51,5 @@ app.use(pinia)
app.use(hljsVuePlugin)
app.use(VueVideoPlayer)
app.mount('#app')
initTalkingData()
+13 -19
View File
@@ -1,21 +1,12 @@
import axios from 'axios'
import {
app,
clipboard,
dialog,
shell
} from 'electron'
import { app, clipboard, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { gte, lte } from 'semver'
import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/utils/common'
import {
IRemoteNoticeActionType,
IRemoteNoticeTriggerCount,
IRemoteNoticeTriggerHook
} from '#/types/enum'
import { IRemoteNoticeActionType, IRemoteNoticeTriggerCount, IRemoteNoticeTriggerHook } from '#/types/enum'
// for test
const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'
@@ -41,7 +32,10 @@ class RemoteNoticeHandler {
fs.writeFileSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, JSON.stringify({}))
}
try {
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(REMOTE_NOTICE_LOCAL_STORAGE_PATH, 'utf8')
const localCountStorage: IRemoteNoticeLocalCountStorage = fs.readJSONSync(
REMOTE_NOTICE_LOCAL_STORAGE_PATH,
'utf8'
)
this.remoteNoticeLocalCountStorage = localCountStorage
} catch (e) {
this.remoteNoticeLocalCountStorage = localCountStorage
@@ -57,11 +51,11 @@ class RemoteNoticeHandler {
private async getRemoteNoticeInfo(): Promise<IRemoteNotice | null> {
try {
const noticeInfo = await axios({
const noticeInfo = (await axios({
method: 'get',
url: REMOTE_NOTICE_URL,
responseType: 'json'
}).then(res => res.data) as IRemoteNotice
}).then(res => res.data)) as IRemoteNotice
return noticeInfo
} catch {
return null
@@ -143,12 +137,14 @@ class RemoteNoticeHandler {
break
case IRemoteNoticeActionType.SHOW_MESSAGE_BOX: {
const currentWindow = windowManager.getAvailableWindow()
dialog.showMessageBox(currentWindow, {
dialog
.showMessageBox(currentWindow, {
title: action.data?.title || '',
message: action.data?.content || '',
type: 'info',
buttons: action.data?.buttons?.map(item => item.label) || ['Yes']
}).then(res => {
})
.then(res => {
const button = action.data?.buttons?.[res.response]
if (button?.type === 'cancel') {
// do nothing
@@ -198,6 +194,4 @@ class RemoteNoticeHandler {
const remoteNoticeHandler = new RemoteNoticeHandler()
export {
remoteNoticeHandler
}
export { remoteNoticeHandler }
@@ -1,7 +1,4 @@
import {
globalShortcut
} from 'electron'
import { globalShortcut } from 'electron'
import shortKeyService from 'apis/app/shortKey/shortKeyService'
import GuiApi from 'apis/gui'
@@ -72,7 +69,12 @@ class ShortKeyHandler {
}
}
private registerShortKey (config: IShortKeyConfig | IPluginShortKeyConfig, command: string, handler: IShortKeyHandler, writeFlag: boolean) {
private registerShortKey(
config: IShortKeyConfig | IPluginShortKeyConfig,
command: string,
handler: IShortKeyHandler,
writeFlag: boolean
) {
shortKeyService.registerCommand(command, handler)
if (config.key) {
globalShortcut.register(config.key, () => {
+71 -21
View File
@@ -75,7 +75,13 @@ export function createMenu () {
label: 'PicList',
submenu: [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } }
{
label: T('RELOAD_APP'),
click() {
app.relaunch()
app.exit(0)
}
}
]
},
{ label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
@@ -93,9 +99,7 @@ export function createMenu () {
},
{
label: T('QUIT'),
submenu: [
{ label: T('QUIT'), role: 'quit' }
]
submenu: [{ label: T('QUIT'), role: 'quit' }]
}
])
Menu.setApplicationMenu(appMenu)
@@ -104,7 +108,8 @@ export function createMenu () {
export function createContextMenu() {
const ClipboardWatcher = clipboardPoll
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const isMiniWindowVisible = windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible()
const isMiniWindowVisible =
windowManager.has(IWindowList.MINI_WINDOW) && windowManager.get(IWindowList.MINI_WINDOW)!.isVisible()
const startWatchClipboard = () => {
db.set(configPaths.settings.isListeningClipboard, true)
@@ -125,18 +130,44 @@ export function createContextMenu () {
if (process.platform === 'darwin' || process.platform === 'win32') {
const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
const template: Array<MenuItemConstructorOptions | MenuItem> = [
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('CHOOSE_DEFAULT_PICBED'), type: 'submenu', submenu },
{ label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard },
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard },
{ label: T('RELOAD_APP'), click () { app.relaunch(); app.exit(0) } },
{
label: T('START_WATCH_CLIPBOARD'),
click: startWatchClipboard,
visible: !isListeningClipboard
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click: stopWatchClipboard,
visible: isListeningClipboard
},
{
label: T('RELOAD_APP'),
click() {
app.relaunch()
app.exit(0)
}
},
{ label: T('QUIT'), role: 'quit' }
]
if (process.platform === 'win32') {
template.splice(2, 0,
{ label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible },
{ label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible }
template.splice(
2,
0,
{
label: T('OPEN_MINI_WINDOW'),
click() {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
},
{
label: T('HIDE_MINI_WINDOW'),
click: hideMiniWindow,
visible: isMiniWindowVisible
}
)
}
contextMenu = Menu.buildFromTemplate(template)
@@ -150,10 +181,28 @@ export function createContextMenu () {
contextMenu = Menu.buildFromTemplate([
{ label: T('OPEN_MAIN_WINDOW'), click: openMainWindow },
{ label: T('OPEN_MINI_WINDOW'), click () { openMiniWindow(false) }, visible: !isMiniWindowVisible },
{ label: T('HIDE_MINI_WINDOW'), click: hideMiniWindow, visible: isMiniWindowVisible },
{ label: T('START_WATCH_CLIPBOARD'), click: startWatchClipboard, visible: !isListeningClipboard },
{ label: T('STOP_WATCH_CLIPBOARD'), click: stopWatchClipboard, visible: isListeningClipboard },
{
label: T('OPEN_MINI_WINDOW'),
click() {
openMiniWindow(false)
},
visible: !isMiniWindowVisible
},
{
label: T('HIDE_MINI_WINDOW'),
click: hideMiniWindow,
visible: isMiniWindowVisible
},
{
label: T('START_WATCH_CLIPBOARD'),
click: startWatchClipboard,
visible: !isListeningClipboard
},
{
label: T('STOP_WATCH_CLIPBOARD'),
click: stopWatchClipboard,
visible: isListeningClipboard
},
{
label: T('ABOUT'),
click() {
@@ -260,9 +309,7 @@ export function createTray (tooltip: string) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
const rawInput = cloneDeep(files)
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)!
const imgs = await uploader
.setWebContents(trayWindow.webContents)
.upload(files)
const imgs = await uploader.setWebContents(trayWindow.webContents).upload(files)
const deleteLocalFile = db.get(configPaths.settings.deleteLocalFile) || false
if (imgs !== false) {
const pasteText: string[] = []
@@ -270,8 +317,11 @@ export function createTray (tooltip: string) {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
+23 -12
View File
@@ -1,7 +1,4 @@
import {
Notification,
WebContents
} from 'electron'
import { Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
@@ -19,7 +16,10 @@ import { IPasteStyle, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
const handleClipboardUploading = async (): Promise<false | ImgInfo[]> => {
const useBuiltinClipboard = db.get(configPaths.settings.useBuiltinClipboard) === undefined ? true : !!db.get(configPaths.settings.useBuiltinClipboard)
const useBuiltinClipboard =
db.get(configPaths.settings.useBuiltinClipboard) === undefined
? true
: !!db.get(configPaths.settings.useBuiltinClipboard)
const win = windowManager.getAvailableWindow()
if (useBuiltinClipboard) {
return await uploader.setWebContents(win!.webContents).uploadWithBuildInClipboard()
@@ -33,8 +33,11 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
if (img.length > 0) {
const trayWindow = windowManager.get(IWindowList.TRAY_WINDOW)
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))
const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -75,7 +78,10 @@ export const uploadClipboardFiles = async (): Promise<IStringKeyMap> => {
}
}
export const uploadChoosedFiles = async (webContents: WebContents, files: IFileWithPath[]): Promise<IStringKeyMap[]> => {
export const uploadChoosedFiles = async (
webContents: WebContents,
files: IFileWithPath[]
): Promise<IStringKeyMap[]> => {
const input = files.map(item => item.path)
const rawInput = cloneDeep(input)
const imgs = await uploader.setWebContents(webContents).upload(input)
@@ -86,14 +92,19 @@ export const uploadChoosedFiles = async (webContents: WebContents, files: IFileW
const pasteText: string[] = []
for (let i = 0; i < imgs.length; i++) {
if (deleteLocalFile) {
fs.remove(rawInput[i]).then(() => {
fs.remove(rawInput[i])
.then(() => {
picgo.log.info(`delete local file: ${rawInput[i]}`)
}).catch((err: Error) => {
})
.catch((err: Error) => {
picgo.log.error(err)
})
}
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
+8 -16
View File
@@ -1,11 +1,5 @@
import dayjs from 'dayjs'
import {
BrowserWindow,
clipboard,
ipcMain,
Notification,
WebContents
} from 'electron'
import { BrowserWindow, clipboard, ipcMain, Notification, WebContents } from 'electron'
import fs from 'fs-extra'
import util from 'util'
import path from 'path'
@@ -21,17 +15,13 @@ import logger from '@core/picgo/logger'
import { T } from '~/i18n'
import { showNotification, getClipboardFilePath, calcDurationRange } from '~/utils/common'
import {
GET_RENAME_FILE_NAME,
RENAME_FILE_NAME,
TALKING_DATA_EVENT
} from '#/events/constants'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME, TALKING_DATA_EVENT } from '#/events/constants'
import { ICOREBuildInEvent, IWindowList } from '#/types/enum'
import { configPaths } from '#/utils/configPaths'
import { CLIPBOARD_IMAGE_FOLDER } from '#/utils/static'
const waitForRename = (window: BrowserWindow, id: number): Promise<string | null> => {
return new Promise((resolve) => {
return new Promise(resolve => {
const windowId = window.id
ipcMain.once(`${RENAME_FILE_NAME}${id}`, (_: Event, newName: string) => {
resolve(newName)
@@ -91,7 +81,8 @@ class Uploader {
const rename = db.get(configPaths.settings.rename)
const autoRename = db.get(configPaths.settings.autoRename)
if (autoRename || rename) {
await Promise.all(ctx.output.map(async (item, index) => {
await Promise.all(
ctx.output.map(async (item, index) => {
let name: undefined | string | null
let fileName: string | undefined
if (autoRename) {
@@ -102,7 +93,7 @@ class Uploader {
if (rename) {
const window = windowManager.create(IWindowList.RENAME_WINDOW)!
logger.info('create rename window')
ipcMain.on(GET_RENAME_FILE_NAME, (evt) => {
ipcMain.on(GET_RENAME_FILE_NAME, evt => {
try {
if (evt.sender.id === window.webContents.id) {
logger.info('rename window ready, wait for rename...')
@@ -115,7 +106,8 @@ class Uploader {
name = await waitForRename(window, window.webContents.id)
}
item.fileName = name || fileName
}))
})
)
}
}
})
+12 -11
View File
@@ -1,25 +1,26 @@
const isDevelopment = process.env.NODE_ENV !== 'production'
export const MANUAL_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#documents`
export const MANUAL_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#documents`
: 'picgo://./index.html#documents'
export const MINI_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#mini-page`
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#mini-page`
: 'picgo://./index.html#mini-page'
export const RENAME_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#rename-page`
export const RENAME_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#rename-page`
: 'picgo://./index.html#rename-page'
export const SETTING_WINDOW_URL = isDevelopment
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#main-page/upload`
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#main-page/upload`
: 'picgo://./index.html#main-page/upload'
export const TRAY_WINDOW_URL = isDevelopment
? (process.env.WEBPACK_DEV_SERVER_URL as string)
: 'picgo://./index.html'
export const TRAY_WINDOW_URL = isDevelopment ? (process.env.WEBPACK_DEV_SERVER_URL as string) : 'picgo://./index.html'
export const TOOLBOX_WINDOW_URL = process.env.NODE_ENV === 'development'
? `${(process.env.WEBPACK_DEV_SERVER_URL as string)}#toolbox-page`
export const TOOLBOX_WINDOW_URL =
process.env.NODE_ENV === 'development'
? `${process.env.WEBPACK_DEV_SERVER_URL as string}#toolbox-page`
: 'picgo://./index.html#toolbox-page'
+13 -5
View File
@@ -23,8 +23,11 @@ const windowList = new Map<IWindowList, IWindowListItem>()
const handleWindowParams = (windowURL: string) => windowURL
const getDefaultWindowSizes = (): { width: number, height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([configPaths.settings.mainWindowWidth, configPaths.settings.mainWindowHeight])
const getDefaultWindowSizes = (): { width: number; height: number } => {
const [mainWindowWidth, mainWindowHeight] = db.get([
configPaths.settings.mainWindowWidth,
configPaths.settings.mainWindowHeight
])
return {
width: mainWindowWidth || 1200,
height: mainWindowHeight || 800
@@ -232,14 +235,17 @@ windowList.set(IWindowList.RENAME_WINDOW, {
if (currentWindow && currentWindow.isVisible()) {
// bounds: { x: 821, y: 75, width: 800, height: 450 }
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 150
let positionX = bounds.x + bounds.width / 2 - 150
let positionY
// if is the settingWindow
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 88
} else { // if is the miniWindow
} else {
// if is the miniWindow
positionY = bounds.y + bounds.height / 2
}
positionX = Math.floor(positionX)
positionY = Math.floor(positionY)
window.setPosition(positionX, positionY, false)
}
}
@@ -254,13 +260,15 @@ windowList.set(IWindowList.TOOLBOX_WINDOW, {
const currentWindow = windowManager.getAvailableWindow(true)
if (currentWindow && currentWindow.isVisible()) {
const bounds = currentWindow.getBounds()
const positionX = bounds.x + bounds.width / 2 - 400
let positionX = bounds.x + bounds.width / 2 - 400
let positionY
if (bounds.height > 400) {
positionY = bounds.y + bounds.height / 2 - 225
} else {
positionY = bounds.y + bounds.height / 2
}
positionX = Math.floor(positionX)
positionY = Math.floor(positionY)
window.setPosition(positionX, positionY, false)
}
}
+9 -7
View File
@@ -11,10 +11,10 @@ import {
} from '@core/bus/constants'
export const uploadWithClipboardFiles = (): Promise<{
success: boolean,
success: boolean
result?: string[]
}> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE, (result: string) => {
if (result) {
return resolve({
@@ -31,11 +31,13 @@ export const uploadWithClipboardFiles = (): Promise<{
})
}
export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
success: boolean,
export const uploadWithFiles = (
pathList: IFileWithPath[]
): Promise<{
success: boolean
result?: string[]
}> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(UPLOAD_WITH_FILES_RESPONSE, (result: string[]) => {
if (result.length) {
return resolve({
@@ -55,7 +57,7 @@ export const uploadWithFiles = (pathList: IFileWithPath[]): Promise<{
// get available window id:
// miniWindow or settingWindow or trayWindow
export const getWindowId = (): Promise<number> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(GET_WINDOW_ID_REPONSE, (id: number) => {
resolve(id)
})
@@ -65,7 +67,7 @@ export const getWindowId = (): Promise<number> => {
// get settingWindow id:
export const getSettingWindowId = (): Promise<number> => {
return new Promise((resolve) => {
return new Promise(resolve => {
bus.once(GET_SETTING_WINDOW_ID_RESPONSE, (id: number) => {
resolve(id)
})
+7 -8
View File
@@ -54,7 +54,9 @@ function dbChecker () {
fs.unlinkSync(configFilePath)
if (fs.existsSync(configFileBackupPath)) {
try {
configFile = fs.readFileSync(configFileBackupPath, { encoding: 'utf-8' })
configFile = fs.readFileSync(configFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile)
writeFile.sync(configFilePath, configFile, { encoding: 'utf-8' })
const stats = fs.statSync(configFileBackupPath)
@@ -91,7 +93,9 @@ function dbPathChecker (): string {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultConfigPath, { encoding: 'utf-8' })
const configString = fs.readFileSync(defaultConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
@@ -135,9 +139,4 @@ function getGalleryDBPath (): {
}
}
export {
dbChecker,
dbPathChecker,
dbPathDir,
getGalleryDBPath
}
export { dbChecker, dbPathChecker, dbPathDir, getGalleryDBPath }
+1 -3
View File
@@ -115,6 +115,4 @@ class GalleryDB {
}
}
export {
GalleryDB
}
export { GalleryDB }
+1 -3
View File
@@ -94,6 +94,4 @@ const getLogger = (logPath: string, logType: string) => {
}
}
export {
getLogger
}
export { getLogger }
+32 -27
View File
@@ -1,9 +1,4 @@
import {
BrowserWindow,
dialog,
ipcMain,
Notification
} from 'electron'
import { BrowserWindow, dialog, ipcMain, Notification } from 'electron'
import fs from 'fs-extra'
import { cloneDeep } from 'lodash'
import { DBStore } from '@picgo/store'
@@ -47,7 +42,7 @@ class GuiApi implements IGuiApi {
return true
}
settingWindow?.show()
return new Promise<void>((resolve) => {
return new Promise<void>(resolve => {
setTimeout(() => {
resolve()
}, 1000) // TODO: a better way to wait page loaded.
@@ -58,13 +53,15 @@ class GuiApi implements IGuiApi {
return BrowserWindow.fromId(id)?.webContents
}
async showInputBox (options: IShowInputBoxOption = {
async showInputBox(
options: IShowInputBoxOption = {
title: '',
placeholder: ''
}) {
}
) {
await this.showSettingWindow()
this.getWebcontentsByWindowId(this.settingWindowId)?.send(SHOW_INPUT_BOX, options)
return new Promise<string>((resolve) => {
return new Promise<string>(resolve => {
ipcMain.once(SHOW_INPUT_BOX, (_: Event, value: string) => {
resolve(value)
})
@@ -90,8 +87,11 @@ class GuiApi implements IGuiApi {
if (deleteLocalFile) {
await fs.remove(rawInput[i])
}
pasteText.push(await (pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
pasteText.push(await pasteTemplate(pasteStyle, imgs[i], db.get(configPaths.settings.customLink)))
const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -112,10 +112,12 @@ class GuiApi implements IGuiApi {
return []
}
showNotification (options: IShowNotificationOption = {
showNotification(
options: IShowNotificationOption = {
title: '',
body: ''
}) {
}
) {
const notification = new Notification({
title: options.title,
body: options.body
@@ -123,18 +125,17 @@ class GuiApi implements IGuiApi {
notification.show()
}
showMessageBox (options: IShowMessageBoxOption = {
showMessageBox(
options: IShowMessageBoxOption = {
title: '',
message: '',
type: 'info',
buttons: ['Yes', 'No']
}) {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
}
) {
return new Promise<IShowMessageBoxResult>(async resolve => {
this.windowId = await getWindowId()
dialog.showMessageBox(
BrowserWindow.fromId(this.windowId)!,
options
).then((res) => {
dialog.showMessageBox(BrowserWindow.fromId(this.windowId)!, options).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
@@ -162,14 +163,16 @@ class GuiApi implements IGuiApi {
if (prop === 'overwrite') {
return new Proxy(GalleryDB.getInstance().overwrite, {
apply(target, ctx, args) {
return new Promise((resolve) => {
return new Promise(resolve => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
guiApi
.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
})
.then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
@@ -183,14 +186,16 @@ class GuiApi implements IGuiApi {
if (prop === 'removeById') {
return new Proxy(GalleryDB.getInstance().removeById, {
apply(target, ctx, args) {
return new Promise((resolve) => {
return new Promise(resolve => {
const guiApi = GuiApi.getInstance()
guiApi.showMessageBox({
guiApi
.showMessageBox({
title: T('TIPS_WARNING'),
message: T('TIPS_PLUGIN_REMOVE_GALLERY_ITEM'),
type: 'info',
buttons: ['Yes', 'No']
}).then(res => {
})
.then(res => {
if (res.result === 0) {
resolve(Reflect.apply(target, ctx, args))
} else {
+2 -7
View File
@@ -11,13 +11,8 @@ import {
UPLOAD_WITH_CLIPBOARD_FILES_RESPONSE
} from '@core/bus/constants'
import {
createMenu
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { createMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
+29 -37
View File
@@ -1,20 +1,10 @@
import {
app,
dialog,
BrowserWindow,
Menu,
shell,
MenuItemConstructorOptions,
MenuItem
} from 'electron'
import { app, dialog, BrowserWindow, Menu, shell, MenuItemConstructorOptions, MenuItem } from 'electron'
import { PicGo as PicGoCore } from 'piclist'
import db from '@core/datastore'
import picgo from '@core/picgo'
import {
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import GuiApi from 'apis/gui'
@@ -47,7 +37,7 @@ const buildMiniPageMenu = () => {
const isListeningClipboard = db.get(configPaths.settings.isListeningClipboard) || false
const ClipboardWatcher = clipboardPoll
const submenu = buildPicBedListMenu()
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
const template: Array<MenuItemConstructorOptions | MenuItem> = [
{
label: T('OPEN_MAIN_WINDOW'),
click: openMainWindow
@@ -145,8 +135,7 @@ const buildMainPageMenu = (win: BrowserWindow) => {
shell.openExternal(url)
}
}
]
// @ts-ignore
] as Array<MenuItemConstructorOptions | MenuItem>
return Menu.buildFromTemplate(template)
}
@@ -155,28 +144,33 @@ const buildPicBedListMenu = () => {
const currentPicBed = picgo.getConfig(configPaths.picBed.uploader)
const currentPicBedName = picBeds.find(item => item.type === currentPicBed)?.name
const picBedConfigList = picgo.getConfig<IUploaderConfig>('uploader')
const currentPicBedMenuItem = [{
const currentPicBedMenuItem = [
{
label: `${T('CURRENT_PICBED')} - ${currentPicBedName}`,
enabled: false
}, {
},
{
type: 'separator'
}]
let submenu = picBeds.filter(item => item.visible).map(item => {
}
]
let submenu = picBeds
.filter(item => item.visible)
.map(item => {
const configList = picBedConfigList?.[item.type]?.configList
const defaultId = picBedConfigList?.[item.type]?.defaultId
const hasSubmenu = !!configList
return {
label: item.name,
type: !hasSubmenu ? 'checkbox' : undefined,
checked: !hasSubmenu ? (currentPicBed === item.type) : undefined,
checked: !hasSubmenu ? currentPicBed === item.type : undefined,
submenu: hasSubmenu
? configList.map((config) => {
? configList.map(config => {
return {
label: config._configName || 'Default',
// if only one config, use checkbox, or radio will checked as default
// see: https://github.com/electron/electron/issues/21292
type: 'checkbox',
checked: config._id === defaultId && (item.type === currentPicBed),
checked: config._id === defaultId && item.type === currentPicBed,
click: function () {
changeCurrentUploader(item.type, config, config._id)
if (windowManager.has(IWindowList.SETTING_WINDOW)) {
@@ -201,9 +195,9 @@ const buildPicBedListMenu = () => {
: undefined
}
})
// @ts-ignore
// @ts-expect-error submenu type
submenu = currentPicBedMenuItem.concat(submenu)
// @ts-ignore
// @ts-expect-error submenu type
return Menu.buildFromTemplate(submenu)
}
@@ -230,7 +224,8 @@ const handleRestoreState = (item: string, name: string): void => {
}
const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const menu = [{
const menu = [
{
label: T('ENABLE_PLUGIN'),
enabled: !plugin.enabled,
click() {
@@ -240,7 +235,8 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_TOGGLE_PLUGIN, plugin.fullName, true)
}
}, {
},
{
label: T('DISABLE_PLUGIN'),
enabled: plugin.enabled,
click() {
@@ -258,21 +254,24 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
handleRestoreState('uploader', plugin.config.uploader.name)
}
}
}, {
},
{
label: T('UNINSTALL_PLUGIN'),
click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUninstall(plugin.fullName)
}
}, {
},
{
label: T('UPDATE_PLUGIN'),
click() {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send(PICGO_HANDLE_PLUGIN_ING, plugin.fullName)
handlePluginUpdate(plugin.fullName)
}
}]
}
] as Array<MenuItemConstructorOptions | MenuItem>
for (const i in plugin.config) {
if (plugin.config[i].config.length > 0) {
const obj = {
@@ -317,7 +316,6 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
// plugin custom menus
if (plugin.guiMenu) {
menu.push({
// @ts-ignore
type: 'separator'
})
for (const i of plugin.guiMenu) {
@@ -338,13 +336,7 @@ const buildPluginPageMenu = (plugin: IPicGoPlugin) => {
}
}
// @ts-ignore
return Menu.buildFromTemplate(menu)
}
export {
buildMiniPageMenu,
buildMainPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
}
export { buildMiniPageMenu, buildMainPageMenu, buildPicBedListMenu, buildPluginPageMenu }
+4 -4
View File
@@ -10,6 +10,7 @@ import { systemRouter } from '~/events/rpc/routes/system'
import { toolboxRouter } from '~/events/rpc/routes/toolbox'
import { trayRouter } from '~/events/rpc/routes/tray'
import { uploadRouter } from '~/events/rpc/routes/upload'
import { manageRouter } from '~/events/rpc/routes/manage'
import { IRPCActionType, IRPCType } from '#/types/enum'
import { RPC_ACTIONS, RPC_ACTIONS_INVOKE } from '#/events/constants'
@@ -75,13 +76,12 @@ const routes = [
systemRouter.routes(),
toolboxRouter.routes(),
trayRouter.routes(),
uploadRouter.routes()
uploadRouter.routes(),
manageRouter.routes()
]
for (const route of routes) {
rpcServer.use(route)
}
export {
rpcServer
}
export { rpcServer }
+7 -5
View File
@@ -1,4 +1,3 @@
import { clipboard } from 'electron'
import { GalleryDB } from '@core/datastore'
@@ -8,7 +7,12 @@ import { IFilter, IObject } from '@picgo/store/dist/types'
import GuiApi from 'apis/gui'
import { RPCRouter } from '~/events/rpc/router'
import { removeFileFromDogeInMain, removeFileFromHuaweiInMain, removeFileFromS3InMain, removeFileFromSFTPInMain } from '~/utils/deleteFunc'
import {
removeFileFromDogeInMain,
removeFileFromHuaweiInMain,
removeFileFromS3InMain,
removeFileFromSFTPInMain
} from '~/utils/deleteFunc'
import pasteTemplate from '~/utils/pasteTemplate'
import { ICOREBuildInEvent, IPasteStyle, IRPCActionType, IRPCType } from '#/types/enum'
@@ -128,6 +132,4 @@ const galleryRoutes = [
galleryRouter.addBatch(galleryRoutes)
export {
galleryRouter
}
export { galleryRouter }
+100
View File
@@ -0,0 +1,100 @@
import { ManageApi } from '~/manage/manageApi'
import { IRPCActionType, IRPCType } from '#/types/enum'
export default [
{
action: IRPCActionType.MANAGE_GET_BUCKET_LIST,
handler: async (_: IIPCEvent, args: [currentPicBed: string]) => {
return new ManageApi(args[0]).getBucketList()
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_BUCKET_LIST_BACKSTAGE,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).getBucketListBackstage(args[1])
}
},
{
action: IRPCActionType.MANAGE_GET_BUCKET_LIST_RECURSIVELY,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).getBucketListRecursively(args[1])
}
},
{
action: IRPCActionType.MANAGE_CREATE_BUCKET,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).createBucket(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_BUCKET_FILE_LIST,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).getBucketFileList(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_BUCKET_DOMAIN,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).getBucketDomain(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_SET_BUCKET_ACL_POLICY,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).setBucketAclPolicy(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_RENAME_BUCKET_FILE,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).renameBucketFile(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_DELETE_BUCKET_FILE,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).deleteBucketFile(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_DELETE_BUCKET_FOLDER,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).deleteBucketFolder(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_PRE_SIGNED_URL,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).getPreSignedUrl(args[1])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_UPLOAD_BUCKET_FILE,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).uploadBucketFile(args[1])
}
},
{
action: IRPCActionType.MANAGE_DOWNLOAD_BUCKET_FILE,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).downloadBucketFile(args[1])
}
},
{
action: IRPCActionType.MANAGE_CREATE_BUCKET_FOLDER,
handler: async (_: IIPCEvent, args: [currentPicBed: string, param: IStringKeyMap]) => {
return new ManageApi(args[0]).createBucketFolder(args[1])
},
type: IRPCType.INVOKE
}
]
@@ -0,0 +1,26 @@
import { IRPCActionType, IRPCType } from '#/types/enum'
import getManageApi from '~/manage/Main'
const manageApi = getManageApi()
export default [
{
action: IRPCActionType.MANAGE_GET_CONFIG,
handler: async (_: IIPCEvent, args: [key?: string]) => {
return manageApi.getConfig(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_SAVE_CONFIG,
handler: async (_: IIPCEvent, args: [data: IObj]) => {
manageApi.saveConfig(args[0])
}
},
{
action: IRPCActionType.MANAGE_REMOVE_CONFIG,
handler: async (_: IIPCEvent, args: [key: string, propName: string]) => {
manageApi.removeConfig(args[0], args[1])
}
}
]
@@ -0,0 +1,13 @@
import { RPCRouter } from '~/events/rpc/router'
import configRoutes from '~/events/rpc/routes/manage/config'
import bucketRoutes from '~/events/rpc/routes/manage/bucket'
import upDownLoadRoutes from '~/events/rpc/routes/manage/upDownload'
const manageRouter = new RPCRouter()
const manageRoutes = [...configRoutes, ...bucketRoutes, ...upDownLoadRoutes]
manageRouter.addBatch(manageRoutes)
export { manageRouter }
@@ -0,0 +1,108 @@
import { app, dialog, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import { IRPCActionType, IRPCType } from '#/types/enum'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { downloadFileFromUrl } from '~/manage/utils/common'
export default [
{
action: IRPCActionType.MANAGE_OPEN_FILE_SELECT_DIALOG,
handler: async () => {
const res = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections']
})
return res.canceled ? [] : res.filePaths
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_UPLOAD_TASK_LIST,
handler: async () => {
return UpDownTaskQueue.getInstance().getAllUploadTask()
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_DOWNLOAD_TASK_LIST,
handler: async () => {
return UpDownTaskQueue.getInstance().getAllDownloadTask()
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_DELETE_UPLOADED_TASK,
handler: async () => {
UpDownTaskQueue.getInstance().removeUploadedTask()
}
},
{
action: IRPCActionType.MANAGE_DELETE_ALL_UPLOADED_TASK,
handler: async () => {
UpDownTaskQueue.getInstance().clearUploadTaskQueue()
}
},
{
action: IRPCActionType.MANAGE_DELETE_DOWNLOADED_TASK,
handler: async () => {
UpDownTaskQueue.getInstance().removeDownloadedTask()
}
},
{
action: IRPCActionType.MANAGE_DELETE_ALL_DOWNLOADED_TASK,
handler: async () => {
UpDownTaskQueue.getInstance().clearDownloadTaskQueue()
}
},
{
action: IRPCActionType.MANAGE_SELECT_DOWNLOAD_FOLDER,
handler: async () => {
const res = await dialog.showOpenDialog({
properties: ['openDirectory']
})
return res.filePaths[0]
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_GET_DEFAULT_DOWNLOAD_FOLDER,
handler: async () => {
return app.getPath('downloads')
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_OPEN_DOWNLOADED_FOLDER,
handler: async (_: IIPCEvent, args: [path?: string]) => {
const path = args[0]
if (path) {
shell.showItemInFolder(path)
} else {
shell.openPath(app.getPath('downloads'))
}
}
},
{
action: IRPCActionType.MANAGE_OPEN_LOCAL_FILE,
handler: async (_: IIPCEvent, args: [fullPath: string]) => {
const fullPath = args[0]
fs.existsSync(fullPath) ? shell.showItemInFolder(fullPath) : shell.openPath(path.dirname(fullPath))
}
},
{
action: IRPCActionType.MANAGE_DOWNLOAD_FILE_FROM_URL,
handler: async (_: IIPCEvent, args: [urls: string[]]) => {
return await downloadFileFromUrl(args[0])
},
type: IRPCType.INVOKE
},
{
action: IRPCActionType.MANAGE_CONVERT_PATH_TO_BASE64,
handler: async (_: IIPCEvent, args: [filePath: string]) => {
return fs.readFileSync(args[0], 'base64')
},
type: IRPCType.INVOKE
}
]
+1 -3
View File
@@ -94,6 +94,4 @@ const picbedRoutes = [
picbedRouter.addBatch(picbedRoutes)
export {
picbedRouter
}
export { picbedRouter }
+1 -4
View File
@@ -1,4 +1,3 @@
import { RPCRouter } from '~/events/rpc/router'
import {
pluginImportLocalFunc,
@@ -32,6 +31,4 @@ const pluginRoutes = [
pluginRouter.addBatch(pluginRoutes)
export {
pluginRouter
}
export { pluginRouter }
+4 -2
View File
@@ -104,11 +104,13 @@ const getPluginList = (): IPicGoPlugin[] => {
const handleNPMError = (): IDispose => {
const handler = (msg: string) => {
if (msg === 'NPM is not installed') {
dialog.showMessageBox({
dialog
.showMessageBox({
title: T('TIPS_ERROR'),
message: T('TIPS_INSTALL_NODE_AND_RELOAD_PICGO'),
buttons: ['Yes']
}).then((res) => {
})
.then(res => {
if (res.response === 0) {
shell.openExternal('https://nodejs.org/')
}
@@ -1,4 +1,3 @@
import { IRPCActionType } from '#/types/enum'
import server from '~/server'
import webServer from '~/server/webServer'
@@ -16,19 +16,15 @@ export default [
action: IRPCActionType.CONFIGURE_MIGRATE_FROM_PICGO,
handler: async () => {
const picGoConfigPath = STORE_PATH.replace('piclist', 'picgo')
const files = [
'data.json',
'data.bak.json',
'picgo.db',
'picgo.bak.db'
]
const files = ['data.json', 'data.bak.json', 'picgo.db', 'picgo.bak.db']
try {
await Promise.all(files.map(async file => {
await Promise.all(
files.map(async file => {
const sourcePath = path.join(picGoConfigPath, file)
const targetPath = path.join(STORE_PATH, file.replace('picgo', 'piclist'))
await fs.copy(sourcePath, targetPath, { overwrite: true })
}
))
})
)
return true
} catch (err: any) {
logger.error(err)
+2 -9
View File
@@ -7,15 +7,8 @@ import shortKeyRoutes from '~/events/rpc/routes/setting/shortKey'
const settingRouter = new RPCRouter()
const settingRoutes = [
...advancedRoutes,
...configureRoutes,
...mainAppRoutes,
...shortKeyRoutes
]
const settingRoutes = [...advancedRoutes, ...configureRoutes, ...mainAppRoutes, ...shortKeyRoutes]
settingRouter.addBatch(settingRoutes)
export {
settingRouter
}
export { settingRouter }
@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import bus from '@core/bus'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
+2 -7
View File
@@ -4,13 +4,8 @@ import windowRoutes from '~/events/rpc/routes/system/window'
const systemRouter = new RPCRouter()
const systemRoutes = [
...appRoutes,
...windowRoutes
]
const systemRoutes = [...appRoutes, ...windowRoutes]
systemRouter.addBatch(systemRoutes)
export {
systemRouter
}
export { systemRouter }
+1 -6
View File
@@ -2,12 +2,7 @@ import { app, BrowserWindow } from 'electron'
import windowManager from 'apis/app/window/windowManager'
import {
buildMainPageMenu,
buildMiniPageMenu,
buildPicBedListMenu,
buildPluginPageMenu
} from '~/events/remotes/menu'
import { buildMainPageMenu, buildMiniPageMenu, buildPicBedListMenu, buildPluginPageMenu } from '~/events/remotes/menu'
import { openMiniWindow } from '~/utils/windowHelper'
import { IRPCActionType, IWindowList } from '#/types/enum'
@@ -14,10 +14,8 @@ const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_
const defaultClipboardImagePath = path.join(defaultConfigPath, CLIPBOARD_IMAGE_FOLDER)
export const checkClipboardUploadMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async (event) => {
export const checkClipboardUploadMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async event => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
@@ -54,9 +52,7 @@ IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
}
}
export const fixClipboardUploadMap: IToolboxFixMap<
IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD
> = {
export const fixClipboardUploadMap: IToolboxFixMap<IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD> = {
[IToolboxItemType.HAS_PROBLEM_WITH_CLIPBOARD_PIC_UPLOAD]: async () => {
const configFilePath = dbPathChecker()
const dirPath = path.dirname(configFilePath)
@@ -38,7 +38,7 @@ IToolboxItemType.IS_CONFIG_FILE_BROKEN | IToolboxItemType.IS_GALLERY_FILE_BROKEN
})
}
},
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async (event) => {
[IToolboxItemType.IS_GALLERY_FILE_BROKEN]: async event => {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.IS_GALLERY_FILE_BROKEN)
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
@@ -1,4 +1,3 @@
import axios, { AxiosRequestConfig } from 'axios'
import fs from 'fs-extra'
import { IConfig } from 'piclist'
@@ -27,10 +26,8 @@ function getProxy (proxyStr: string): AxiosRequestConfig['proxy'] | null {
const sendToolboxRes = sendToolboxResWithType(IToolboxItemType.HAS_PROBLEM_WITH_PROXY)
export const checkProxyMap: IToolboxCheckerMap<
IToolboxItemType.HAS_PROBLEM_WITH_PROXY
> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async (event) => {
export const checkProxyMap: IToolboxCheckerMap<IToolboxItemType.HAS_PROBLEM_WITH_PROXY> = {
[IToolboxItemType.HAS_PROBLEM_WITH_PROXY]: async event => {
sendToolboxRes(event, {
status: IToolboxItemCheckStatus.LOADING
})
@@ -38,9 +35,8 @@ IToolboxItemType.HAS_PROBLEM_WITH_PROXY
if (fs.existsSync(configFilePath)) {
let config: IConfig | undefined
try {
config = await fs.readJSON(configFilePath) as IConfig
} catch (e) {
}
config = (await fs.readJSON(configFilePath)) as IConfig
} catch (e) {}
if (!config) {
return sendToolboxRes(event, {
status: IToolboxItemCheckStatus.SUCCESS,
+3 -4
View File
@@ -42,7 +42,8 @@ toolboxRouter
IRPCType.SEND
)
.add(
IRPCActionType.TOOLBOX_CHECK_FIX, async (event, args) => {
IRPCActionType.TOOLBOX_CHECK_FIX,
async (event, args) => {
const [type] = args as IToolboxCheckArgs
const handler = toolboxFixMap[type]
if (handler) {
@@ -52,6 +53,4 @@ toolboxRouter
IRPCType.INVOKE
)
export {
toolboxRouter
}
export { toolboxRouter }
+7 -8
View File
@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import { RPCRouter } from '~/events/rpc/router'
import { generateShortUrl, setTrayToolTip, handleCopyUrl } from '~/utils/common'
@@ -42,8 +40,11 @@ const trayRoutes = [
const img = await uploader.setWebContents(trayWindow.webContents).uploadWithBuildInClipboard()
if (img !== false) {
const pasteStyle = db.get(configPaths.settings.pasteStyle) || IPasteStyle.MARKDOWN
handleCopyUrl(await (pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink))))
const isShowResultNotification = db.get(configPaths.settings.uploadResultNotification) === undefined ? true : !!db.get(configPaths.settings.uploadResultNotification)
handleCopyUrl(await pasteTemplate(pasteStyle, img[0], db.get(configPaths.settings.customLink)))
const isShowResultNotification =
db.get(configPaths.settings.uploadResultNotification) === undefined
? true
: !!db.get(configPaths.settings.uploadResultNotification)
if (isShowResultNotification) {
const notification = new Notification({
title: T('UPLOAD_SUCCEED'),
@@ -66,6 +67,4 @@ const trayRoutes = [
trayRouter.addBatch(trayRoutes)
export {
trayRouter
}
export { trayRouter }
+1 -3
View File
@@ -30,6 +30,4 @@ const uploadRoutes = [
uploadRouter.addBatch(uploadRoutes)
export {
uploadRouter
}
export { uploadRouter }
+4 -2
View File
@@ -27,9 +27,11 @@ export function startFileServer () {
})
})
server.listen(serverPort, () => {
server
.listen(serverPort, () => {
logger.info(`File server is running, http://localhost:${serverPort}`)
}).on('error', (err) => {
})
.on('error', err => {
logger.error(err)
})
}
+3 -7
View File
@@ -1,7 +1,7 @@
// TODO: so how to import pure esm module in electron main process????? help wanted
// just copy the fix-path because I can't import pure ESM module in electron main process
// @ts-nocheck
// @ts-nocheck since the module is not pure ESM
// shell-path 3.0.0 not work
const shellPath = require('shell-path')
@@ -11,10 +11,6 @@ export default function fixPath () {
return
}
process.env.PATH = shellPath.sync() || [
'./node_modules/.bin',
'/.nodebrew/current/bin',
'/usr/local/bin',
process.env.PATH
].join(':')
process.env.PATH =
shellPath.sync() || ['./node_modules/.bin', '/.nodebrew/current/bin', '/usr/local/bin', process.env.PATH].join(':')
}
+32 -39
View File
@@ -1,19 +1,9 @@
import axios from 'axios'
import fs from 'fs-extra'
import {
app,
globalShortcut,
protocol,
Notification,
dialog,
screen,
shell
} from 'electron'
import { app, globalShortcut, protocol, Notification, dialog, screen, shell } from 'electron'
import { UpdateInfo, autoUpdater } from 'electron-updater'
import path from 'path'
import {
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import bus from '@core/bus'
import db from '@core/datastore'
@@ -22,13 +12,8 @@ import logger from '@core/picgo/logger'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import {
createTray, setDockMenu
} from 'apis/app/system'
import {
uploadChoosedFiles,
uploadClipboardFiles
} from 'apis/app/uploader/apis'
import { createTray, setDockMenu } from 'apis/app/system'
import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis'
import windowManager from 'apis/app/window/windowManager'
import busEventList from '~/events/busEventList'
@@ -37,7 +22,6 @@ import { T } from '~/i18n'
import '~/lifeCycle/errorHandler'
import fixPath from '~/lifeCycle/fixPath'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { manageIpcList } from '~/manage/events/ipcList'
import getManageApi from '~/manage/Main'
import { clearTempFolder } from '~/manage/utils/common'
import server from '~/server/index'
@@ -57,7 +41,8 @@ const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => {
const files = getUploadFiles(argv, cwd, logger)
if (files === null || files.length > 0) { // 如果有文件列表作为参数,说明是命令行启动
if (files === null || files.length > 0) {
// 如果有文件列表作为参数,说明是命令行启动
if (files === null) {
logger.info('cli -> uploading file from clipboard')
uploadClipboardFiles()
@@ -83,34 +68,43 @@ autoUpdater.on('update-available', async (info: UpdateInfo) => {
const lang = db.get(configPaths.settings.language) || II18nLanguage.ZH_CN
let updateLog = ''
try {
const url = lang === II18nLanguage.ZH_CN ? 'https://release.piclist.cn/currentVersion.md' : 'https://release.piclist.cn/currentVersion_en.md'
const url =
lang === II18nLanguage.ZH_CN
? 'https://release.piclist.cn/currentVersion.md'
: 'https://release.piclist.cn/currentVersion_en.md'
const res = await axios.get(url)
updateLog = res.data
} catch (e: any) {
logger.error(e)
}
dialog.showMessageBox({
dialog
.showMessageBox({
type: 'info',
title: T('FIND_NEW_VERSION'),
buttons: ['Yes', 'Go to download page'],
message: T('TIPS_FIND_NEW_VERSION', {
message:
T('TIPS_FIND_NEW_VERSION', {
v: info.version
}) + '\n\n' + updateLog,
}) +
'\n\n' +
updateLog,
checkboxLabel: T('NO_MORE_NOTICE'),
checkboxChecked: false
}).then((result) => {
})
.then(result => {
if (result.response === 0) {
autoUpdater.downloadUpdate()
} else {
shell.openExternal('https://github.com/Kuingsmile/PicList/releases/latest')
}
db.set(configPaths.settings.showUpdateTip, !result.checkboxChecked)
}).catch((err) => {
})
.catch(err => {
logger.error(err)
})
})
autoUpdater.on('download-progress', (progressObj) => {
autoUpdater.on('download-progress', progressObj => {
const percent = {
progress: progressObj.percent
}
@@ -119,23 +113,26 @@ autoUpdater.on('download-progress', (progressObj) => {
})
autoUpdater.on('update-downloaded', () => {
dialog.showMessageBox({
dialog
.showMessageBox({
type: 'info',
title: T('UPDATE_DOWNLOADED'),
buttons: ['Yes', 'No'],
message: T('TIPS_UPDATE_DOWNLOADED')
}).then((result) => {
})
.then(result => {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
window.webContents.send('updateProgress', { progress: 100 })
if (result.response === 0) {
autoUpdater.quitAndInstall()
}
}).catch((err) => {
})
.catch(err => {
logger.error(err)
})
})
autoUpdater.on('error', (err) => {
autoUpdater.on('error', err => {
console.log(err)
})
@@ -145,11 +142,10 @@ class LifeCycle {
// fix the $PATH in macOS & linux
fixPath()
beforeOpen()
initI18n()
rpcServer.start()
getManageApi()
UpDownTaskQueue.getInstance()
manageIpcList.listen()
initI18n()
rpcServer.start()
busEventList.listen()
}
@@ -173,7 +169,6 @@ class LifeCycle {
const isHideDock = db.get(configPaths.settings.isHideDock) || false
const startMode = db.get(configPaths.settings.startMode) || ISartMode.QUIET
const currentPicBed = db.get(configPaths.picBed.uploader) || db.get(configPaths.picBed.current) || 'smms'
// @ts-ignore
const currentPicBedConfig = db.get(`picBed.${currentPicBed}`)?._configName || 'Default'
const tooltip = `${currentPicBed} ${currentPicBedConfig}`
if (process.platform === 'darwin') {
@@ -319,6 +314,4 @@ class LifeCycle {
const bootstrap = new LifeCycle()
export {
bootstrap
}
export { bootstrap }
+99 -44
View File
@@ -8,7 +8,13 @@ import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { ManageLogger } from '~/manage/utils/logger'
import { hmacSha1Base64, getFileMimeType, formatError, NewDownloader, ConcurrencyPromisePool } from '~/manage/utils/common'
import {
hmacSha1Base64,
getFileMimeType,
formatError,
NewDownloader,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
import { isImage } from '#/utils/common'
@@ -72,13 +78,19 @@ class AliyunApi {
}, {} as IStringKeyMap)
let canonicalizedOSSHeaders = ''
const headerKeys = Object.keys(lowerCaseHeaders).sort()
headerKeys.forEach((key) => {
headerKeys.forEach(key => {
key.startsWith('x-oss-') && (canonicalizedOSSHeaders += `${key}:${lowerCaseHeaders[key]}\n`)
})
return canonicalizedOSSHeaders
}
authorization (method: string, canonicalizedResource: string, headers: IStringKeyMap, contentMd5: string, contentType: string) {
authorization(
method: string,
canonicalizedResource: string,
headers: IStringKeyMap,
contentMd5: string,
contentType: string
) {
const date = new Date().toUTCString()
const stringToSign = `${method.toUpperCase()}\n${contentMd5}\n${contentType}\n${date}\n${this.getCanonicalizedOSSHeaders(headers)}${canonicalizedResource}`
return `OSS ${this.accessKeyId}:${hmacSha1Base64(this.accessKeySecret, stringToSign)}`
@@ -99,17 +111,21 @@ class AliyunApi {
*/
async getBucketList(): Promise<any> {
const getBuckets = async (marker?: string) => {
const res = await this.ctx.listBuckets({
const res = (await this.ctx.listBuckets({
marker,
'max-keys': 1000
}) as IStringKeyMap
})) as IStringKeyMap
if (res?.res?.statusCode !== 200 || !res?.buckets) return { result: [], isTruncated: false }
const formattedBuckets = res.buckets.map((item: OSS.Bucket) => ({
Name: item.name,
Location: item.region,
CreationDate: item.creationDate
}))
return { result: formattedBuckets, isTruncated: res.isTruncated, nextMarker: res.nextMarker }
return {
result: formattedBuckets,
isTruncated: res.isTruncated,
nextMarker: res.nextMarker
}
}
const result: IStringKeyMap[] = []
let NextMarker: string | undefined
@@ -193,7 +209,12 @@ class AliyunApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker
@@ -212,13 +233,16 @@ class AliyunApi {
}
const client = this.getNewCtx(region, bucket)
do {
res = await client.listV2({
res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
'max-keys': '1000',
'continuation-token': marker
}, {
},
{
timeout: this.timeOut
})
}
)
if (res?.res?.statusCode === 200) {
res?.objects?.forEach((item: OSS.ObjectMeta) => {
item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
@@ -240,7 +264,12 @@ class AliyunApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
let marker
@@ -259,14 +288,17 @@ class AliyunApi {
}
const client = this.getNewCtx(region, bucket)
do {
res = await client.listV2({
res = await client.listV2(
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
}, {
},
{
timeout: this.timeOut
})
}
)
if (res?.res?.statusCode === 200) {
res?.prefixes?.forEach((item: string) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
@@ -305,19 +337,28 @@ class AliyunApi {
* }
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
const client = this.getNewCtx(region, bucket)
const res = await client.listV2({
const res = (await client.listV2(
{
prefix: slicedPrefix || undefined,
delimiter: '/',
'max-keys': itemsPerPage.toString(),
'continuation-token': marker
}, {
},
{
timeout: this.timeOut
}) as any
}
)) as any
// prefixes can be null
// objects will be [] when no file
if (res?.res.statusCode !== 200) {
@@ -330,7 +371,9 @@ class AliyunApi {
}
const fullList = [
...(res.prefixes?.map((item: string) => this.formatFolder(item, slicedPrefix, urlPrefix)) || []),
...(res.objects?.filter((item: OSS.ObjectMeta) => item.size !== 0).map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
...(res.objects
?.filter((item: OSS.ObjectMeta) => item.size !== 0)
.map((item: OSS.ObjectMeta) => this.formatFile(item, slicedPrefix, urlPrefix)) || [])
]
return {
fullList,
@@ -353,12 +396,9 @@ class AliyunApi {
async renameBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, oldKey, newKey } = configMap
const client = this.getNewCtx(region, bucketName)
const copyRes = await client.copy(
newKey,
oldKey
) as any
const copyRes = (await client.copy(newKey, oldKey)) as any
if (copyRes?.res.statusCode === 200) {
const deleteRes = await client.delete(oldKey) as any
const deleteRes = (await client.delete(oldKey)) as any
return deleteRes?.res.statusCode === 204
}
return false
@@ -376,7 +416,7 @@ class AliyunApi {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName)
const res = await client.delete(key) as any
const res = (await client.delete(key)) as any
return res?.res.statusCode === 204
}
@@ -394,14 +434,17 @@ class AliyunApi {
Contents: [] as any[]
}
do {
const res = await client.listV2({
const res = (await client.listV2(
{
prefix: key,
delimiter: '/',
'max-keys': '1000',
'continuation-token': marker
}, {
},
{
timeout: this.timeOut
}) as any
}
)) as any
if (res?.res.statusCode !== 200) return false
res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
@@ -423,8 +466,9 @@ class AliyunApi {
if (allFileList.Contents.length > 0) {
const cycle = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycle; i++) {
const deleteRes = await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)) as any
const deleteRes = (await client.deleteMulti(
allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => item.name)
)) as any
if (deleteRes?.res.statusCode !== 200) return false
}
}
@@ -485,10 +529,8 @@ class AliyunApi {
targetFileBucket: bucketName,
targetFileRegion: region
})
client.multipartUpload(
key,
filePath,
{
client
.multipartUpload(key, filePath, {
partSize: 1 * 1024 * 1024,
mime: getFileMimeType(fileName),
progress: (p: number) => {
@@ -499,8 +541,8 @@ class AliyunApi {
status: uploadTaskSpecialStatus.uploading
})
}
}
).then((res: any) => {
})
.then((res: any) => {
const id = `${bucketName}-${region}-${key}-${filePath}`
if (res?.res?.statusCode === 200) {
instance.updateUploadTask({
@@ -519,8 +561,14 @@ class AliyunApi {
finishTime: new Date().toLocaleString()
})
}
}).catch((err: any) => {
this.logger.error(formatError(err, { class: 'AliyunApi', method: 'uploadBucketFile' }))
})
.catch((err: any) => {
this.logger.error(
formatError(err, {
class: 'AliyunApi',
method: 'uploadBucketFile'
})
)
const id = `${bucketName}-${region}-${key}-${filePath}`
instance.updateUploadTask({
id,
@@ -541,7 +589,7 @@ class AliyunApi {
async createBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName, region, key } = configMap
const client = this.getNewCtx(region, bucketName)
const res = await client.put(key, Buffer.from('')) as any
const res = (await client.put(key, Buffer.from(''))) as any
return res?.res?.statusCode === 200
}
@@ -571,20 +619,27 @@ class AliyunApi {
const preSignedUrl = client.signatureUrl(key, {
expires: 60 * 60 * 48
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error: any) => {
this.logger.error(formatError(error, { class: 'AliyunApi', method: 'downloadBucketFile' }))
this.logger.error(
formatError(error, {
class: 'AliyunApi',
method: 'downloadBucketFile'
})
)
})
return true
}
+131 -49
View File
@@ -6,7 +6,14 @@ import path from 'path'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { gotUpload, NewDownloader, getAgent, getOptions, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import {
gotUpload,
NewDownloader,
getAgent,
getOptions,
ConcurrencyPromisePool,
formatError
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList } from '#/types/enum'
@@ -41,7 +48,16 @@ class GithubApi {
rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? key : ''
const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? key
: ''
return url.replaceAll(ph, value)
}, cdnUrl)
: `${cdnUrl}/${key}`
@@ -69,7 +85,16 @@ class GithubApi {
rawUrl = cdnUrl
? placeholders.some(item => cdnUrl.includes(item))
? placeholders.reduce((url, ph) => {
const value = ph === '{username}' ? this.username : ph === '{repo}' ? repo : ph === '{branch}' ? branch : ph === '{path}' ? `${slicedPrefix}/${item.path}` : ''
const value =
ph === '{username}'
? this.username
: ph === '{repo}'
? repo
: ph === '{branch}'
? branch
: ph === '{path}'
? `${slicedPrefix}/${item.path}`
: ''
return url.replaceAll(ph, value)
}, cdnUrl)
: `${cdnUrl}/${key}`
@@ -99,10 +124,18 @@ class GithubApi {
let res
const result = [] as any[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/user/repos`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) {
res.body.forEach((item: any) => {
result.push({
@@ -129,10 +162,18 @@ class GithubApi {
let res
const result = [] as string[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches`,
getOptions('GET', this.commonHeaders, { page: initPage, per_page: 100 }, 'json', undefined, undefined, this.proxy)
) as any
getOptions(
'GET',
this.commonHeaders,
{ page: initPage, per_page: 100 },
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (res.statusCode === 200) {
res.body.forEach((item: any) => result.push(item.name))
} else {
@@ -167,10 +208,10 @@ class GithubApi {
return result
}
const currentPrefix = treeQueue[0]
res = await got(
res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${treeQueue.shift()}`,
getOptions('GET', this.commonHeaders, {}, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res && res.statusCode === 200) {
const { tree } = res.body
tree.forEach((item: any) => {
@@ -265,65 +306,98 @@ class GithubApi {
async deleteBucketFolder(configMap: IStringKeyMap): Promise<boolean> {
const { bucketName: repo, githubBranch: branch, key } = configMap
// get sha of the branch
const refRes = await got(
const refRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (refRes.statusCode !== 200) return false
const refSha = refRes.body.object.sha
// get sha of the root tree
const rootRes = await got(
const rootRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (rootRes.statusCode !== 200) return false
const rootSha = rootRes.body.commit.commit.tree.sha
// TODO: if there are more than 10000 files in the folder, it will be truncated
// Rare cases, not considered for now
// get sha of the folder tree
const treeRes = await got(
const treeRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/(^\/+|\/+$)/g, '')}`,
getOptions('GET', this.commonHeaders, {
getOptions(
'GET',
this.commonHeaders,
{
recursive: true
}, 'json', undefined, undefined, this.proxy)
) as any
},
'json',
undefined,
undefined,
this.proxy
)
)) as any
if (treeRes.statusCode !== 200) return false
const oldTree = treeRes.body.tree
// create a new tree
const newTree = oldTree.filter((item: any) => item.type === 'blob')
const newTree = oldTree
.filter((item: any) => item.type === 'blob')
.map((item: any) => ({
path: `${key.replace(/(^\/+|\/+$)/g, '')}/${item.path}`,
mode: item.mode,
type: item.type,
sha: null
}))
const newTreeShaRes = await got(
const newTreeShaRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/trees`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
base_tree: rootSha,
tree: newTree
}), undefined, this.proxy)
) as any
}),
undefined,
this.proxy
)
)) as any
if (newTreeShaRes.statusCode !== 201) return false
const newTreeSha = newTreeShaRes.body.sha
// create a new commit
const commitRes = await got(
const commitRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
getOptions(
'POST',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
message: 'deleted by PicList',
tree: newTreeSha,
parents: [refSha]
}), undefined, this.proxy)
) as any
}),
undefined,
this.proxy
)
)) as any
if (commitRes.statusCode !== 201) return false
const commitSha = commitRes.body.sha
// update the branch
const updateRefRes = await got(
const updateRefRes = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({
getOptions(
'PATCH',
this.commonHeaders,
undefined,
'json',
JSON.stringify({
sha: commitSha
}), undefined, this.proxy)
) as any
}),
undefined,
this.proxy
)
)) as any
return updateRefRes.statusCode === 200
}
@@ -341,12 +415,20 @@ class GithubApi {
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
if (!isPrivate) return rawUrl
const res = await got(
const res = (await got(
`${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`,
getOptions('GET', this.commonHeaders, {
getOptions(
'GET',
this.commonHeaders,
{
ref: branch
}, 'json', undefined, undefined, this.proxy)
) as any
},
'json',
undefined,
undefined,
this.proxy
)
)) as any
return res.statusCode === 200 ? res.body.download_url : ''
}
@@ -454,27 +536,27 @@ class GithubApi {
} else {
downloadUrl = githubUrl
}
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(
instance,
downloadUrl,
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, downloadUrl, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
this.logger.error(formatError(error, { class: 'GithubApi', method: 'downloadBucketFile' }))
pool.all(promises).catch(error => {
this.logger.error(
formatError(error, {
class: 'GithubApi',
method: 'downloadBucketFile'
})
)
})
return true
}
+19 -21
View File
@@ -69,10 +69,10 @@ class ImgurApi {
let res
const result = [] as any[]
do {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/albums/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (!(res.statusCode === 200 && res.body.success)) {
return []
}
@@ -95,7 +95,10 @@ class ImgurApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketConfig: { Location: albumHash }, cancelToken } = configMap
const {
bucketConfig: { Location: albumHash },
cancelToken
} = configMap
const cancelTask = [false]
ipcMain.on('cancelLoadingFileList', (_: IpcMainEvent, token: string) => {
if (token === cancelToken) {
@@ -110,10 +113,10 @@ class ImgurApi {
finished: false
}
if (albumHash !== 'unclassified') {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/album/${albumHash}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res.statusCode === 200 && res.body.success) {
res.body.data.images.forEach((item: any) => {
result.fullList.push(this.formatFile(item))
@@ -127,10 +130,10 @@ class ImgurApi {
} else {
let initPage = 0
do {
res = await got(
res = (await got(
`${this.baseUrl}/account/${this.userName}/images/${initPage}`,
getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
if (res.statusCode === 200 && res.body.success) {
res.body.data.forEach((item: any) => {
result.fullList.push(this.formatFile(item))
@@ -152,10 +155,10 @@ class ImgurApi {
async deleteBucketFile(configMap: IStringKeyMap): Promise<boolean> {
const { DeleteHash: deleteHash } = configMap
const res = await got(
const res = (await got(
`${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
getOptions('DELETE', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
) as any
)) as any
return res.statusCode === 200 && res.body.success
}
@@ -241,26 +244,21 @@ class ImgurApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(
instance,
url,
id,
savedFilePath,
this.logger,
this.proxyStr
)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, url, id, savedFilePath, this.logger, this.proxyStr).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'ImgurApi', method: 'downloadBucketFile' }))
})
return true
+5 -3
View File
@@ -21,8 +21,7 @@ class LocalApi {
this.isWindows = process.platform === 'win32'
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'LocalApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'LocalApi', method }))
// windows 系统下将路径转换为 unix 风格
transPathToUnix(filePath: string | undefined) {
@@ -33,7 +32,10 @@ class LocalApi {
transBack(filePath: string | undefined) {
if (!filePath) return ''
return this.isWindows
? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+|\\+$/g, '')
? filePath
.split(path.posix.sep)
.join(path.sep)
.replace(/^\\+|\\+$/g, '')
: `/${filePath.replace(/^\/+|\/+$/g, '')}`
}
+86 -41
View File
@@ -1,4 +1,3 @@
import axios from 'axios'
import { ipcMain, IpcMainEvent } from 'electron'
import path from 'path'
@@ -7,7 +6,13 @@ import qiniu from 'qiniu/index'
import windowManager from 'apis/app/window/windowManager'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import { hmacSha1Base64, getFileMimeType, NewDownloader, formatError, ConcurrencyPromisePool } from '~/manage/utils/common'
import {
hmacSha1Base64,
getFileMimeType,
NewDownloader,
formatError,
ConcurrencyPromisePool
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, IWindowList, uploadTaskSpecialStatus } from '#/types/enum'
@@ -79,7 +84,7 @@ class QiniuApi {
if (xQiniuHeaders) {
const xQiniuHeaderStr = Object.keys(xQiniuHeaders)
.sort()
.map((key) => `\n${key}:${xQiniuHeaders[key]}`)
.map(key => `\n${key}:${xQiniuHeaders[key]}`)
.join('')
signStr += xQiniuHeaderStr
}
@@ -252,11 +257,14 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do {
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
limit: 1000
}, (err: any, respBody: any, respInfo: any) => {
},
(err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
@@ -265,10 +273,13 @@ class QiniuApi {
respInfo
})
}
})
}
)
})
if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
@@ -308,12 +319,15 @@ class QiniuApi {
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
do {
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
bucketManager.listPrefix(
bucket,
{
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
delimiter: '/',
marker,
limit: 1000
}, (err: any, respBody: any, respInfo: any) => {
},
(err: any, respBody: any, respInfo: any) => {
if (err) {
reject(err)
} else {
@@ -322,13 +336,18 @@ class QiniuApi {
respInfo
})
}
})
}
)
})
if (res && res.respInfo.statusCode === 200) {
res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => {
res.respBody &&
res.respBody.commonPrefixes &&
res.respBody.commonPrefixes.forEach((item: any) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.respBody && res.respBody.items && res.respBody.items.forEach((item: any) => {
res.respBody &&
res.respBody.items &&
res.respBody.items.forEach((item: any) => {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send('refreshFileTransferList', result)
@@ -374,12 +393,15 @@ class QiniuApi {
success: false
}
res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucket, {
bucketManager.listPrefix(
bucket,
{
limit: itemsPerPage,
prefix: slicedPrefix === '' ? undefined : slicedPrefix,
marker,
delimiter: '/'
}, (err, respBody, respInfo) => {
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
@@ -388,7 +410,8 @@ class QiniuApi {
respInfo
})
}
})
}
)
})
if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.commonPrefixes) {
@@ -401,7 +424,7 @@ class QiniuApi {
item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
}
result.isTruncated = !!(res.respBody?.marker)
result.isTruncated = !!res.respBody?.marker
result.nextMarker = res.respBody?.marker ? res.respBody.marker : ''
result.success = true
}
@@ -421,7 +444,7 @@ class QiniuApi {
const { bucketName, key } = configMap
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
const res = await new Promise((resolve, reject) => {
const res = (await new Promise((resolve, reject) => {
bucketManager.delete(bucketName, key, (err, respBody, respInfo) => {
if (err) {
reject(err)
@@ -432,7 +455,7 @@ class QiniuApi {
})
}
})
}) as any
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -450,12 +473,15 @@ class QiniuApi {
Contents: [] as any[]
}
do {
const res = await new Promise((resolve, reject) => {
bucketManager.listPrefix(bucketName, {
const res = (await new Promise((resolve, reject) => {
bucketManager.listPrefix(
bucketName,
{
prefix: key,
marker,
limit: 1000
}, (err, respBody, respInfo) => {
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
@@ -464,13 +490,14 @@ class QiniuApi {
respInfo
})
}
})
}) as any
}
)
})) as any
if (res?.respInfo?.statusCode === 200) {
if (res.respBody?.items) {
allFileList.Contents = allFileList.Contents.concat(res.respBody.items)
}
isTruncated = !!(res.respBody?.marker)
isTruncated = !!res.respBody?.marker
marker = res.respBody?.marker ? res.respBody.marker : ''
} else {
return false
@@ -481,7 +508,7 @@ class QiniuApi {
const deleteOps = allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
return qiniu.rs.deleteOp(bucketName, item.key)
})
const res = await new Promise((resolve, reject) => {
const res = (await new Promise((resolve, reject) => {
bucketManager.batch(deleteOps, (err, respBody, respInfo) => {
if (err) {
reject(err)
@@ -492,7 +519,7 @@ class QiniuApi {
})
}
})
}) as any
})) as any
if (res?.respInfo?.statusCode !== 200) return false
}
return true
@@ -512,10 +539,16 @@ class QiniuApi {
const { bucketName, oldKey, newKey } = configMap
const config = new qiniu.conf.Config()
const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
const res = await new Promise((resolve, reject) => {
bucketManager.move(bucketName, oldKey, bucketName, newKey, {
const res = (await new Promise((resolve, reject) => {
bucketManager.move(
bucketName,
oldKey,
bucketName,
newKey,
{
force: true
}, (err, respBody, respInfo) => {
},
(err, respBody, respInfo) => {
if (err) {
reject(err)
} else {
@@ -524,8 +557,9 @@ class QiniuApi {
respInfo
})
}
})
}) as any
}
)
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -585,7 +619,7 @@ class QiniuApi {
putExtra.version = 'v2'
putExtra.partSize = 4 * 1024 * 1024
putExtra.progressCallback = (uploadBytes, totalBytes) => {
const progress = Math.floor(uploadBytes / totalBytes * 100)
const progress = Math.floor((uploadBytes / totalBytes) * 100)
instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`,
progress,
@@ -594,7 +628,12 @@ class QiniuApi {
}
resumeUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => {
if (respErr) {
this.logger.error(formatError(respErr, { class: 'Qiniu', method: 'uploadBucketFile' }))
this.logger.error(
formatError(respErr, {
class: 'Qiniu',
method: 'uploadBucketFile'
})
)
instance.updateUploadTask({
id: `${bucketName}-${region}-${key}-${filePath}`,
progress: 0,
@@ -636,7 +675,7 @@ class QiniuApi {
const uploadToken = putPolicy.uploadToken(this.mac)
const FormUploader = new qiniu.form_up.FormUploader()
const putExtra = new qiniu.form_up.PutExtra()
const res = await new Promise((resolve, reject) => {
const res = (await new Promise((resolve, reject) => {
FormUploader.put(uploadToken, key, '', putExtra, (err, respBody, respInfo) => {
if (err) {
reject(err)
@@ -647,7 +686,7 @@ class QiniuApi {
})
}
})
}) as any
})) as any
return res?.respInfo?.statusCode === 200
}
@@ -673,20 +712,26 @@ class QiniuApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
const preSignedUrl = await this.getPreSignedUrl({ key, expires: 36000, customUrl })
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
const preSignedUrl = await this.getPreSignedUrl({
key,
expires: 36000,
customUrl
})
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'QiniuApi', method: 'downloadBucketFile' }))
})
return true
+78 -32
View File
@@ -77,7 +77,7 @@ class S3plistApi {
async getDogeCloudToken() {
if (!this.dogeCloudSupport) return
const token = await getTempToken(this.accessKeyId, this.secretAccessKey) as DogecloudToken
const token = (await getTempToken(this.accessKeyId, this.secretAccessKey)) as DogecloudToken
if (Object.keys(token).length === 0) {
throw new Error('manage.setting.dogeCloudTokenError')
}
@@ -115,8 +115,7 @@ class S3plistApi {
})
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'S3plistApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'S3plistApi', method }))
formatFolder(item: CommonPrefix, slicedPrefix: string, urlPrefix: string): any {
return {
@@ -186,7 +185,7 @@ class S3plistApi {
const command = new ListBucketsCommand({})
const data = await client.send(command)
if (data.$metadata.httpStatusCode === 200) {
const bucketList = data.Buckets?.map((item) => item.Name)
const bucketList = data.Buckets?.map(item => item.Name)
if (bucketList?.includes(BucketName)) {
return true
}
@@ -258,7 +257,7 @@ class S3plistApi {
}
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
const result: IStringKeyMap[] = []
const endpoint = options.endpoint as string || ''
const endpoint = (options.endpoint as string) || ''
options.region = endpoint.includes('cloudflarestorage') ? 'auto' : 'us-east-1'
try {
const client = new S3Client(options)
@@ -271,21 +270,26 @@ class S3plistApi {
if (data.Buckets) {
if (endpoint.includes('cloudflarestorage')) {
result.push(...data.Buckets.map(bucket => ({
result.push(
...data.Buckets.map(bucket => ({
Name: bucket.Name,
CreationDate: bucket.CreationDate,
Location: 'auto'
})))
}))
)
} else {
for (const bucket of data.Buckets) {
const bucketName = bucket.Name
const bucketConfig = await client.send(new GetBucketLocationCommand({
const bucketConfig = await client.send(
new GetBucketLocationCommand({
Bucket: bucketName
}))
})
)
result.push({
Name: bucketName,
CreationDate: bucket.CreationDate,
Location: bucketConfig.$metadata.httpStatusCode === 200
Location:
bucketConfig.$metadata.httpStatusCode === 200
? bucketConfig.LocationConstraint?.toLowerCase() || 'us-east-1'
: 'us-east-1'
})
@@ -303,7 +307,12 @@ class S3plistApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker
@@ -333,7 +342,8 @@ class S3plistApi {
})
res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) {
res.Contents && res.Contents.forEach((item: _Object) => {
res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send(refreshDownloadFileTransferList, result)
@@ -361,7 +371,12 @@ class S3plistApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
let marker
@@ -393,10 +408,12 @@ class S3plistApi {
})
res = await client.send(command)
if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && res.CommonPrefixes.forEach((item: CommonPrefix) => {
res.CommonPrefixes &&
res.CommonPrefixes.forEach((item: CommonPrefix) => {
result.fullList.push(this.formatFolder(item, slicedPrefix, urlPrefix))
})
res.Contents && res.Contents.forEach((item: _Object) => {
res.Contents &&
res.Contents.forEach((item: _Object) => {
result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
})
window.webContents.send('refreshFileTransferList', result)
@@ -423,7 +440,13 @@ class S3plistApi {
}
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = configMap.customUrl || `https://${bucket}.s3.amazonaws.com`
const result = {
@@ -434,7 +457,10 @@ class S3plistApi {
}
try {
await this.getDogeCloudToken()
const options = Object.assign({}, { ...this.baseOptions, region: String(region) || 'us-east-1' }) as S3ClientConfig
const options = Object.assign(
{},
{ ...this.baseOptions, region: String(region) || 'us-east-1' }
) as S3ClientConfig
const client = new S3Client(options)
const command = new ListObjectsV2Command({
Bucket: bucket,
@@ -474,7 +500,10 @@ class S3plistApi {
let result = false
try {
await this.getDogeCloudToken()
const options = Object.assign({}, { ...this.baseOptions, region: String(region) || 'us-east-1' }) as S3ClientConfig
const options = Object.assign(
{},
{ ...this.baseOptions, region: String(region) || 'us-east-1' }
) as S3ClientConfig
const client = new S3Client(options)
const command = new CopyObjectCommand({
Bucket: bucketName,
@@ -562,7 +591,7 @@ class S3plistApi {
Delimiter: '/',
MaxKeys: 1000
})
res = await client.send(command) as ListObjectsV2CommandOutput
res = (await client.send(command)) as ListObjectsV2CommandOutput
if (res.$metadata.httpStatusCode === 200) {
res.CommonPrefixes && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
res.Contents && allFileList.Contents.push(...res.Contents)
@@ -595,7 +624,7 @@ class S3plistApi {
const deleteCommand = new DeleteObjectsCommand({
Bucket: bucketName,
Delete: {
Objects: deleteList.map((item) => {
Objects: deleteList.map(item => {
return {
Key: item.Key
}
@@ -635,12 +664,16 @@ class S3plistApi {
const options = Object.assign({}, this.baseOptions) as S3ClientConfig
options.region = String(region) || 'us-east-1'
const client = new S3Client(options)
const signedUrl = await getSignedUrl(client, new GetObjectCommand({
const signedUrl = await getSignedUrl(
client,
new GetObjectCommand({
Bucket: bucketName,
Key: key
}), {
}),
{
expiresIn: expires || 3600
})
}
)
return signedUrl
} catch (error) {
this.logParam(error, 'getPreSignedUrl')
@@ -693,7 +726,15 @@ class S3plistApi {
fileArray.forEach((item: any) => {
item.key.startsWith('/') && (item.key = item.key.slice(1))
})
const allowedAcl = ['private', 'public-read', 'public-read-write', 'aws-exec-read', 'authenticated-read', 'bucket-owner-read', 'bucket-owner-full-control']
const allowedAcl = [
'private',
'public-read',
'public-read-write',
'aws-exec-read',
'authenticated-read',
'bucket-owner-read',
'bucket-owner-full-control'
]
for (const item of fileArray) {
const { bucketName, region, key, filePath, fileName, aclForUpload } = item
const id = `${bucketName}-${String(region)}-${key}-${filePath}`
@@ -743,11 +784,13 @@ class S3plistApi {
parallelUploads3.on('httpUploadProgress', (progress: Progress) => {
instance.updateUploadTask({
id,
progress: progress.loaded && progress.total ? Math.floor(progress.loaded / progress.total * 100) : 0,
progress: progress.loaded && progress.total ? Math.floor((progress.loaded / progress.total) * 100) : 0,
status: uploadTaskSpecialStatus.uploading
})
})
parallelUploads3.done().then((data) => {
parallelUploads3
.done()
.then(data => {
if (data.$metadata.httpStatusCode === 200) {
instance.updateUploadTask({
id,
@@ -763,7 +806,8 @@ class S3plistApi {
finishTime: new Date().toLocaleString()
})
}
}).catch((error) => {
})
.catch(error => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
@@ -806,19 +850,21 @@ class S3plistApi {
expires: 36000,
customUrl
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxy).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile')
})
return true
+3 -3
View File
@@ -76,8 +76,7 @@ class SftpApi {
}
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'SftpApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'SftpApi', method }))
transFormPermission = (permissionsStr: string) => {
const permissions = permissionsStr.length === 10 ? permissionsStr.slice(1) : permissionsStr
@@ -250,7 +249,8 @@ class SftpApi {
if (formatedLSRes.length) {
formatedLSRes.forEach((item: listDirResult) => {
const relativePath = path.relative(baseDir, item.key.startsWith('/') ? item.key : `/${item.key}`)
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
const relative =
webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
if (item.isDir) {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else {
+12 -18
View File
@@ -71,9 +71,7 @@ class SmmsApi {
finished: false
}
do {
res = await this.axiosInstance(
'/upload_history',
{
res = await this.axiosInstance('/upload_history', {
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
@@ -131,9 +129,7 @@ class SmmsApi {
nextMarker: '',
success: false
}
const res = await this.axiosInstance(
'/upload_history',
{
const res = await this.axiosInstance('/upload_history', {
method: 'GET',
headers: {
'Content-Type': 'multipart/form-data'
@@ -141,8 +137,7 @@ class SmmsApi {
params: {
page: currentPage
}
}
)
})
if (res?.status !== 200 || !res?.data?.success) return result
if (res.data.Count === 0) return { ...result, success: true }
@@ -167,16 +162,13 @@ class SmmsApi {
* }
*/
async deleteBucketFile({ DeleteHash }: IStringKeyMap): Promise<boolean> {
const res = await this.axiosInstance(
`/delete/${DeleteHash}`,
{
const res = await this.axiosInstance(`/delete/${DeleteHash}`, {
method: 'GET',
params: {
hash: DeleteHash,
format: 'json'
}
}
)
})
return res?.status === 200 && res?.data?.success
}
@@ -239,19 +231,21 @@ class SmmsApi {
sourceFileName: fileName,
targetFilePath: savedFilePath
})
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'SmmsApi', method: 'downloadBucketFile' }))
})
return true
+66 -21
View File
@@ -1,4 +1,3 @@
import COS from 'cos-nodejs-sdk-v5'
import { ipcMain, IpcMainEvent } from 'electron'
import fs from 'fs-extra'
@@ -99,7 +98,13 @@ class TcyunApi {
async getBucketListRecursively(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1, prefix.length)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false]
@@ -125,8 +130,11 @@ class TcyunApi {
Marker: marker
})
if (res?.statusCode === 200) {
result.fullList.push(...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix)))
result.fullList.push(
...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
)
window.webContents.send(refreshDownloadFileTransferList, result)
} else {
result.finished = true
@@ -144,7 +152,13 @@ class TcyunApi {
async getBucketListBackstage(configMap: IStringKeyMap): Promise<any> {
const window = windowManager.get(IWindowList.SETTING_WINDOW)!
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, cancelToken } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
cancelToken
} = configMap
const slicedPrefix = prefix.slice(1, prefix.length)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const cancelTask = [false]
@@ -173,8 +187,9 @@ class TcyunApi {
if (res?.statusCode === 200) {
result.fullList.push(
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix))
...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
)
window.webContents.send('refreshFileTransferList', result)
} else {
@@ -207,17 +222,24 @@ class TcyunApi {
* }
*/
async getBucketFileList(configMap: IStringKeyMap): Promise<any> {
const { bucketName: bucket, bucketConfig: { Location: region }, prefix, customUrl, marker, itemsPerPage } = configMap
const {
bucketName: bucket,
bucketConfig: { Location: region },
prefix,
customUrl,
marker,
itemsPerPage
} = configMap
const slicedPrefix = prefix.slice(1)
const urlPrefix = customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
const res = await this.ctx.getBucket({
const res = (await this.ctx.getBucket({
Bucket: bucket,
Region: region,
Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
Delimiter: '/',
Marker: marker,
MaxKeys: itemsPerPage
}) as COS.GetBucketResult
})) as COS.GetBucketResult
if (res?.statusCode !== 200) {
return {
fullList: [],
@@ -229,8 +251,9 @@ class TcyunApi {
const result = {
fullList: [
...res.CommonPrefixes.map(item => this.formatFolder(item, slicedPrefix, urlPrefix)),
...res.Contents.filter(item => parseInt(item.Size) !== 0)
.map(item => this.formatFile(item, slicedPrefix, urlPrefix))
...res.Contents.filter(item => parseInt(item.Size) !== 0).map(item =>
this.formatFile(item, slicedPrefix, urlPrefix)
)
],
isTruncated: res.IsTruncated === 'true',
nextMarker: res.NextMarker || '',
@@ -317,7 +340,14 @@ class TcyunApi {
marker = res.NextMarker
} while (res.IsTruncated === 'true')
for (const item of allFileList.CommonPrefixes) {
if (!(await this.deleteBucketFolder({ bucketName, region, key: item.Prefix }))) return false
if (
!(await this.deleteBucketFolder({
bucketName,
region,
key: item.Prefix
}))
)
return false
}
const cycles = Math.ceil(allFileList.Contents.length / 1000)
for (let i = 0; i < cycles; i++) {
@@ -344,14 +374,16 @@ class TcyunApi {
*/
async getPreSignedUrl(configMap: IStringKeyMap): Promise<string> {
const { bucketName, region, key, expires, customUrl } = configMap
const res = this.ctx.getObjectUrl({
const res = this.ctx.getObjectUrl(
{
Bucket: bucketName,
Region: region,
Key: key,
Expires: expires,
Sign: true
}, () => {
})
},
() => {}
)
return customUrl ? `${customUrl.replace(/\/+$/, '')}/${key}${res.slice(res.indexOf('?'))}` : res
}
@@ -412,7 +444,12 @@ class TcyunApi {
finishTime: new Date().toLocaleString()
})
} else {
this.logger.error(formatError(err, { method: 'uploadBucketFile', class: 'TcyunApi' }))
this.logger.error(
formatError(err, {
method: 'uploadBucketFile',
class: 'TcyunApi'
})
)
instance.updateUploadTask({
id,
progress: 0,
@@ -472,7 +509,8 @@ class TcyunApi {
targetFilePath: path.join(downloadPath, fileName)
})
fs.ensureDirSync(path.dirname(path.join(downloadPath, fileName)))
this.ctx.downloadFile({
this.ctx
.downloadFile({
Bucket: bucketName,
Region: region,
Key: key,
@@ -486,7 +524,8 @@ class TcyunApi {
status: downloadTaskSpecialStatus.downloading
})
}
}).then((res: any) => {
})
.then((res: any) => {
instance.updateDownloadTask({
id,
progress: res && res.statusCode === 200 ? 100 : 0,
@@ -494,8 +533,14 @@ class TcyunApi {
response: typeof res === 'object' ? JSON.stringify(res) : String(res),
finishTime: new Date().toLocaleString()
})
}).catch((err: any) => {
this.logger.error(formatError(err, { method: 'downloadBucketFile', class: 'TcyunApi' }))
})
.catch((err: any) => {
this.logger.error(
formatError(err, {
method: 'downloadBucketFile',
class: 'TcyunApi'
})
)
instance.updateDownloadTask({
id,
progress: 0,
+29 -16
View File
@@ -7,7 +7,15 @@ import Upyun from 'upyun'
import windowManager from 'apis/app/window/windowManager'
import { md5, hmacSha1Base64, getFileMimeType, NewDownloader, gotUpload, ConcurrencyPromisePool, formatError } from '~/manage/utils/common'
import {
md5,
hmacSha1Base64,
getFileMimeType,
NewDownloader,
gotUpload,
ConcurrencyPromisePool,
formatError
} from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
@@ -26,7 +34,14 @@ class UpyunApi {
stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
logger: ManageLogger
constructor (bucket: string, operator: string, password: string, logger: ManageLogger, antiLeechToken?: string, expireTime?: number) {
constructor(
bucket: string,
operator: string,
password: string,
logger: ManageLogger,
antiLeechToken?: string,
expireTime?: number
) {
this.ser = new Upyun.Service(bucket, operator, password)
this.cli = new Upyun.Client(this.ser)
this.bucket = bucket
@@ -87,13 +102,7 @@ class UpyunApi {
}
}
authorization (
method: string,
uri: string,
contentMd5: string,
operator: string,
password: string
) {
authorization(method: string, uri: string, contentMd5: string, operator: string, password: string) {
return `UPYUN ${operator}:${hmacSha1Base64(
md5(password, 'hex'),
`${method.toUpperCase()}&${encodeURI(uri)}&${new Date().toUTCString()}${contentMd5 ? `&${contentMd5}` : ''}`
@@ -313,11 +322,13 @@ class UpyunApi {
})
if (res) {
res.files.forEach((item: any) => {
item.type === 'N' && allFileList.Contents.push({
item.type === 'N' &&
allFileList.Contents.push({
...item,
key: `${key}${item.name}`
})
item.type === 'F' && allFileList.CommonPrefixes.push({
item.type === 'F' &&
allFileList.CommonPrefixes.push({
...item,
key: `${key}${item.name}/`
})
@@ -445,19 +456,21 @@ class UpyunApi {
targetFilePath: savedFilePath
})
const preSignedUrl = `${customUrl}/${key}`
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger).then((res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
})
}))
})
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logger.error(formatError(error, { class: 'UpyunApi', method: 'downloadBucketFile' }))
})
return true
+35 -21
View File
@@ -29,7 +29,15 @@ class WebdavplistApi {
agent: https.Agent | http.Agent
ctx: WebDAVClient
constructor (endpoint: string, username: string, password: string, sslEnabled: boolean, proxy: string | undefined, authType: 'basic' | 'digest' | undefined, logger: ManageLogger) {
constructor(
endpoint: string,
username: string,
password: string,
sslEnabled: boolean,
proxy: string | undefined,
authType: 'basic' | 'digest' | undefined,
logger: ManageLogger
) {
this.endpoint = formatEndpoint(endpoint, sslEnabled)
this.username = username
this.password = password
@@ -50,14 +58,10 @@ class WebdavplistApi {
if (this.authType === 'digest') {
options.authType = AuthType.Digest
}
this.ctx = createClient(
this.endpoint,
options
)
this.ctx = createClient(this.endpoint, options)
}
logParam = (error:any, method: string) =>
this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
logParam = (error: any, method: string) => this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
formatFolder(item: FileStat, urlPrefix: string, isWebPath = false) {
const key = item.filename.replace(/^\/+/, '')
@@ -166,7 +170,8 @@ class WebdavplistApi {
if (res.data?.length) {
res.data.forEach((item: FileStat) => {
const relativePath = path.relative(baseDir, item.filename)
const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
const relative =
webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/')
if (item.type === 'directory') {
result.fullList.push(this.formatFolder(item, webPath ? relative : urlPrefix, !!webPath))
} else {
@@ -261,10 +266,8 @@ class WebdavplistApi {
targetFileRegion: region,
noProgress: true
})
this.ctx.putFileContents(
key,
this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath),
{
this.ctx
.putFileContents(key, this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath), {
overwrite: true,
onUploadProgress: (progressEvent: ProgressEvent) => {
instance.updateUploadTask({
@@ -273,8 +276,8 @@ class WebdavplistApi {
status: uploadTaskSpecialStatus.uploading
})
}
}
).then((res: boolean) => {
})
.then((res: boolean) => {
if (res) {
instance.updateUploadTask({
id,
@@ -290,7 +293,8 @@ class WebdavplistApi {
finishTime: new Date().toLocaleString()
})
}
}).catch((error: any) => {
})
.catch((error: any) => {
this.logParam(error, 'uploadBucketFile')
instance.updateUploadTask({
id,
@@ -345,25 +349,35 @@ class WebdavplistApi {
Authorization: `Basic ${base64Str}`
}
} else if (this.authType === 'digest') {
const authHeader = await getAuthHeader('GET', this.endpoint, `/${key.replace(/^\/+/, '')}`, this.username, this.password)
const authHeader = await getAuthHeader(
'GET',
this.endpoint,
`/${key.replace(/^\/+/, '')}`,
this.username,
this.password
)
headers = {
Authorization: authHeader
}
preSignedUrl = `${this.endpoint}/${key.replace(/^\/+/, '')}`
}
promises.push(() => new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers)
.then((res: boolean) => {
promises.push(
() =>
new Promise((resolve, reject) => {
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers).then(
(res: boolean) => {
if (res) {
resolve(res)
} else {
reject(res)
}
}
)
})
}))
)
}
const pool = new ConcurrencyPromisePool(maxDownloadFileCount)
pool.all(promises).catch((error) => {
pool.all(promises).catch(error => {
this.logParam(error, 'downloadBucketFile')
})
return true
+13 -9
View File
@@ -42,9 +42,13 @@ function manageDbChecker () {
fs.unlinkSync(manageConfigFilePath)
if (fs.existsSync(manageConfigFileBackupPath)) {
try {
configFile = fs.readFileSync(manageConfigFileBackupPath, { encoding: 'utf-8' })
configFile = fs.readFileSync(manageConfigFileBackupPath, {
encoding: 'utf-8'
})
JSON.parse(configFile)
writeFile.sync(manageConfigFilePath, configFile, { encoding: 'utf-8' })
writeFile.sync(manageConfigFilePath, configFile, {
encoding: 'utf-8'
})
const stats = fs.statSync(manageConfigFileBackupPath)
optionsTpl.body = `${errorMsg.brokenButBackup}\n${T('TIPS_PICGO_BACKUP_FILE_VERSION', {
v: dayjs(stats.mtime).format('YYYY-MM-DD HH:mm:ss')
@@ -61,7 +65,9 @@ function manageDbChecker () {
global.notificationList.push(optionsTpl)
return
}
writeFile.sync(manageConfigFileBackupPath, configFile, { encoding: 'utf-8' })
writeFile.sync(manageConfigFileBackupPath, configFile, {
encoding: 'utf-8'
})
}
}
@@ -80,7 +86,9 @@ function managePathChecker (): string {
return _configFilePath
}
try {
const configString = fs.readFileSync(defaultManageConfigPath, { encoding: 'utf-8' })
const configString = fs.readFileSync(defaultManageConfigPath, {
encoding: 'utf-8'
})
const config = JSON.parse(configString)
const userConfigPath: string = config.configPath || ''
if (userConfigPath) {
@@ -111,8 +119,4 @@ function managePathDir () {
return path.dirname(managePathChecker())
}
export {
managePathChecker,
managePathDir,
manageDbChecker
}
export { managePathChecker, managePathDir, manageDbChecker }
+27 -8
View File
@@ -104,11 +104,21 @@ class UpDownTaskQueue {
}
removeUploadedTask() {
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(item => item.status !== uploadTaskSpecialStatus.uploaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed)
UpDownTaskQueue.getInstance().uploadTaskQueue = UpDownTaskQueue.getInstance().uploadTaskQueue.filter(
item =>
item.status !== uploadTaskSpecialStatus.uploaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
}
removeDownloadedTask() {
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(item => item.status !== downloadTaskSpecialStatus.downloaded && item.status !== commonTaskStatus.canceled && item.status !== commonTaskStatus.failed)
UpDownTaskQueue.getInstance().downloadTaskQueue = UpDownTaskQueue.getInstance().downloadTaskQueue.filter(
item =>
item.status !== downloadTaskSpecialStatus.downloaded &&
item.status !== commonTaskStatus.canceled &&
item.status !== commonTaskStatus.failed
)
}
clearDownloadTaskQueue() {
@@ -123,10 +133,13 @@ class UpDownTaskQueue {
persist() {
try {
this.checkPersistPath()
fs.writeFileSync(this.persistPath, JSON.stringify({
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
})
)
} catch (e) {
console.log(e)
}
@@ -146,18 +159,24 @@ class UpDownTaskQueue {
private checkPersistPath() {
if (!fs.existsSync(this.persistPath)) {
fs.writeFileSync(this.persistPath, JSON.stringify({
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
})
)
}
try {
JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
} catch (e) {
fs.writeFileSync(this.persistPath, JSON.stringify({
fs.writeFileSync(
this.persistPath,
JSON.stringify({
uploadTaskQueue: this.uploadTaskQueue,
downloadTaskQueue: this.downloadTaskQueue
}))
})
)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
export const PICLIST_MANAGE_GET_CONFIG = 'PICLIST_MANAGE_GET_CONFIG'
export const PICLIST_MANAGE_REMOVE_CONFIG = 'PICLIST_MANAGE_REMOVE_CONFIG'
export const PICLIST_MANAGE_SAVE_CONFIG = 'PICLIST_MANAGE_SAVE_CONFIG'
-155
View File
@@ -1,155 +0,0 @@
import { ipcMain, IpcMainInvokeEvent, dialog, app, shell } from 'electron'
import fs from 'fs-extra'
import path from 'path'
import UpDownTaskQueue from '~/manage/datastore/upDownTaskQueue'
import manageCoreIPC from '~/manage/events/manageCoreIPC'
import { ManageApi } from '~/manage/manageApi'
import { downloadFileFromUrl } from '~/manage/utils/common'
import { selectDownloadFolder } from '#/utils/static'
export const manageIpcList = {
listen () {
manageCoreIPC.listen()
ipcMain.handle('getBucketList', async (_: IpcMainInvokeEvent, currentPicBed: string) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketList()
})
ipcMain.handle('createBucket', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.createBucket(param)
})
ipcMain.handle('getBucketFileList', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketFileList(param)
})
ipcMain.handle('getBucketDomain', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
const result = await manage.getBucketDomain(param)
return result
})
ipcMain.handle('setBucketAclPolicy', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.setBucketAclPolicy(param)
})
ipcMain.handle('renameBucketFile', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.renameBucketFile(param)
})
ipcMain.handle('deleteBucketFile', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.deleteBucketFile(param)
})
ipcMain.handle('deleteBucketFolder', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.deleteBucketFolder(param)
})
ipcMain.on('getBucketListBackstage', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketListBackstage(param)
})
ipcMain.on('getBucketListRecursively', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getBucketListRecursively(param)
})
ipcMain.handle('convertPathToBase64', async (_: IpcMainInvokeEvent, filePath: string) => {
const res = fs.readFileSync(filePath, 'base64')
return res
})
ipcMain.handle('openFileSelectDialog', async () => {
const res = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections']
})
if (res.canceled) {
return []
} else {
return res.filePaths
}
})
ipcMain.handle('getPreSignedUrl', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.getPreSignedUrl(param)
})
ipcMain.handle('getUploadTaskList', async () => {
return UpDownTaskQueue.getInstance().getAllUploadTask()
})
ipcMain.handle('getDownloadTaskList', async () => {
return UpDownTaskQueue.getInstance().getAllDownloadTask()
})
ipcMain.on('uploadBucketFile', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.uploadBucketFile(param)
})
ipcMain.on('downloadBucketFile', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.downloadBucketFile(param)
})
ipcMain.handle('createBucketFolder', async (_: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
const manage = new ManageApi(currentPicBed)
return manage.createBucketFolder(param)
})
ipcMain.on('deleteUploadedTask', async () => {
UpDownTaskQueue.getInstance().removeUploadedTask()
})
ipcMain.on('deleteAllUploadedTask', async () => {
UpDownTaskQueue.getInstance().clearUploadTaskQueue()
})
ipcMain.on('deleteDownloadedTask', async () => {
UpDownTaskQueue.getInstance().removeDownloadedTask()
})
ipcMain.on('deleteAllDownloadedTask', async () => {
UpDownTaskQueue.getInstance().clearDownloadTaskQueue()
})
ipcMain.handle(selectDownloadFolder, async () => {
const res = await dialog.showOpenDialog({
properties: ['openDirectory']
})
return res.filePaths[0]
})
ipcMain.handle('getDefaultDownloadFolder', async () => {
return app.getPath('downloads')
})
ipcMain.on('OpenDownloadedFolder', async (_: IpcMainInvokeEvent, path: string | undefined) => {
if (path) {
shell.showItemInFolder(path)
} else {
shell.openPath(app.getPath('downloads'))
}
})
ipcMain.on('OpenLocalFile', async (_: IpcMainInvokeEvent, fullPath: string) => {
fs.existsSync(fullPath) ? shell.showItemInFolder(fullPath) : shell.openPath(path.dirname(fullPath))
})
ipcMain.handle('downloadFileFromUrl', async (_: IpcMainInvokeEvent, urls: string[]) => {
const res = await downloadFileFromUrl(urls)
return res
})
}
}
-36
View File
@@ -1,36 +0,0 @@
import {
IpcMainEvent,
IpcMainInvokeEvent,
ipcMain
} from 'electron'
import getManageApi from '~/manage/Main'
import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from '~/manage/events/constants'
const manageApi = getManageApi()
const handleManageGetConfig = () => {
ipcMain.handle(PICLIST_MANAGE_GET_CONFIG, (_: IpcMainInvokeEvent, key: string | undefined) => {
return manageApi.getConfig(key)
})
}
const handleManageSaveConfig = () => {
ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_: IpcMainEvent, data: any) => {
manageApi.saveConfig(data)
})
}
const handleManageRemoveConfig = () => {
ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_: IpcMainEvent, key: string, propName: string) => {
manageApi.removeConfig(key, propName)
})
}
export default {
listen () {
handleManageGetConfig()
handleManageSaveConfig()
handleManageRemoveConfig()
}
}
+88 -87
View File
@@ -1,4 +1,3 @@
import { ipcMain } from 'electron'
import { EventEmitter } from 'events'
import fs from 'fs-extra'
@@ -15,12 +14,7 @@ import { isInputConfigValid, formatError } from '~/manage/utils/common'
import { ManageLogger } from '~/manage/utils/logger'
import { IWindowList } from '#/types/enum'
import {
IManageApiType,
IManageConfigType,
IManageError,
IPicBedMangeConfig
} from '#/types/manage'
import { IManageApiType, IManageConfigType, IManageError, IPicBedMangeConfig } from '#/types/manage'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '#/utils/static'
export class ManageApi extends EventEmitter implements IManageApiType {
@@ -58,11 +52,25 @@ export class ManageApi extends EventEmitter implements IManageApiType {
const name = this.currentPicBedConfig.picBedName
switch (name) {
case 'aliyun':
return new API.AliyunApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.accessKeySecret, this.logger)
return new API.AliyunApi(
this.currentPicBedConfig.accessKeyId,
this.currentPicBedConfig.accessKeySecret,
this.logger
)
case 'github':
return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger)
return new API.GithubApi(
this.currentPicBedConfig.token,
this.currentPicBedConfig.githubUsername,
this.currentPicBedConfig.proxy,
this.logger
)
case 'imgur':
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger)
return new API.ImgurApi(
this.currentPicBedConfig.imgurUserName,
this.currentPicBedConfig.accessToken,
this.currentPicBedConfig.proxy,
this.logger
)
case 'local':
return new API.LocalApi(this.logger)
case 'qiniu':
@@ -70,15 +78,50 @@ export class ManageApi extends EventEmitter implements IManageApiType {
case 'smms':
return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
case 's3plist':
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger, this.currentPicBedConfig.dogeCloudSupport || false, this.currentPicBedConfig.bucketName || '')
return new API.S3plistApi(
this.currentPicBedConfig.accessKeyId,
this.currentPicBedConfig.secretAccessKey,
this.currentPicBedConfig.endpoint,
this.currentPicBedConfig.sslEnabled,
this.currentPicBedConfig.s3ForcePathStyle,
this.currentPicBedConfig.proxy,
this.logger,
this.currentPicBedConfig.dogeCloudSupport || false,
this.currentPicBedConfig.bucketName || ''
)
case 'sftp':
return new API.SftpApi(this.currentPicBedConfig.host, this.currentPicBedConfig.port, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.privateKey, this.currentPicBedConfig.passphrase, this.currentPicBedConfig.fileMode, this.currentPicBedConfig.dirMode, this.logger)
return new API.SftpApi(
this.currentPicBedConfig.host,
this.currentPicBedConfig.port,
this.currentPicBedConfig.username,
this.currentPicBedConfig.password,
this.currentPicBedConfig.privateKey,
this.currentPicBedConfig.passphrase,
this.currentPicBedConfig.fileMode,
this.currentPicBedConfig.dirMode,
this.logger
)
case 'tcyun':
return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
case 'upyun':
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger, this.currentPicBedConfig.antiLeechToken, this.currentPicBedConfig.expireTime)
return new API.UpyunApi(
this.currentPicBedConfig.bucketName,
this.currentPicBedConfig.operator,
this.currentPicBedConfig.password,
this.logger,
this.currentPicBedConfig.antiLeechToken,
this.currentPicBedConfig.expireTime
)
case 'webdavplist':
return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.currentPicBedConfig.authType, this.logger)
return new API.WebdavplistApi(
this.currentPicBedConfig.endpoint,
this.currentPicBedConfig.username,
this.currentPicBedConfig.password,
this.currentPicBedConfig.sslEnabled,
this.currentPicBedConfig.proxy,
this.currentPicBedConfig.authType,
this.logger
)
default:
return {} as any
}
@@ -117,9 +160,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
saveConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) {
this.logger.warn(
'the format of config is invalid, please provide object'
)
this.logger.warn('the format of config is invalid, please provide object')
return
}
this.setConfig(config)
@@ -136,9 +177,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
setConfig(config: IStringKeyMap): void {
if (!isInputConfigValid(config)) {
this.logger.warn(
'the format of config is invalid, please provide object'
)
this.logger.warn('the format of config is invalid, please provide object')
return
}
Object.keys(config).forEach((name: string) => {
@@ -151,9 +190,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
unset(this.getConfig(key), propName)
}
async getBucketList (
param?: IStringKeyMap | undefined
): Promise<any> {
async getBucketList(param?: IStringKeyMap | undefined): Promise<any> {
let client
const name = this.currentPicBedConfig.picBedName.replace('plist', '')
switch (this.currentPicBedConfig.picBedName) {
@@ -171,36 +208,36 @@ export class ManageApi extends EventEmitter implements IManageApiType {
return []
}
case 'upyun':
return [{
return [
{
Name: this.currentPicBedConfig.bucketName,
Location: 'upyun',
CreationDate: new Date().toISOString()
}]
}
]
case 'smms':
case 'webdavplist':
case 'local':
case 'sftp':
return [{
return [
{
Name: name,
Location: name,
CreationDate: new Date().toISOString()
}]
}
]
default:
console.log(param)
return []
}
}
async getBucketInfo (
param?: IStringKeyMap | undefined
): Promise<IStringKeyMap | IManageError> {
async getBucketInfo(param?: IStringKeyMap | undefined): Promise<IStringKeyMap | IManageError> {
console.log(param)
return {}
}
async getBucketDomain (
param: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketDomain(param: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -225,9 +262,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async createBucket (
param?: IStringKeyMap
): Promise<boolean> {
async createBucket(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -246,44 +281,32 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucket (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucket(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async getOperatorList (
param?: IStringKeyMap
): Promise<string[] | IManageError> {
async getOperatorList(param?: IStringKeyMap): Promise<string[] | IManageError> {
console.log(param)
return []
}
async addOperator (
param?: IStringKeyMap
): Promise<boolean> {
async addOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async deleteOperator (
param?: IStringKeyMap
): Promise<boolean> {
async deleteOperator(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async getBucketAclPolicy (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketAclPolicy(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
console.log(param)
return {}
}
async setBucketAclPolicy (
param?: IStringKeyMap
): Promise<boolean> {
async setBucketAclPolicy(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'qiniu':
@@ -299,9 +322,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async getBucketListRecursively (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketListRecursively(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
let window
const defaultResult = {
@@ -344,9 +365,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* @param param
* @returns
*/
async getBucketListBackstage (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketListBackstage(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
let client
let window
const defaultResult = {
@@ -393,9 +412,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
* isDir: 是否是文件夹
* fileSize: 文件大小
**/
async getBucketFileList (
param?: IStringKeyMap
): Promise<IStringKeyMap | IManageError> {
async getBucketFileList(param?: IStringKeyMap): Promise<IStringKeyMap | IManageError> {
const defaultResponse = {
fullList: <any>[],
isTruncated: false,
@@ -422,9 +439,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -451,9 +466,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async deleteBucketFolder (
param?: IStringKeyMap
): Promise<boolean> {
async deleteBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -477,9 +490,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async renameBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async renameBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -502,9 +513,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async downloadBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async downloadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -531,16 +540,12 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async copyMoveBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async copyMoveBucketFile(param?: IStringKeyMap): Promise<boolean> {
console.log(param)
return false
}
async createBucketFolder (
param?: IStringKeyMap
): Promise<boolean> {
async createBucketFolder(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -564,9 +569,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async uploadBucketFile (
param?: IStringKeyMap
): Promise<boolean> {
async uploadBucketFile(param?: IStringKeyMap): Promise<boolean> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
@@ -592,9 +595,7 @@ export class ManageApi extends EventEmitter implements IManageApiType {
}
}
async getPreSignedUrl (
param?: IStringKeyMap
): Promise<string> {
async getPreSignedUrl(param?: IStringKeyMap): Promise<string> {
let client
switch (this.currentPicBedConfig.picBedName) {
case 'tcyun':
+18 -19
View File
@@ -18,17 +18,12 @@ import { ManageLogger } from '~/manage/utils/logger'
import { commonTaskStatus, downloadTaskSpecialStatus, uploadTaskSpecialStatus } from '#/types/enum'
import { formatHttpProxy } from '#/utils/common'
export const getFSFile = async (
filePath: string,
stream: boolean = false
): Promise<IStringKeyMap> => {
export const getFSFile = async (filePath: string, stream: boolean = false): Promise<IStringKeyMap> => {
try {
return {
extension: path.extname(filePath),
fileName: path.basename(filePath),
buffer: stream
? fs.createReadStream(filePath)
: await fs.readFile(filePath),
buffer: stream ? fs.createReadStream(filePath) : await fs.readFile(filePath),
success: true
}
} catch (e) {
@@ -39,9 +34,7 @@ export const getFSFile = async (
}
export function isInputConfigValid(config: any): boolean {
return typeof config === 'object' &&
!Array.isArray(config) &&
Object.keys(config).length > 0
return typeof config === 'object' && !Array.isArray(config) && Object.keys(config).length > 0
}
export const getFileMimeType = (filePath: string): string => mime.lookup(filePath) || 'application/octet-stream'
@@ -83,7 +76,8 @@ export const clearTempFolder = () => fs.emptyDirSync(getTempDirPath())
export const md5 = (str: string, code: 'hex' | 'base64'): string => crypto.createHash('md5').update(str).digest(code)
export const hmacSha1Base64 = (secretKey: string, stringToSign: string) : string => crypto.createHmac('sha1', secretKey).update(Buffer.from(stringToSign, 'utf8')).digest('base64')
export const hmacSha1Base64 = (secretKey: string, stringToSign: string): string =>
crypto.createHmac('sha1', secretKey).update(Buffer.from(stringToSign, 'utf8')).digest('base64')
export const NewDownloader = async (
instance: UpDownTaskQueue,
@@ -150,9 +144,7 @@ export const gotUpload = async (
throwHttpErrors: boolean = false,
agent: any = {}
) => {
got(
url,
{
got(url, {
headers,
method,
body,
@@ -161,8 +153,7 @@ export const gotUpload = async (
},
throwHttpErrors,
agent
}
)
})
.on('uploadProgress', (progress: any) => {
instance.updateUploadTask({
id,
@@ -174,7 +165,10 @@ export const gotUpload = async (
instance.updateUploadTask({
id,
progress: res?.statusCode === 200 || res?.statusCode === 201 ? 100 : 0,
status: res?.statusCode === 200 || res?.statusCode === 201 ? uploadTaskSpecialStatus.uploaded : commonTaskStatus.failed,
status:
res?.statusCode === 200 || res?.statusCode === 201
? uploadTaskSpecialStatus.uploaded
: commonTaskStatus.failed,
finishTime: new Date().toLocaleString()
})
})
@@ -220,7 +214,10 @@ const commonOptions = {
scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
} as any
export const getAgent = (proxy:any, https: boolean = true): {
export const getAgent = (
proxy: any,
https: boolean = true
): {
https?: HttpsProxyAgent
http?: HttpProxyAgent
} => {
@@ -298,7 +295,9 @@ export function getOptions (
...(body && { body }),
...(responseType && { responseType }),
...(timeout !== undefined ? { timeout: { request: timeout } } : { timeout: { request: 30000 } }),
...(proxy && { agent: Object.fromEntries(Object.entries(getAgent(proxy)).filter(([, v]) => v !== undefined)) }),
...(proxy && {
agent: Object.fromEntries(Object.entries(getAgent(proxy)).filter(([, v]) => v !== undefined))
}),
throwHttpErrors: false
}
}
+14 -5
View File
@@ -18,7 +18,10 @@ export async function dogecloudApi (
secretKey: string
) {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
const sign = crypto.createHmac('sha1', secretKey).update(Buffer.from(apiPath + '\n' + body, 'utf8')).digest('hex')
const sign = crypto
.createHmac('sha1', secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex')
const authorization = `TOKEN ${accessKey}:${sign}`
try {
const res = await axios.request({
@@ -40,16 +43,22 @@ export async function dogecloudApi (
}
}
export async function getTempToken (accessKey: string, secretKey: string): Promise<{} | DogecloudToken> {
const dogeTempToken = await picgo.getConfig('Credentials.doge-token') || {} as any
export async function getTempToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudToken> {
const dogeTempToken = (await picgo.getConfig('Credentials.doge-token')) || ({} as any)
if (dogeTempToken.token && dogeTempToken.expires > Date.now() + 7200000) {
return dogeTempToken.token
}
try {
const data = await dogecloudApi('/auth/tmp_token.json', {
const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL',
scopes: ['*']
}, true, accessKey, secretKey)
},
true,
accessKey,
secretKey
)
const token = data.Credentials
picgo.saveConfig({
Credentials: {
+4 -17
View File
@@ -27,9 +27,7 @@ export class ManageLogger implements ILogger {
}
#handleLog(type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
const logHeader = chalk[this.#level[type] as ILogColor](
`[PicList ${type.toUpperCase()}]`
)
const logHeader = chalk[this.#level[type] as ILogColor](`[PicList ${type.toUpperCase()}]`)
console.log(logHeader, ...msg)
this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
this.#logPath =
@@ -61,11 +59,7 @@ export class ManageLogger implements ILogger {
if (fs.existsSync(logPath)) {
const logFileSize = fs.statSync(logPath).size
const logFileSizeLimit =
enforceNumber(
this.#ctx.getConfig<Undefinable<number>>(
configPaths.settings.logFileSizeLimit
) || 10
) *
enforceNumber(this.#ctx.getConfig<Undefinable<number>>(configPaths.settings.logFileSizeLimit) || 10) *
1024 *
1024
return {
@@ -87,11 +81,7 @@ export class ManageLogger implements ILogger {
}
}
#handleWriteLog (
logPath: string,
type: string,
...msg: ILogArgvTypeWithError[]
): void {
#handleWriteLog(logPath: string, type: string, ...msg: ILogArgvTypeWithError[]): void {
try {
if (this.#checkLogLevel(type, this.#logLevel)) {
let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
@@ -122,10 +112,7 @@ export class ManageLogger implements ILogger {
return result
}
#checkLogLevel (
type: string,
level: undefined | string | string[]
): boolean {
#checkLogLevel(type: string, level: undefined | string | string[]): boolean {
if (level === undefined || level === 'all') {
return true
}
+6 -11
View File
@@ -9,10 +9,7 @@ import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
import routers from '~/server/routerManager'
import {
handleResponse,
ensureHTTPLink
} from '~/server/utils'
import { handleResponse, ensureHTTPLink } from '~/server/utils'
import { configPaths } from '#/utils/configPaths'
@@ -31,9 +28,7 @@ const multerStorage = multer.diskStorage({
filename: function (_req: any, file: { originalname: any }, cb: (arg0: null, arg1: any) => void) {
// eslint-disable-next-line no-control-regex
if (!/[^\u0000-\u00ff]/.test(file.originalname)) {
file.originalname = Buffer.from(file.originalname, 'latin1').toString(
'utf8'
)
file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8')
}
cb(null, file.originalname)
}
@@ -63,7 +58,7 @@ class Server {
}
#isValidConfig(config: IObj | undefined) {
return config && config.port && config.host && (config.enable !== undefined)
return config && config.port && config.host && config.enable !== undefined
}
#handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
@@ -108,7 +103,7 @@ class Server {
}
}
if (request.headers['content-type'] && request.headers['content-type'].startsWith('multipart/form-data')) {
// @ts-ignore
// @ts-expect-error since the multer type is not correct
uploadMulter.any()(request, response, (err: any) => {
if (err) {
logger.info('[PicList Server]', err)
@@ -120,7 +115,7 @@ class Server {
}
})
}
// @ts-ignore
// @ts-expect-error since the multer type is not correct
const list = request.files.map(file => file.path)
logger.info('[PicList Server] get a formData request')
const handler = routers.getHandler(url!, 'POST')?.handler
@@ -140,7 +135,7 @@ class Server {
})
request.on('end', () => {
try {
postObj = (body === '') ? {} : JSON.parse(body)
postObj = body === '' ? {} : JSON.parse(body)
} catch (err: any) {
logger.error('[PicList Server]', err)
return handleResponse({
+1 -1
View File
@@ -1,7 +1,7 @@
type HttpMethod = 'GET' | 'POST'
class Router {
#router = new Map<string, Map<HttpMethod, {handler: routeHandler, urlparams?: URLSearchParams}>>()
#router = new Map<string, Map<HttpMethod, { handler: routeHandler; urlparams?: URLSearchParams }>>()
#addRoute(method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
if (!this.#router.has(url)) {
+17 -24
View File
@@ -16,10 +16,7 @@ import windowManager from 'apis/app/window/windowManager'
import { markdownContent } from '~/server/apiDoc'
import router from '~/server/router'
import {
deleteChoosedFiles,
handleResponse
} from '~/server/utils'
import { deleteChoosedFiles, handleResponse } from '~/server/utils'
import { configPaths } from '#/utils/configPaths'
@@ -42,13 +39,15 @@ async function responseForGet ({ response } : {response: http.ServerResponse}) {
router.get('/', responseForGet)
router.get('/upload', responseForGet)
router.post('/upload', async ({
router.post(
'/upload',
async ({
response,
list = [],
urlparams
}: {
response: IHttpResponse,
list?: string[],
response: IHttpResponse
list?: string[]
urlparams?: URLSearchParams
}): Promise<void> => {
try {
@@ -70,9 +69,9 @@ router.post('/upload', async ({
let currentPicBedConfigId = ''
let needRestore = false
if (picbed) {
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || {} as IStringKeyMap
currentPicBedType = currentPicBed.current || ''
currentPicBedConfig = currentPicBed[currentPicBedType] || {} as IStringKeyMap
const currentPicBed = picgo.getConfig<IStringKeyMap>('picBed') || ({} as IStringKeyMap)
currentPicBedType = currentPicBed.uploader || currentPicBed.current || 'smms'
currentPicBedConfig = currentPicBed[currentPicBedType] || ({} as IStringKeyMap)
currentPicBedConfigId = currentPicBedConfig._id
const configName = urlparams?.get('configName') || currentPicBed[picbed]?._configName
if (picbed === currentPicBedType && configName === currentPicBedConfig._configName) {
@@ -176,15 +175,12 @@ router.post('/upload', async ({
}
})
}
})
}
)
router.post('/delete', async ({
response,
list = []
} : {
response: IHttpResponse,
list?: IStringKeyMap[]
}): Promise<void> => {
router.post(
'/delete',
async ({ response, list = [] }: { response: IHttpResponse; list?: IStringKeyMap[] }): Promise<void> => {
if (list.length === 0) {
handleResponse({
response,
@@ -221,13 +217,10 @@ router.post('/delete', async ({
}
})
}
})
}
)
router.any('/heartbeat', async ({
response
} : {
response: IHttpResponse,
}) => {
router.any('/heartbeat', async ({ response }: { response: IHttpResponse }) => {
handleResponse({
response,
body: {
+6 -13
View File
@@ -1,6 +1,4 @@
import {
Notification
} from 'electron'
import { Notification } from 'electron'
import picgo from '@core/picgo'
import logger from '@core/picgo/logger'
@@ -29,9 +27,9 @@ export const handleResponse = ({
success: false
}
}: {
response: IHttpResponse,
statusCode?: number,
header?: IObj,
response: IHttpResponse
statusCode?: number
header?: IObj
body?: any
}) => {
if (body?.success === false) {
@@ -43,9 +41,7 @@ export const handleResponse = ({
}
export const ensureHTTPLink = (url: string): string => {
return url.startsWith('http')
? url
: `http://${url}`
return url.startsWith('http') ? url : `http://${url}`
}
export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> => {
@@ -61,10 +57,7 @@ export const deleteChoosedFiles = async (list: ImgInfo[]): Promise<boolean[]> =>
const noteFunc = (value: boolean) => {
const notification = new Notification({
title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'),
body: T(value
? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED'
: 'GALLERY_SYNC_DELETE_NOTICE_FAILED'
)
body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED')
})
notification.show()
}
+8 -4
View File
@@ -83,10 +83,14 @@ class WebServer {
this.#server
.listen(
this.#config.webServerPort === 36699 ? 37777 : this.#config.webServerPort,
this.#config.webServerHost, () => {
logger.info(`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`)
})
.on('error', (err) => {
this.#config.webServerHost,
() => {
logger.info(
`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`
)
}
)
.on('error', err => {
logger.error(err)
})
} else {
+2 -11
View File
@@ -18,10 +18,7 @@ function beforeOpen () {
resolveOtherI18nFiles()
}
function copyFileOutsideOfElectronAsar (
sourceInAsarArchive: string,
destOutsideAsarArchive: string
) {
function copyFileOutsideOfElectronAsar(sourceInAsarArchive: string, destOutsideAsarArchive: string) {
if (fs.existsSync(sourceInAsarArchive)) {
// file will be copied
if (fs.statSync(sourceInAsarArchive).isFile()) {
@@ -85,13 +82,7 @@ function resolveClipboardImageGenerator () {
}
function getClipboardFiles() {
const files = [
'/linux.sh',
'/mac.applescript',
'/windows.ps1',
'/windows10.ps1',
'/wsl.sh'
]
const files = ['/linux.sh', '/mac.applescript', '/windows.ps1', '/windows10.ps1', '/wsl.sh']
return files.map(item => {
return {
+21 -10
View File
@@ -12,7 +12,9 @@ import { configPaths } from '#/utils/configPaths'
export let tray: Tray
export const setTray = (t: Tray) => { tray = t }
export const setTray = (t: Tray) => {
tray = t
}
export const getTray = () => tray
@@ -32,13 +34,15 @@ export const handleCopyUrl = (str: string): void => {
* show notification
* @param options
*/
export const showNotification = (options: IPrivateShowNotificationOption = {
export const showNotification = (
options: IPrivateShowNotificationOption = {
title: '',
body: '',
clickToCopy: false,
copyContent: '',
clickFn: () => {}
}) => {
}
) => {
const notification = new Notification({
title: options.title,
body: options.body
@@ -60,10 +64,8 @@ export const showNotification = (options: IPrivateShowNotificationOption = {
}
export const showMessageBox = (options: any) => {
return new Promise<IShowMessageBoxResult>(async (resolve) => {
dialog.showMessageBox(
options
).then((res) => {
return new Promise<IShowMessageBoxResult>(async resolve => {
dialog.showMessageBox(options).then(res => {
resolve({
result: res.response,
checkboxChecked: res.checkboxChecked
@@ -118,14 +120,18 @@ export const getClipboardFilePath = (): string => {
}
if (img.isEmpty() && platform === 'win32') {
const imgPath = clipboard.readBuffer('FileNameW')?.toString('ucs2')?.replace(RegExp(String.fromCharCode(0), 'g'), '')
const imgPath = clipboard
.readBuffer('FileNameW')
?.toString('ucs2')
?.replace(RegExp(String.fromCharCode(0), 'g'), '')
return imgPath || ''
}
return ''
}
export const handleUrlEncodeWithSetting = (url: string) => db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
export const handleUrlEncodeWithSetting = (url: string) =>
db.get(configPaths.settings.encodeOutputURL) ? handleUrlEncode(url) : url
const c1nApi = 'https://c1n.cn/link/short'
@@ -163,7 +169,12 @@ const generateYOURLSShortUrl = async (url: string) => {
if (!/^https?:\/\//.test(domain)) {
domain = `http://${domain}`
}
const params = new URLSearchParams({ signature, action: 'shorturl', format: 'json', url })
const params = new URLSearchParams({
signature,
action: 'shorturl',
format: 'json',
url
})
try {
const res = await axios.get(`${domain}/yourls-api.php?${params.toString()}`)
if (res.data?.shorturl) {
+30 -9
View File
@@ -40,7 +40,10 @@ async function dogecloudApi (
secretKey: string
) {
const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
const sign = crypto.createHmac('sha1', secretKey).update(Buffer.from(apiPath + '\n' + body, 'utf8')).digest('hex')
const sign = crypto
.createHmac('sha1', secretKey)
.update(Buffer.from(apiPath + '\n' + body, 'utf8'))
.digest('hex')
const authorization = `TOKEN ${accessKey}:${sign}`
try {
const res = await axios.request({
@@ -62,12 +65,18 @@ async function dogecloudApi (
}
}
async function getDogeToken (accessKey: string, secretKey: string): Promise<{} | DogecloudTokenFull> {
async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
try {
const data = await dogecloudApi('/auth/tmp_token.json', {
const data = await dogecloudApi(
'/auth/tmp_token.json',
{
channel: 'OSS_FULL',
scopes: ['*']
}, true, accessKey, secretKey)
},
true,
accessKey,
secretKey
)
return data
} catch (err: any) {
console.log(err)
@@ -77,8 +86,15 @@ async function getDogeToken (accessKey: string, secretKey: string): Promise<{} |
export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
try {
const { url: rawUrl, type, config: { accessKeyID, secretAccessKey, bucketName, endpoint, pathStyleAccess, rejectUnauthorized, proxy } } = configMap
let { imgUrl, config: { region } } = configMap
const {
url: rawUrl,
type,
config: { accessKeyID, secretAccessKey, bucketName, endpoint, pathStyleAccess, rejectUnauthorized, proxy }
} = configMap
let {
imgUrl,
config: { region }
} = configMap
if (type === 'aws-s3' || type === 'aws-s3-plist') {
imgUrl = rawUrl || imgUrl || ''
}
@@ -139,6 +155,9 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
}
}
let result: any
try {
fileKey = decodeURIComponent(fileKey)
} catch (err: any) {}
try {
const client = new S3Client(s3Options)
const command = new DeleteObjectCommand({
@@ -164,8 +183,10 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
try {
const { config: { bucketName, AccessKey, SecretKey } } = configMap
const token = await getDogeToken(AccessKey, SecretKey) as DogecloudTokenFull
const {
config: { bucketName, AccessKey, SecretKey }
} = configMap
const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
const newConfigMap = Object.assign({}, configMap)
newConfigMap.config = {
@@ -227,7 +248,7 @@ export async function removeFileFromSFTPInMain (config: ISftpPlistConfig, fileNa
try {
const client = SSHClient.instance
await client.connect(config)
const uploadPath = `/${(config.uploadPath || '')}/`.replace(/\/+/g, '/')
const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
const remote = path.join(uploadPath, fileName)
const deleteResult = await client.deleteFileSFTP(config, remote)
client.close()
+2 -5
View File
@@ -9,11 +9,8 @@ export const isMacOS = process.platform === 'darwin'
let version: string | undefined
const clean = (version: string) => version.split('.').length === 1
? `${version}.0.0`
: version.split('.').length === 2
? `${version}.0`
: version
const clean = (version: string) =>
version.split('.').length === 1 ? `${version}.0.0` : version.split('.').length === 2 ? `${version}.0` : version
const parseVersion = (plist: string) => {
const matches = /<key>ProductVersion<\/key>\s*<string>([\d.]+)<\/string>/.exec(plist)
+4 -2
View File
@@ -4,14 +4,16 @@ import { configPaths } from '#/utils/configPaths'
const getPicBeds = () => {
const picBedTypes = picgo.helper.uploader.getIdList()
const picBedFromDB = picgo.getConfig<IPicBedType[]>(configPaths.picBed.list) || []
const picBeds = picBedTypes.map((item: string) => {
const picBeds = picBedTypes
.map((item: string) => {
const visible = picBedFromDB.find((i: IPicBedType) => i.type === item) // object or undefined
return {
type: item,
name: picgo.helper.uploader.get(item)!.name || item,
visible: visible ? visible.visible : true
}
}).sort((a) => {
})
.sort(a => {
if (a.type === 'tcyun') {
return -1
}
+5 -5
View File
@@ -36,7 +36,8 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
if (fileList?.length === 0) {
return null // for uploading images in clipboard
} else if ((fileList?.length || 0) > 0) {
const result = fileList!.map(item => {
const result = fileList!
.map(item => {
if (isUrl(item)) {
return {
path: item
@@ -57,13 +58,12 @@ const getUploadFiles = (argv = process.argv, cwd = process.cwd(), logger: Logger
return null
}
}
}).filter(item => item !== null) as Result
})
.filter(item => item !== null) as Result
return result
}
}
return []
}
export {
getUploadFiles
}
export { getUploadFiles }
+11 -4
View File
@@ -13,6 +13,7 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
config[i].default = config[i].default()
}
if (typeof config[i].choices === 'function') {
// eslint-disable-next-line @typescript-eslint/ban-types
config[i].choices = (config[i].choices as Function)()
}
}
@@ -20,13 +21,17 @@ export const handleConfigWithFunction = (config: IPicGoPluginOriginConfig[]): IP
}
export const completeUploaderMetaConfig = (originData: IStringKeyMap): IUploaderConfigListItem => {
return Object.assign({
return Object.assign(
{
_configName: 'Default'
}, trimValues(originData), {
},
trimValues(originData),
{
_id: uuid(),
_createdAt: Date.now(),
_updatedAt: Date.now()
})
}
)
}
/**
@@ -130,7 +135,9 @@ export const deleteUploaderConfig = (type: string, id: string): IUploaderConfigI
/**
* upgrade old uploader config to new format
*/
export const upgradeUploaderConfig = (type: string): {
export const upgradeUploaderConfig = (
type: string
): {
configList: IStringKeyMap[]
defaultId: string
} => {
+36 -14
View File
@@ -1,4 +1,3 @@
// @ts-nocheck
import fs from 'fs-extra'
import { NodeSSH, Config, SSHExecCommandResponse } from 'node-ssh-no-cpu-features'
import path from 'path'
@@ -26,7 +25,11 @@ class SSHClient {
async connect(config: ISftpPlistConfig): Promise<boolean> {
const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey
? { username, privateKeyPath: privateKey, passphrase: passphrase || undefined }
? {
username,
privateKeyPath: privateKey,
passphrase: passphrase || undefined
}
: { username, password }
try {
await SSHClient.client.connect({
@@ -46,30 +49,42 @@ class SSHClient {
const client = new Client()
const { username, password, privateKey, passphrase } = config
const loginInfo: Config = privateKey
? { username, privateKey: fs.readFileSync(privateKey), passphrase: passphrase || undefined }
? {
username,
privateKey: fs.readFileSync(privateKey),
passphrase: passphrase || undefined
}
: { username, password }
remote = this.changeWinStylePathToUnix(remote)
if (remote === '/' || remote.includes('*')) return false
const promise = new Promise((resolve, reject) => {
client.on('ready', () => {
client.sftp((err, sftp) => {
client
.on('ready', () => {
client.sftp(
(
err: any,
sftp: {
unlink: (arg0: string, arg1: (err: any) => void) => void
}
) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
sftp.unlink(remote, (err) => {
sftp.unlink(remote, (err: any) => {
// eslint-disable-next-line prefer-promise-reject-errors
if (err) reject(false)
client.end()
resolve(true)
})
}
)
})
}).connect({
.connect({
host: config.host,
port: Number(config.port) || 22,
...loginInfo
})
}
)
return await promise
})
return (await promise) as boolean
} catch (err: any) {
console.log(err)
return false
@@ -103,10 +118,14 @@ class SSHClient {
}
}
async putFile (local: string, remote: string, config: {
async putFile(
local: string,
remote: string,
config: {
fileMode?: string
dirMode?: string
} = {}): Promise<boolean> {
} = {}
): Promise<boolean> {
if (!this._isConnected) {
throw new Error('SSH 未连接')
}
@@ -126,9 +145,12 @@ class SSHClient {
}
}
async mkdir (dirPath: string, config: {
async mkdir(
dirPath: string,
config: {
dirMode?: string
} = {}): Promise<boolean> {
} = {}
): Promise<boolean> {
if (!this._isConnected) {
throw new Error('SSH 未连接')
}
+25 -19
View File
@@ -15,10 +15,12 @@ const STORE_PATH = app.getPath('userData')
const readFileAsBase64 = (filePath: string) => fs.readFileSync(filePath, { encoding: 'base64' })
const isHttpResSuccess = (res: any) => res.status >= 200 && res.status < 300
const uploadOrUpdateMsg = (fileName: string, isUpdate: boolean = true) => isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList`
const uploadOrUpdateMsg = (fileName: string, isUpdate: boolean = true) =>
isUpdate ? `update ${fileName} from PicList` : `upload ${fileName} from PicList`
const getSyncConfig = () => {
return db.get(configPaths.settings.sync) || {
return (
db.get(configPaths.settings.sync) || {
type: 'github',
username: '',
repo: '',
@@ -26,6 +28,7 @@ const getSyncConfig = () => {
token: '',
proxy: ''
}
)
}
const getProxyagent = (proxy: string | undefined) => {
@@ -50,13 +53,7 @@ function getOctokit (syncConfig: ISyncConfig) {
})
}
const isSyncConfigValidate = ({
type,
username,
repo,
branch,
token
}: ISyncConfig) => {
const isSyncConfigValidate = ({ type, username, repo, branch, token }: ISyncConfig) => {
return type && username && repo && branch && token
}
@@ -179,12 +176,16 @@ async function updateLocalToRemote (syncConfig: ISyncConfig, fileName: string) {
}
const data = shaRes.data as any
const sha = data.sha
const res = await axios.put(apiUrl, {
const res = await axios.put(
apiUrl,
{
...defaultConfig,
sha
}, {
},
{
headers
})
}
)
return isHttpResSuccess(res)
}
default:
@@ -220,7 +221,10 @@ async function uploadFile (fileName: string[]): Promise<number> {
async function downloadAndWriteFile(url: string, localFilePath: string, config: any, isWriteJson = false) {
const res = await axios.get(url, config)
if (isHttpResSuccess(res)) {
await fs.writeFile(localFilePath, isWriteJson ? JSON.stringify(res.data, null, 2) : Buffer.from(res.data.content, 'base64'))
await fs.writeFile(
localFilePath,
isWriteJson ? JSON.stringify(res.data, null, 2) : Buffer.from(res.data.content, 'base64')
)
return true
}
return false
@@ -251,9 +255,14 @@ async function downloadRemoteToLocal (syncConfig: ISyncConfig, fileName: string)
if (res.status === 200) {
const data = res.data as any
const downloadUrl = data.download_url
return downloadAndWriteFile(downloadUrl, localFilePath, {
return downloadAndWriteFile(
downloadUrl,
localFilePath,
{
httpsAgent: getProxyagent(proxy)
}, true)
},
true
)
}
return false
}
@@ -294,7 +303,4 @@ async function downloadFile (fileName: string[]): Promise<number> {
return (await Promise.all(fileName.map(downloadFunc))).reduce((a, b) => a + b, 0)
}
export {
uploadFile,
downloadFile
}
export { uploadFile, downloadFile }
+1 -5
View File
@@ -1,8 +1,5 @@
<template>
<div
id="app"
:key="pageReloadCount"
>
<div id="app" :key="pageReloadCount">
<router-view />
</div>
</template>
@@ -25,7 +22,6 @@ onBeforeMount(async () => {
store?.setDefaultPicBed(config?.picBed?.uploader || config?.picBed?.current || 'smms')
}
})
</script>
<script lang="ts">
+75
View File
@@ -0,0 +1,75 @@
import axios from 'axios'
import path from 'path'
import { getConfig, saveConfig } from '@/utils/dataSender'
import { deleteFailedLog, deleteLog } from '#/utils/deleteLog'
interface IConfigMap {
fileName: string
config: {
url: string
username: string
password: string
uploadPath: string
token: string
}
}
const getAListToken = async (url: string, username: string, password: string) => {
const tokenStore = await getConfig<IStringKeyMap>('picgo-plugin-buildin-alistplist')
if (tokenStore && tokenStore.refreshedAt && Date.now() - tokenStore.refreshedAt < 3600000 && tokenStore.token) {
return tokenStore.token
}
const res = await axios.post(`${url}/api/auth/login`, {
username,
password
})
if (res.data.code === 200 && res.data.message === 'success') {
const token = res.data.data.token
saveConfig({
'picgo-plugin-buildin-alistplist': {
token,
refreshedAt: Date.now()
}
})
return token
}
}
export default class AListplistApi {
static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, config } = configMap
try {
const { url, username, password, uploadPath } = config
let token = config.token
if (!token) {
token = await getAListToken(url, username, password)
}
if (!url || !(token || (username && password))) {
deleteFailedLog(fileName, 'Alist', 'No valid token or username/password provided')
return false
}
const result = await axios.request({
method: 'post',
url: `${url}/api/fs/remove`,
headers: {
'Content-Type': 'application/json',
Authorization: token
},
data: {
dir: path.join('/', uploadPath, path.dirname(fileName)),
names: [path.basename(fileName)]
}
})
if (result.data.code === 200) {
deleteLog(fileName, 'Alist')
return true
}
deleteLog(fileName, 'Alist', false)
return false
} catch (error: any) {
deleteFailedLog(fileName, 'Alist', error)
return false
}
}
}
+1 -3
View File
@@ -9,9 +9,7 @@ interface IConfigMap {
export default class AliyunApi {
static #getKey(fileName: string, path?: string): string {
return path && path !== '/'
? `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
: fileName
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}` : fileName
}
static async delete(configMap: IConfigMap): Promise<boolean> {
+2
View File
@@ -1,4 +1,5 @@
import AlistApi from '@/apis/alist'
import AlistplistApi from '@/apis/alistplist'
import AliyunApi from '@/apis/aliyun'
import AwsS3Api from '@/apis/awss3'
import DogeCloudApi from '@/apis/dogecloud'
@@ -17,6 +18,7 @@ import WebdavApi from '@/apis/webdav'
const apiMap: IStringKeyMap = {
alist: AlistApi,
alistplist: AlistplistApi,
aliyun: AliyunApi,
'aws-s3': AwsS3Api,
'aws-s3-plist': AwsS3Api,
+1 -1
View File
@@ -9,7 +9,7 @@ export default class AwsS3Api {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, getRawData(configMap)) || false
? (await triggerRPC(IRPCActionType.GALLERY_DELETE_AWS_S3_FILE, getRawData(configMap))) || false
: await removeFileFromS3InMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'AWS S3', error)
+1 -1
View File
@@ -10,7 +10,7 @@ export default class AwsS3Api {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_DOGE_FILE, getRawData(configMap)) || false
? (await triggerRPC(IRPCActionType.GALLERY_DELETE_DOGE_FILE, getRawData(configMap))) || false
: await removeFileFromDogeInMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'DogeCloud', error)
+6 -4
View File
@@ -17,13 +17,15 @@ export default class GithubApi {
static #createKey(path: string | undefined, fileName: string): string {
const formatedFileName = fileName.replace(/%2F/g, '/')
return path && path !== '/'
? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}`
: formatedFileName
return path && path !== '/' ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}` : formatedFileName
}
static async delete(configMap: IConfigMap): Promise<boolean> {
const { fileName, hash, config: { repo, token, branch, path } } = configMap
const {
fileName,
hash,
config: { repo, token, branch, path }
} = configMap
const [owner, repoName] = repo.split('/')
const octokit = GithubApi.#createOctokit(token)
const key = GithubApi.#createKey(path, fileName)
+1 -1
View File
@@ -10,7 +10,7 @@ export default class HuaweicloudApi {
static async delete(configMap: IStringKeyMap): Promise<boolean> {
try {
return ipcRenderer
? await triggerRPC(IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, getRawData(configMap)) || false
? (await triggerRPC(IRPCActionType.GALLERY_DELETE_HUAWEI_OSS_FILE, getRawData(configMap))) || false
: await removeFileFromHuaweiInMain(getRawData(configMap))
} catch (error: any) {
deleteFailedLog(configMap.fileName, 'HuaweiCloud', error)
+1 -4
View File
@@ -11,10 +11,7 @@ export default class ImgurApi {
static #baseUrl = 'https://api.imgur.com/3'
static async delete(configMap: IConfigMap): Promise<boolean> {
const {
config: { clientId = '', username = '', accessToken = '' } = {},
hash = ''
} = configMap
const { config: { clientId = '', username = '', accessToken = '' } = {}, hash = '' } = configMap
let Authorization: string, apiUrl: string
if (username && accessToken) {
+1 -2
View File
@@ -26,8 +26,7 @@ export default class LskyplistApi {
rejectUnauthorized: false
})
try {
const response: AxiosResponse = await axios.delete(
`${host}/api/v1/images/${hash}`, {
const response: AxiosResponse = await axios.delete(`${host}/api/v1/images/${hash}`, {
headers: v2Headers,
timeout: 30000,
httpsAgent: requestAgent

Some files were not shown because too many files have changed in this diff Show More