diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index e4ced6e..51ea155 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1 @@
-custom: ["https://paypal.me/Molunerfinn"]
\ No newline at end of file
+custom: ["https://paypal.me/Kuingsmile"]
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index c79d42e..61bc251 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -3,12 +3,12 @@ description: 提交一个问题 / Report a bug
title: "[Bug]: "
labels: ["bug"]
assignees:
- - molunerfinn
+ - Kuingsmile
body:
- type: markdown
attributes:
value: |+
- ## PicGo Issue 模板
+ ## PicList Issue 模板
请依照该模板来提交,否则将会被关闭。
**提问之前请注意你看过 FAQ、文档以及那些被关闭的 issues。否则同样的提问也会被关闭!**
@@ -24,15 +24,15 @@ body:
options:
- label: "[文档/Doc](https://picgo.github.io/PicGo-Doc/)"
required: true
- - label: "[Issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
+ - label: "[Issues](https://github.com/Kuingsmile/PicList/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
required: true
- - label: "[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)"
+ - label: "[FAQ](https://github.com/Kuingsmile/PicList/blob/dev/FAQ.md)"
required: true
- type: input
id: version
attributes:
- label: PicGo的版本 | PicGo Version
- placeholder: 例如 v2.3.0-beta.1
+ label: PicList的版本 | PicList Version
+ placeholder: 例如 v0.0.1
validations:
required: true
- type: dropdown
@@ -58,11 +58,11 @@ body:
id: log
attributes:
label: 相关日志 | Logs
- description: 请附上 PicGo 的相关报错日志(用文本的形式)。报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到 | Please attach PicGo's relevant error log (in text form). The error log can be found in PicGo Settings -> Set Log File -> Click to Open
+ description: 请附上 PicList 的相关报错日志(用文本的形式)。报错日志可以在 PicList 设置 -> 设置日志文件 -> 点击打开 后找到 | Please attach PicList's relevant error log (in text form). The error log can be found in PicList Settings -> Set Log File -> Click to Open
- type: markdown
attributes:
value: |
- 最后,喜欢 PicGo 的话不妨给它点个 star~
+ 最后,喜欢 PicList 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
- Finally, if you like PicGo, give it a star~
+ Finally, if you like PicList, give it a star~
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 4df34da..b91dc7a 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -3,12 +3,12 @@ description: 功能请求 / Feature request
title: "[Feature]: "
labels: ["feature request"]
assignees:
- - molunerfinn
+ - Kuingsmile
body:
- type: markdown
attributes:
value: |+
- ## PicGo Issue 模板
+ ## PicList Issue 模板
请依照该模板来提交,否则将会被关闭。
**提问之前请注意你看过 FAQ、文档以及那些被关闭的 issues。否则同样的提问也会被关闭!**
@@ -24,15 +24,15 @@ body:
options:
- label: "[文档/Doc](https://picgo.github.io/PicGo-Doc/)"
required: true
- - label: "[Issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
+ - label: "[Issues](https://github.com/Kuingsmile/PicList/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed)"
required: true
- - label: "[FAQ](https://github.com/Molunerfinn/PicGo/blob/dev/FAQ.md)"
+ - label: "[FAQ](https://github.com/Kuingsmile/PicList/blob/dev/FAQ.md)"
required: true
- type: input
id: version
attributes:
- label: PicGo的版本 | PicGo Version
- placeholder: 例如 v2.3.0-beta.1
+ label: PicList的版本 | PicList Version
+ placeholder: 例如 v0.0.1
validations:
required: true
- type: dropdown
@@ -57,7 +57,7 @@ body:
- type: markdown
attributes:
value: |
- 最后,喜欢 PicGo 的话不妨给它点个 star~
+ 最后,喜欢 PicList 的话不妨给它点个 star~
如果可以的话,请我喝杯咖啡?首页有赞助二维码,谢谢你的支持!
- Finally, if you like PicGo, give it a star~
+ Finally, if you like PicList, give it a star~
Buy me a cup of coffee if you can? There is a sponsorship QR code on the homepage, thank you for your support!
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 17ea7c7..a129b56 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,13 +1,13 @@
# main.yml
# Workflow's name
-name: Build
+name: Auto Build
# Workflow's trigger
on:
push:
branches:
- - master
+ - release
# Workflow's jobs
jobs:
@@ -54,5 +54,6 @@ jobs:
yarn upload-dist
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
- PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
+ R2_SECRET_ID: ${{ secrets.R2_SECRET_ID }}
+ R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
+ R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
diff --git a/.github/workflows/manually.yml b/.github/workflows/manually.yml
index b149b47..afb88b4 100644
--- a/.github/workflows/manually.yml
+++ b/.github/workflows/manually.yml
@@ -1,7 +1,7 @@
# main.yml
# Workflow's name
-name: Build
+name: Manually Build
# Workflow's trigger
on: workflow_dispatch
@@ -51,5 +51,6 @@ jobs:
yarn upload-dist
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- PICGO_ENV_COS_SECRET_ID: ${{ secrets.PICGO_ENV_COS_SECRET_ID }}
- PICGO_ENV_COS_SECRET_KEY: ${{ secrets.PICGO_ENV_COS_SECRET_KEY }}
+ R2_SECRET_ID: ${{ secrets.R2_SECRET_ID }}
+ R2_SECRET_KEY: ${{ secrets.R2_SECRET_KEY }}
+ R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
diff --git a/.gitignore b/.gitignore
index 94a9fc0..c899d13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,9 @@ dist_electron/
test.js
.env
scripts/*.yml
+scripts/generateYmlFile.js
#Electron-builder output
-/dist_electron
\ No newline at end of file
+/dist_electron
+/docs
+cloc.exe
\ No newline at end of file
diff --git a/356u2spwu37 b/356u2spwu37
new file mode 100644
index 0000000..bb448f6
Binary files /dev/null and b/356u2spwu37 differ
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a0499e8..c09ccfb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
-## 贡献指南
+# 贡献指南
-### 安装与启动
+## 安装与启动
1. 使用 [yarn](https://yarnpkg.com/) 安装依赖
@@ -22,16 +22,18 @@ yarn dev
4. 所有的全局类型定义请在 `src/universal/types/` 里添加,如果是 `enum`,请在 `src/universal/types/enum.ts` 里添加。
+5. 与图床管理功能相关的代码请在`src/main/manage`和`src/renderer/manage`目录下添加。
-### i18n
-1. 在 `public/i18n/` 下面创建一种语言的 `yml` 文件,例如 `zh-Hans.yml`。然后参考 `zh-CN.yml` 或者 `en.yml` 编写语言文件。并注意,PicGo 会通过语言文件中的 `LANG_DISPLAY_LABEL` 向用户展示该语言的名称。
+## 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`,确保能生成正确的语言定义文件。
-### 提交代码
+## 提交代码
1. 请检查代码没有多余的注释、`console.log` 等调试代码。
2. 提交代码前,请执行命令 `git add . && yarn cz`,唤起 PicGo 的[代码提交规范工具](https://github.com/PicGo/bump-version)。通过该工具提交代码。
diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md
index 1f962f6..df17415 100644
--- a/CONTRIBUTING_EN.md
+++ b/CONTRIBUTING_EN.md
@@ -1,6 +1,6 @@
-## Contribution Guidelines
+# Contribution Guidelines
-### Installation and startup
+## Installation and startup
1. Use [yarn](https://yarnpkg.com/) to install dependencies
@@ -22,16 +22,17 @@ Startup project.
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.
-### i18n
+## 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 PicGo will display the name of the language to the user via `LANG_DISPLAY_LABEL` in the language file.
+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.
-### Submit code
+## 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 PicGo's [Code Submission Specification Tool](https://github.com/PicGo/bump-version). Submit code through this tool.
\ No newline at end of file
+2. Before submitting the code, please execute the command `git add . && yarn cz` to invoke PicGo's [Code Submission Specification Tool](https://github.com/PicGo/bump-version). Submit code through this tool.
diff --git a/FAQ.md b/FAQ.md
index 9c3b1a8..603ad72 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -1,80 +1,85 @@
+# FAQ
+
+该FAQ修改自PicGo的FAQ,感谢PicGo的作者Molunerfinn。
+
## 常见问题
-> 在使用 PicGo 期间你会遇到很多问题,不过很多问题其实之前就有人提问过,也被解决,所以你可以先看看 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B),这份 FAQ,以及那些被关闭的 [issues](https://github.com/Molunerfinn/PicGo/issues?q=is%3Aissue+is%3Aclosed),应该能找到答案。
+> 本软件的上传工具部分来自PicGo,基本没有改动,请参考PicGo的 [使用文档](https://picgo.github.io/PicGo-Doc/zh/guide/getting-started.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)
-## 1. 七牛图床上传图片成功后,相册里无法显示或图片无`http://`前缀
+## 1. PicList和PicGo有什么关系?
-通常是你的七牛图床配置里的`设定访问网址`没有加上`http://`或者`https//`头。
+PicList项目fork自PicGo项目,基于PicGo进行了二次开发,添加了如下功能:
-参考:[issue#79](https://github.com/Molunerfinn/PicGo/issues/79)
+注意:以下功能已适配的图床包括:阿里云 OSS、腾讯云 COS、七牛云 Kodo、又拍云、SM.MS、Imgur、GitHub。
-## 2. 能否支持图床远端同步删除
+- 相册中可同步删除云端图片
+- 支持管理所有图床,可以在线进行云端目录查看、文件搜索、批量上传、批量下载、删除文件和图片预览等
+- 对于私有存储桶等支持复制预签名链接进行分享
+- 优化了PicGo的界面,解锁了窗口大小限制,同时美化了部分界面布局
-不能。有些图床(比如微博图床、SM.MS、Imgur 等)不支持后台管理,为了架构统一不支持远端删除。
+PicList所有新功能的添加没有影响到PicGo的原有功能,所以你可以在PicList中使用PicGo的所有插件。同时仍然可以配合typora、obsidian等软件进行使用。
-## 3. 能否支持上传视频文件
+## 2. 使用图床管理功能时,出现无法获取目录等错误
-目前不能。如果有人开发了相应的插件理论可以支持任意文件上传。
+请查看日志文件`manage.log`,此外,各平台的API调用基本都有每小时次数限制,如果出现错误,请稍后再试。
-## 4. 微博图床上传之后无法显示预览图
+## 3. 支持哪些图床远端同步删除
-通常是挂了全局代理导致的。
+可以,本软件基于PicGo进行了二次开发,添加了远端同步删除功能。但是需要你的图床支持,目前支持的图床有:
-参考:[issue36](https://github.com/Molunerfinn/PicGo/issues/36)
+- 阿里云 OSS
+- 腾讯云 COS
+- 七牛云 Kodo
+- 又拍云
+- SM.MS
+- Imgur
+- GitHub
+
+## 4. 能否支持上传视频文件
+
+可以,通过新添加的图床管理功能,你可以上传任意格式的文件,包括视频文件。同时,在管理界面内上传时,使用分片上传/流式上传等方式,相对于PicGo内置的转换为base64的方式,上传更快,更稳定。
## 5. 能否支持某某某图床
-截止 v1.6,PicGo 支持了如下图床:
+PicGo本体支持了如下图床:
-- `微博图床` v1.0
-- `七牛图床` v1.0
-- `腾讯云 COS v4\v5 版本` v1.1 & v1.5.0
-- `又拍云` v1.2.0
-- `GitHub` v1.5.0
-- `SM.MS` v1.5.1
-- `阿里云 OSS` v1.6.0
-- `Imgur` v1.6.0
+- `七牛图床`
+- `腾讯云 COS`
+- `又拍云`
+- `GitHub`
+- `SM.MS`
+- `阿里云 OSS`
+- `Imgur`
-所以本体内将不会再支持其他图床。需要其他图床支持可以参考目前已有的三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
+PicList在上述7个图床之外,计划整合和优化现有插件,内置更多的常用图床。
-## 6. 一个图床设置多个信息
+此外,PicList兼容PicGo的插件系统,需要其他图床支持可以参考目前已有的PicGo三方 [插件](https://github.com/PicGo/Awesome-PicGo),如果还是没有你所需要的图床欢迎开发一个插件供大家使用。
-不能。因为目前的架构只支持一个图床一份信息。
-
-## 7. GitHub 图床有时能上传,有时上传失败
+## 6. Github 图床有时能上传,有时上传失败
1. GitHub 图床不支持上传同名文件,如果有同名文件上传,会报错。建议开启 `时间戳重命名` 避免同名文件。
2. GitHub 服务器和国内 GFW 的问题会导致有时上传成功,有时上传失败,无解。想要稳定请使用付费云存储,如阿里云、腾讯云等,价格也不会贵。
-## 8. Mac 上无法打开 PicGo 的主窗口界面
+## 7. Mac 上无法打开 PicList 的主窗口界面
-PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。要打开主窗口,请右键或者双指点按顶部栏 PicGo 图标,选择「打开详细窗口」即可打开主窗口。
+PicList 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。要打开主窗口,请右键或者双指点按顶部栏 PicList 图标,选择「打开详细窗口」即可打开主窗口。
-## 9. 上传失败,或者是服务器出错
+## 8. 上传失败,或者是服务器出错
-1. PicGo 自带的图床都经过测试,上传出错一般都不是 PicGo 自身的原因。如果你用的是 GitHub 图床请参考上面的第 7 点。
-2. 检查 PicGo 的日志(报错日志可以在 PicGo 设置 -> 设置日志文件 -> 点击打开 后找到),看看 `[PicGo 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. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicGo 的代理设置中设置对应的HTTP代理。参考 [#912](https://github.com/Molunerfinn/PicGo/issues/912)
+3. 通常网络问题引起的上传失败都是因为代理设置不当导致的。如果开启了系统代理,建议同时也在 PicList 的代理设置中设置对应的HTTP代理。
## 10. macOS版本安装完之后没有主界面
-请找到PicGo在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
+请找到PicList在顶部栏的图标,然后右键(触摸板双指点按,或者鼠标右键),即可找到「打开详细窗口」的菜单。
-## 11. 相册突然无法显示图片 或者 上传后相册不更新 或者 使用Typora+PicGo上传图片成功但是没有写回Typora
+## 11. macOS系统安装完PicList显示「文件已损坏」或者安装完打开没有反应
-这个原因可能是相册存储文件损坏导致的。可以找到 PicGo 配置文件所在路径下的 `picgo.db` ,将其删掉(删掉前建议备份一遍),再重启 PicGo 试试。
-注意同时看看日志文件里有没有什么error,必要时可以提issue。2.3.0以上的版本已经解决因为 `picgo.db` 损坏导致的上述问题,建议更新版本。
-
-## 12. Gitee相关问题
-
-如果在使用 Gitee 图床的时候遇到上传的问题,由于 PicGo 并没有官方提供 Gitee 上传服务,无法帮你解决,请去你所使用的 Gitee 插件仓库发相关的issue。
-
-## 13. macOS系统安装完PicGo显示「文件已损坏」或者安装完打开没有反应
-
-因为 PicGo 没有签名,所以会被 macOS 的安全检查所拦下。
+因为 PicList 没有签名,所以会被 macOS 的安全检查所拦下。
1. 安装后打开遇到「文件已损坏」的情况,请按如下方式操作:
@@ -84,10 +89,10 @@ PicGo 在 Mac 上是一个顶部栏应用,在 dock 栏是不会有图标的。
sudo spctl --master-disable
```
-然后放行 PicGo :
+然后放行 PicList :
```
-xattr -cr /Applications/PicGo.app
+xattr -cr /Applications/PicList.app
```
然后就能正常打开。
@@ -118,9 +123,5 @@ options:
执行命令
```
-xattr -c /Applications/PicGo.app/*
+xattr -c /Applications/PicList.app/*
```
-
-2. 如果安装打开后没有反应,请按下方顺序排查:
- 1. macOS安装好之后,PicGo 是不会弹出主窗口的,因为 PicGo 在 macOS 系统里设计是个顶部栏应用。注意看你顶部栏的图标,如果有 PicGo 的图标,说明安装成功了,点击图标即可打开顶部栏窗口。参考上述[第八点](#8-mac-上无法打开-picgo-的主窗口界面)。
- 2. 如果你是 M1 的系统,此前装过 PicGo 的 x64 版本,但是后来更新了 arm64 的版本发现打开后没反应,请重启电脑即可。
diff --git a/LICENSE b/LICENSE
index d33149c..1cee4bc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,7 @@
The MIT License (MIT)
+Copyright (c) 2017-present, Molunerfinn
+Copyright (c) 2019 诗人的咸鱼
Copyright (c) 2023-present, KuingSmile
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/README.md b/README.md
index 0871354..3f20fd9 100644
--- a/README.md
+++ b/README.md
@@ -2,43 +2,61 @@
- 一款综合了PicGo和AList的图片上传和图床管理桌面工具,基于PicGo,处于早期开发中
+ 一款fork自PicGo的二次开发项目,保留了PicGo的所有功能的同时,为相册添加了同步云端删除功能,同时增加了完整的云存储管理功能,包括云端目录查看、文件搜索、批量上传下载和删除文件,复制多种格式文件和图片预览等。
-## 开发计划
+## 特色功能
-本项目的开发初衷是为了解决在个人在使用PicGo桌面版时候的几个痛点:
+- 保留了PicGo的所有功能,兼容已有的PicGo插件系统,包括和typora、obsidian等的搭配
+- 相册中可同步删除云端图片
+- 支持管理所有图床,可以在线进行云端目录查看、文件搜索、批量上传、批量下载、删除文件和图片预览等
+- 管理界面使用内置数据库缓存目录,加速目录加载速度
+- 对于私有存储桶等支持复制预签名链接进行分享
+- 优化了PicGo的界面,解锁了窗口大小限制,同时美化了部分界面布局
-1. 相册中无法同步删除云端图片,不小心上传错或者想更换图片时不方便;
-2. 只能上传图片,无法上传视频或其它格式文件,在需要向文章中插入其它资源的时候需要自己去上传;
-3. 不能查看和复制使用PicGo软件之前上传的图片的链接;
-4. 不能从云端取回文件。
+## 下载安装
-为了优化以上问题,基于PicHoro的开发经验,以及使用AList软件时的一些体验,决定基于PicGo开发一款增强版的软件PicList,期望除了PicGo的核心功能外,增加如下功能:
+### Github release
-1. 相册可同步删除云端图片,支持加强版的图片预览和元信息查看;
-2. 支持所有格式和不大于2G的文件的上传;
-3. 支持管理所有图床,可以在线进行云端目录查看、文件搜索、上传、下载、删除和文件预览等;
-4. 支持不同图床之间的文件复制和移动等;
-5. 兼容已有的PicGo插件系统。
+https://github.com/Kuingsmile/PicList/releases
-## 开发进度
+### CloudFlare R2
-开发中,预计在2023年2月底之前发布第一个发行版。
+请参考release页面的说明
+
+
+## 应用截图
+
+
+
+
+## 开发说明
+
+1. 你需要有 Node、Git 环境,了解 npm 的相关知识。
+2. git clone https://github.com/Kuingsmile/PicList.git 并进入项目。
+yarn 下载依赖。注意如果你没有 yarn,请去 官网 下载安装后再使用。 用 npm install 将导致未知错误!
+3. Mac 需要有 Xcode 环境,Windows 需要有 VS 环境。
+4. 如果需要贡献代码,可以参考[贡献指南](https://github.com/Kuingsmile/PicList/blob/dev/CONTRIBUTING.md)。
+
+## 其它相关
+
+- [PicGo](https://github.com/Molunerfinn/PicGo) : 原版PicGo项目
## License
+本项目基于MIT协议开源,欢迎大家使用和贡献代码,感谢原作者Molunerfinn的开源精神。
+
[MIT](https://opensource.org/licenses/MIT)
-Copyright (c) 2023 Kuingsmile
-
+Copyright (c) 2017-present, Molunerfinn
+Copyright (c) 2023-present Kuingsmile
diff --git a/build/icons/256x256.png b/build/icons/256x256.png
index 2951eed..a2ea42e 100644
Binary files a/build/icons/256x256.png and b/build/icons/256x256.png differ
diff --git a/build/icons/icon.icns b/build/icons/icon.icns
index 113d932..e2e540a 100644
Binary files a/build/icons/icon.icns and b/build/icons/icon.icns differ
diff --git a/build/icons/icon.ico b/build/icons/icon.ico
index 3f967e1..9028a35 100644
Binary files a/build/icons/icon.ico and b/build/icons/icon.ico differ
diff --git a/build/icons/icon2.icns b/build/icons/icon2.icns
new file mode 100644
index 0000000..113d932
Binary files /dev/null and b/build/icons/icon2.icns differ
diff --git a/build/installer.nsh b/build/installer.nsh
index 1b86bf8..a214e7a 100644
--- a/build/installer.nsh
+++ b/build/installer.nsh
@@ -1,13 +1,13 @@
!macro customInstall
SetRegView 64
- WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
- WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
- WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
+ WriteRegStr HKCR "*\shell\PicList" "" "Upload pictures w&ith PicList"
+ WriteRegStr HKCR "*\shell\PicList" "Icon" "$INSTDIR\PicList.exe"
+ WriteRegStr HKCR "*\shell\PicList\command" "" '"$INSTDIR\PicList.exe" "upload" "%1"'
SetRegView 32
- WriteRegStr HKCR "*\shell\PicGo" "" "Upload pictures w&ith PicGo"
- WriteRegStr HKCR "*\shell\PicGo" "Icon" "$INSTDIR\PicGo.exe"
- WriteRegStr HKCR "*\shell\PicGo\command" "" '"$INSTDIR\PicGo.exe" "upload" "%1"'
+ WriteRegStr HKCR "*\shell\PicList" "" "Upload pictures w&ith PicList"
+ WriteRegStr HKCR "*\shell\PicList" "Icon" "$INSTDIR\PicList.exe"
+ WriteRegStr HKCR "*\shell\PicList\command" "" '"$INSTDIR\PicList.exe" "upload" "%1"'
!macroend
!macro customUninstall
- DeleteRegKey HKCR "*\shell\PicGo"
+ DeleteRegKey HKCR "*\shell\PicList"
!macroend
diff --git a/docs/APP.vue b/docs/APP.vue
deleted file mode 100644
index a447d9a..0000000
--- a/docs/APP.vue
+++ /dev/null
@@ -1,216 +0,0 @@
-
- #app(v-cloak)
- #header
- .mask
- img.logo(src="~icons/256x256.png", alt="PicGo")
- h1.title PicGo
- small(v-if="version") {{ version }}
- h2.desc 图片上传+管理新体验
- button.download(@click="goLink('https://github.com/Molunerfinn/picgo/releases')") 免费下载
- button.download(@click="goLink('https://picgo.github.io/PicGo-Doc/zh/guide/')") 查看文档
- h3.desc
- | 基于#[a(href="https://github.com/SimulatedGREG/electron-vue" target="_blank") electron-vue]开发
- h3.desc
- | 支持macOS,Windows,Linux
- h3.desc
- | 支持#[a(href="https://picgo.github.io/PicGo-Doc/zh/guide/config.html#%E6%8F%92%E4%BB%B6%E8%AE%BE%E7%BD%AE%EF%BC%88v2-0%EF%BC%89" target="_blank") 插件系统],让PicGo更强大
- #container.container-fluid
- .row.ex-width
- img.gallery.col-xs-10.col-xs-offset-1.col-md-offset-2.col-md-8(src="https://cdn.jsdelivr.net/gh/Molunerfinn/test/picgo-site/first.png")
- .row.ex-width.display-list
- .display-list__item(v-for="(item, index) in itemList" :key="index" :class="{ 'o-item': index % 2 !== 0 }")
- .col-xs-10.col-xs-offset-1.col-md-7.col-md-offset-0
- img(:src="item.url")
- .col-xs-10.col-xs-offset-1.col-md-5.col-md-offset-0.display-list__content
- .display-list__title {{ item.title }}
- .display-list__desc {{ item.desc }}
- .row.ex-width.info
- .col-xs-10.col-xs-offset-1
- | ©2017 - {{ year }} #[a(href="https://github.com/Molunerfinn" target="_blank") Molunerfinn]
-
-
-
diff --git a/docs/main.js b/docs/main.js
deleted file mode 100644
index 903e5e1..0000000
--- a/docs/main.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Vue from 'vue'
-import App from './APP.vue'
-import 'melody.css'
-import axios from 'axios'
-
-Vue.prototype.$http = axios
-
-new Vue({
- render: h => h(App)
-}).$mount('#app')
diff --git a/docs/template.html b/docs/template.html
deleted file mode 100644
index 88af599..0000000
--- a/docs/template.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
- PicGo
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
index c9f0256..d3f4603 100644
--- a/package.json
+++ b/package.json
@@ -15,28 +15,43 @@
"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-cos.js"
+ "upload-dist": "node ./scripts/upload-dist-to-r2.js"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
+ "@imengyu/vue3-context-menu": "^1.2.2",
+ "@octokit/rest": "^19.0.7",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.0.4",
- "axios": "^0.19.0",
+ "@types/mime-types": "^2.1.1",
+ "ali-oss": "^6.17.1",
+ "aws-sdk": "^2.1304.0",
+ "axios": "^1.3.2",
"compare-versions": "^4.1.3",
"core-js": "^3.27.1",
+ "cos-nodejs-sdk-v5": "^2.11.19",
"custom-electron-titlebar": "^4.1.5",
- "element-plus": "^2.2.28",
- "fs-extra": "^10.0.0",
- "js-yaml": "^4.1.0",
+ "dexie": "^3.2.3",
+ "element-plus": "^2.2.30",
+ "fast-xml-parser": "^4.1.1",
+ "form-data": "^4.0.0",
+ "fs-extra": "^11.1.0",
+ "got": "^12.5.3",
+ "hpagent": "^1.2.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
+ "mime-types": "^2.1.35",
"mitt": "^3.0.0",
- "picgo": "^1.5.0",
+ "piclist": "^0.0.8",
+ "pinia": "^2.0.29",
+ "pinia-plugin-persistedstate": "^3.0.2",
+ "qiniu": "^7.8.0",
"qrcode.vue": "^3.3.3",
- "shell-path": "2.1.0",
+ "shell-path": "3.0.0",
+ "upyun": "^3.4.6",
"uuid": "^9.0.0",
- "vue": "^3.2.45",
+ "vue": "^3.2.47",
"vue-router": "^4.1.6",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.2.9",
@@ -45,8 +60,9 @@
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.16.7",
"@picgo/bump-version": "^1.1.2",
+ "@types/ali-oss": "^6.16.7",
"@types/electron-devtools-installer": "^2.2.0",
- "@types/fs-extra": "^9.0.13",
+ "@types/fs-extra": "^11.0.1",
"@types/inquirer": "^6.5.0",
"@types/js-yaml": "^4.0.5",
"@types/lowdb": "^1.0.9",
@@ -70,16 +86,16 @@
"dotenv": "^16.0.1",
"electron": "^22.0.2",
"electron-devtools-installer": "^3.2.0",
- "eslint": "^8.31.0",
+ "eslint": "^8.34.0",
"eslint-config-standard": ">=16.0.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
- "eslint-plugin-vue": "^9.8.0",
+ "eslint-plugin-vue": "^9.9.0",
"husky": "^3.1.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
- "typescript": "^4.4.3",
+ "typescript": "^4.9.5",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"commitlint": {
diff --git a/public/Upload pictures with PicGo.workflow/Contents/Info.plist b/public/Upload pictures with PicList.workflow/Contents/Info.plist
similarity index 94%
rename from public/Upload pictures with PicGo.workflow/Contents/Info.plist
rename to public/Upload pictures with PicList.workflow/Contents/Info.plist
index 6fbb08c..b3fcbe0 100644
--- a/public/Upload pictures with PicGo.workflow/Contents/Info.plist
+++ b/public/Upload pictures with PicList.workflow/Contents/Info.plist
@@ -14,7 +14,7 @@
NSMenuItem
default
- Upload pictures with PicGo
+ Upload pictures with PicList
NSMessage
runWorkflowAsService
diff --git a/public/Upload pictures with PicGo.workflow/Contents/QuickLook/Thumbnail.png b/public/Upload pictures with PicList.workflow/Contents/QuickLook/Thumbnail.png
similarity index 100%
rename from public/Upload pictures with PicGo.workflow/Contents/QuickLook/Thumbnail.png
rename to public/Upload pictures with PicList.workflow/Contents/QuickLook/Thumbnail.png
diff --git a/public/Upload pictures with PicGo.workflow/Contents/Resources/background.color b/public/Upload pictures with PicList.workflow/Contents/Resources/background.color
similarity index 100%
rename from public/Upload pictures with PicGo.workflow/Contents/Resources/background.color
rename to public/Upload pictures with PicList.workflow/Contents/Resources/background.color
diff --git a/public/Upload pictures with PicGo.workflow/Contents/document.wflow b/public/Upload pictures with PicList.workflow/Contents/document.wflow
similarity index 98%
rename from public/Upload pictures with PicGo.workflow/Contents/document.wflow
rename to public/Upload pictures with PicList.workflow/Contents/document.wflow
index e83bdc6..f2eb263 100644
--- a/public/Upload pictures with PicGo.workflow/Contents/document.wflow
+++ b/public/Upload pictures with PicList.workflow/Contents/document.wflow
@@ -59,7 +59,7 @@
ActionParameters
COMMAND_STRING
- /Applications/PicGo.app/Contents/MacOS/PicGo upload "$@" > /dev/null 2>&1 &
+ /Applications/PicList.app/Contents/MacOS/PicList upload "$@" > /dev/null 2>&1 &
CheckedForUserDefaultShell
inputMethod
diff --git a/public/i18n/en.yml b/public/i18n/en.yml
index 874f6a1..e659e16 100644
--- a/public/i18n/en.yml
+++ b/public/i18n/en.yml
@@ -34,7 +34,7 @@ PICBEDS_SETTINGS: Picbeds Settings
PICBEDS_MANAGE: Picbeds Manage
PICLIST_SETTINGS: PicList Settings
PLUGIN_SETTINGS: Plugins Settings
-PICGO_SPONSOR_TEXT: PicList is a free software, if you like it, please don't forget to buy me a cup of coffee.
+PICLIST_SPONSOR_TEXT: PicList is a free software, if you like it, please don't forget to buy me a cup of coffee.
ALIPAY: Alipay
WECHATPAY: Wechat Pay
CHOOSE_PICBED: Choose Picbed
@@ -88,7 +88,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: PicGo 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 open download page
SETTINGS_LOG_FILE: Log File
SETTINGS_LOG_LEVEL: Log Level
SETTINGS_LOG_FILE_SIZE: Log File Size
@@ -191,12 +191,12 @@ UPDATE_PLUGIN: Update Plugin
TIPS_NOTICE: Tips
TIPS_WARNING: Warning
TIPS_ERROR: Error
-TIPS_INSTALL_NODE_AND_RELOAD_PICGO: Please install Node.js and restart PicGo to continue
+TIPS_INSTALL_NODE_AND_RELOAD_PICGO: Please install Node.js and restart PicList to continue
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: Plugin is trying to remove some images from the album gallery, continue?
TIPS_PLUGIN_OVERWRITE_GALLERY: Plugin is trying to overwrite the album gallery, continue?
TIPS_UPLOAD_NOT_PICTURES: The latest clipboard item is not a picture
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo config file broken, has been restored to default
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo config file broken, has been restored to backup
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicList config file broken, has been restored to default
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicList config file broken, has been restored to backup
TIPS_PICGO_BACKUP_FILE_VERSION: 'Backup file version: ${v}'
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: Custom config file parse error, please check the path content
TIPS_SHORTCUT_MODIFIED_SUCCEED: Shortcut modified successfully
diff --git a/public/i18n/zh-CN.yml b/public/i18n/zh-CN.yml
index 798f154..437ffcb 100644
--- a/public/i18n/zh-CN.yml
+++ b/public/i18n/zh-CN.yml
@@ -34,7 +34,7 @@ PICBEDS_SETTINGS: 图床设置
PICBEDS_MANAGE: 图床管理
PICLIST_SETTINGS: PicList设置
PLUGIN_SETTINGS: 插件设置
-PICGO_SPONSOR_TEXT: PicList是免费开源的软件,如果你喜欢它,对你有帮助,可以请我喝杯蜜雪冰城~
+PICLIST_SPONSOR_TEXT: PicList是免费开源的软件,如果你喜欢它,对你有帮助,可以请我喝杯蜜雪冰城~
ALIPAY: 支付宝
WECHATPAY: 微信支付
CHOOSE_PICBED: 选择图床
@@ -88,7 +88,7 @@ SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安装镜像
SETTINGS_CURRENT_VERSION: 当前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在获取中
-SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,请点击确定打开下载页面
+SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,请点击确定打开下载页面
SETTINGS_LOG_FILE: 日志文件
SETTINGS_LOG_LEVEL: 日志记录等级
SETTINGS_LOG_FILE_SIZE: 日志文件大小
@@ -191,12 +191,12 @@ UPDATE_PLUGIN: 更新插件
TIPS_NOTICE: 注意
TIPS_WARNING: 警告
TIPS_ERROR: 发生错误
-TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 请安装Node.js并重启PicGo再继续操作
+TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 请安装Node.js并重启PicList再继续操作
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在试图删除一些相册图片,是否继续
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在试图覆盖相册列表,是否继续
TIPS_UPLOAD_NOT_PICTURES: 剪贴板最新的一条记录不是图片
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 配置文件损坏,已经恢复为默认配置
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 配置文件损坏,已经恢复为备份配置
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicList 配置文件损坏,已经恢复为默认配置
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicList 配置文件损坏,已经恢复为备份配置
TIPS_PICGO_BACKUP_FILE_VERSION: '备份文件版本: ${v}'
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自定义文件解析出错,请检查路径内容是否正确
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷键已经修改成功
diff --git a/public/i18n/zh-TW.yml b/public/i18n/zh-TW.yml
index c8787a5..0b24301 100644
--- a/public/i18n/zh-TW.yml
+++ b/public/i18n/zh-TW.yml
@@ -34,7 +34,7 @@ PICBEDS_SETTINGS: 圖床設定
PICBEDS_MANAGE: 圖床管理
PICLIST_SETTINGS: PicList設定
PLUGIN_SETTINGS: 插件設定
-PICGO_SPONSOR_TEXT: PicList是開放原始碼的軟體,如果你喜歡它,對你有幫助,不妨請我喝杯咖啡~
+PICLIST_SPONSOR_TEXT: PicList是開放原始碼的軟體,如果你喜歡它,對你有幫助,不妨請我喝杯咖啡~
ALIPAY: 支付寶
WECHATPAY: 微信支付
CHOOSE_PICBED: 選擇圖床
@@ -88,7 +88,7 @@ SETTINGS_PLUGIN_INSTALL_MIRROR: 插件安裝鏡像
SETTINGS_CURRENT_VERSION: 當前版本
SETTINGS_NEWEST_VERSION: 最新版本
SETTINGS_GETING: 正在取得中
-SETTINGS_TIPS_HAS_NEW_VERSION: PicGo更新啦,請點擊確定開啟下載頁面
+SETTINGS_TIPS_HAS_NEW_VERSION: PicList更新啦,請點擊確定開啟下載頁面
SETTINGS_LOG_FILE: 記錄檔案
SETTINGS_LOG_LEVEL: 記錄等级
SETTINGS_LOG_FILE_SIZE: 記錄檔案大小
@@ -191,12 +191,12 @@ UPDATE_PLUGIN: 更新插件
TIPS_NOTICE: 注意
TIPS_WARNING: 警告
TIPS_ERROR: 發生錯誤
-TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 請安裝Node.js並重新啟動PicGo再繼續操作
+TIPS_INSTALL_NODE_AND_RELOAD_PICGO: 請安裝Node.js並重新啟動PicList再繼續操作
TIPS_PLUGIN_REMOVE_GALLERY_ITEM: 有插件正在試圖刪除一些相簿圖片,是否繼續?
TIPS_PLUGIN_OVERWRITE_GALLERY: 有插件正在試圖覆蓋相簿列表,是否繼續?
TIPS_UPLOAD_NOT_PICTURES: 剪貼簿最新的一條記錄不是圖片
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicGo 設定檔案已損壞,已經恢復為預設設定
-TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicGo 設定檔案已損壞,已經恢復為備份設定
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT: PicList設定檔案已損壞,已經恢復為預設設定
+TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP: PicList 設定檔案已損壞,已經恢復為備份設定
TIPS_PICGO_BACKUP_FILE_VERSION: '備份檔案版本: ${v}'
TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR: 自訂設定檔案解析出錯,請檢查路徑內容是否正確
TIPS_SHORTCUT_MODIFIED_SUCCEED: 快捷鍵已經修改成功
diff --git a/public/index.html b/public/index.html
index 79a75a2..f2d56b5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -6,11 +6,11 @@
- PicGo
+ PicList
diff --git a/public/menubar-nodarwin.png b/public/menubar-nodarwin.png
index af5eaff..d6ff740 100644
Binary files a/public/menubar-nodarwin.png and b/public/menubar-nodarwin.png differ
diff --git a/public/menubar-nodarwin@2x.png b/public/menubar-nodarwin@2x.png
index d5c6200..d6ff740 100644
Binary files a/public/menubar-nodarwin@2x.png and b/public/menubar-nodarwin@2x.png differ
diff --git a/public/menubar-nodarwin@3x.png b/public/menubar-nodarwin@3x.png
index b9c4956..c0e2e97 100644
Binary files a/public/menubar-nodarwin@3x.png and b/public/menubar-nodarwin@3x.png differ
diff --git a/public/picbed/aliyun.png b/public/picbed/aliyun.png
new file mode 100644
index 0000000..44b9f47
Binary files /dev/null and b/public/picbed/aliyun.png differ
diff --git a/public/picbed/github.png b/public/picbed/github.png
new file mode 100644
index 0000000..10737d4
Binary files /dev/null and b/public/picbed/github.png differ
diff --git a/public/picbed/imgur.png b/public/picbed/imgur.png
new file mode 100644
index 0000000..4631b7e
Binary files /dev/null and b/public/picbed/imgur.png differ
diff --git a/public/picbed/qiniu.png b/public/picbed/qiniu.png
new file mode 100644
index 0000000..6c86264
Binary files /dev/null and b/public/picbed/qiniu.png differ
diff --git a/public/picbed/smms.png b/public/picbed/smms.png
new file mode 100644
index 0000000..97bb61c
Binary files /dev/null and b/public/picbed/smms.png differ
diff --git a/public/picbed/tcyun.png b/public/picbed/tcyun.png
new file mode 100644
index 0000000..382ad53
Binary files /dev/null and b/public/picbed/tcyun.png differ
diff --git a/public/picbed/upyun.png b/public/picbed/upyun.png
new file mode 100644
index 0000000..14b53da
Binary files /dev/null and b/public/picbed/upyun.png differ
diff --git a/scripts/config.js b/scripts/config.js
index 43e6a46..f1b6d6c 100644
--- a/scripts/config.js
+++ b/scripts/config.js
@@ -2,24 +2,24 @@
// macos
const darwin = [{
- appNameWithPrefix: 'PicGo-',
+ appNameWithPrefix: 'PicList-',
ext: '.dmg',
arch: '-arm64',
'version-file': 'latest-mac.yml'
}, {
- appNameWithPrefix: 'PicGo-',
+ appNameWithPrefix: 'PicList-',
ext: '.dmg',
arch: '-x64',
'version-file': 'latest-mac.yml'
}]
const linux = [{
- appNameWithPrefix: 'PicGo-',
+ appNameWithPrefix: 'PicList-',
ext: '.AppImage',
arch: '',
'version-file': 'latest-linux.yml'
}, {
- appNameWithPrefix: 'picgo_',
+ appNameWithPrefix: 'piclist_',
ext: '.snap',
arch: '_amd64',
'version-file': 'latest-linux.yml'
@@ -27,17 +27,17 @@ const linux = [{
// windows
const win32 = [{
- appNameWithPrefix: 'PicGo-Setup-',
+ appNameWithPrefix: 'PicList-Setup-',
ext: '.exe',
arch: '-ia32',
'version-file': 'latest.yml'
}, {
- appNameWithPrefix: 'PicGo-Setup-',
+ appNameWithPrefix: 'PicList-Setup-',
ext: '.exe',
arch: '-x64',
'version-file': 'latest.yml'
}, {
- appNameWithPrefix: 'PicGo-Setup-',
+ appNameWithPrefix: 'PicList-Setup-',
ext: '.exe',
arch: '', // 32 & 64
'version-file': 'latest.yml'
diff --git a/scripts/cos-link.js b/scripts/cos-link.js
index a2c0afc..667c254 100644
--- a/scripts/cos-link.js
+++ b/scripts/cos-link.js
@@ -1,18 +1,18 @@
const pkg = require('../package.json')
const version = pkg.version
// TODO: use the same name format
-const generateURL = (platform, ext, prefix = 'PicGo-') => {
- return `https://picgo-1251750343.cos.ap-chengdu.myqcloud.com/${version}/${prefix}${version}${platform}${ext}`
+const generateURL = (platform, ext, prefix = 'PicList-') => {
+ return `https://release.piclist.cn/${version}/${prefix}${version}${platform}${ext}`
}
const platformExtList = [
- ['-arm64', '.dmg', 'PicGo-'],
- ['-x64', '.dmg', 'PicGo-'],
- ['', '.AppImage', 'PicGo-'],
- ['-ia32', '.exe', 'PicGo-Setup-'],
- ['-x64', '.exe', 'PicGo-Setup-'],
- ['', '.exe', 'PicGo-Setup-'],
- ['_amd64', '.snap', 'picgo_']
+ ['-arm64', '.dmg', 'PicList-'],
+ ['-x64', '.dmg', 'PicList-'],
+ ['', '.AppImage', 'PicList-'],
+ ['-ia32', '.exe', 'PicList-Setup-'],
+ ['-x64', '.exe', 'PicList-Setup-'],
+ ['', '.exe', 'PicList-Setup-'],
+ ['_amd64', '.snap', 'piclist_']
]
const links = platformExtList.map(([arch, ext, prefix]) => {
diff --git a/scripts/upload-dist-to-cos.js b/scripts/upload-dist-to-cos.js
deleted file mode 100644
index d55fe93..0000000
--- a/scripts/upload-dist-to-cos.js
+++ /dev/null
@@ -1,103 +0,0 @@
-// upload dist bundled-app to cos
-require('dotenv').config()
-const crypto = require('crypto')
-const fs = require('fs')
-const mime = require('mime-types')
-const pkg = require('../package.json')
-const configList = require('./config')
-const axios = require('axios').default
-const path = require('path')
-const distPath = path.join(__dirname, '../dist_electron')
-
-const BUCKET = 'picgo-1251750343'
-// const AREA = 'ap-chengdu'
-const VERSION = pkg.version
-const FILE_PATH = `${VERSION}/`
-const SECRET_ID = process.env.PICGO_ENV_COS_SECRET_ID
-const SECRET_KEY = process.env.PICGO_ENV_COS_SECRET_KEY
-
-// https://cloud.tencent.com/document/product/436/7778#signature
-/**
- * @param {string} fileName
- * @returns
- */
-const generateSignature = (fileName, folder = FILE_PATH) => {
- const secretKey = SECRET_KEY
- // const area = AREA
- const bucket = BUCKET
- const path = folder
- const today = Math.floor(new Date().getTime() / 1000)
- const tomorrow = today + 86400
- const signTime = `${today};${tomorrow}`
- const signKey = crypto.createHmac('sha1', secretKey).update(signTime).digest('hex')
- const httpString = `put\n/${path}${fileName}\n\nhost=${bucket}.cos.accelerate.myqcloud.com\n`
- const sha1edHttpString = crypto.createHash('sha1').update(httpString).digest('hex')
- const stringToSign = `sha1\n${signTime}\n${sha1edHttpString}\n`
- const signature = crypto.createHmac('sha1', signKey).update(stringToSign).digest('hex')
- return {
- signature,
- signTime
- }
-}
-
-/**
- *
- * @param {string} fileName
- * @param {Buffer} fileBuffer
- * @param {{ signature: string, signTime: string }} signature
- * @returns
- */
-const getReqOptions = (fileName, fileBuffer, signature, folder = FILE_PATH) => {
- return {
- method: 'PUT',
- url: `http://${BUCKET}.cos.accelerate.myqcloud.com/${encodeURI(folder)}${encodeURI(fileName)}`,
- headers: {
- Host: `${BUCKET}.cos.accelerate.myqcloud.com`,
- Authorization: `q-sign-algorithm=sha1&q-ak=${SECRET_ID}&q-sign-time=${signature.signTime}&q-key-time=${signature.signTime}&q-header-list=host&q-url-param-list=&q-signature=${signature.signature}`,
- contentType: mime.lookup(fileName),
- useAgent: `PicGo;${pkg.version};null;null`
- },
- maxContentLength: Infinity,
- maxBodyLength: Infinity,
- data: fileBuffer,
- resolveWithFullResponse: true
- }
-}
-
-const uploadFile = async () => {
- try {
- const platform = process.platform
- if (configList[platform]) {
- let versionFileHasUploaded = false
- for (const [index, config] of configList[platform].entries()) {
- const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}`
- const filePath = path.join(distPath, fileName)
- const versionFilePath = path.join(distPath, config['version-file'])
- let versionFileName = config['version-file']
- if (VERSION.toLocaleLowerCase().includes('beta')) {
- versionFileName = versionFileName.replace('.yml', '.beta.yml')
- }
- // upload dist file
- const signature = generateSignature(fileName)
- const reqOptions = getReqOptions(fileName, fs.readFileSync(filePath), signature)
- console.log('[PicGo Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
- await axios.request(reqOptions)
-
- // upload version file
- if (!versionFileHasUploaded) {
- const signature = generateSignature(versionFileName, '')
- const reqOptions = getReqOptions(versionFileName, fs.readFileSync(versionFilePath), signature, '')
- console.log('[PicGo Version File] Uploading...', versionFileName)
- await axios.request(reqOptions)
- versionFileHasUploaded = true
- }
- }
- } else {
- console.warn('platform not supported!', platform)
- }
- } catch (e) {
- console.error(e)
- }
-}
-
-uploadFile()
diff --git a/scripts/upload-dist-to-r2.js b/scripts/upload-dist-to-r2.js
new file mode 100644
index 0000000..b5ad424
--- /dev/null
+++ b/scripts/upload-dist-to-r2.js
@@ -0,0 +1,67 @@
+// upload dist bundled-app to r2
+require('dotenv').config()
+const S3 = require('aws-sdk/clients/s3')
+const pkg = require('../package.json')
+const configList = require('./config')
+const fs = require('fs')
+const path = require('path')
+
+const BUCKET = 'piclist-dl'
+const VERSION = pkg.version
+const FILE_PATH = `${VERSION}/`
+const ACCOUNT_ID = process.env.R2_ACCOUNT_ID
+const SECRET_ID = process.env.R2_SECRET_ID
+const SECRET_KEY = process.env.R2_SECRET_KEY
+console.log(ACCOUNT_ID, SECRET_ID, SECRET_KEY)
+
+const s3 = new S3({
+ endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
+ accessKeyId: SECRET_ID,
+ secretAccessKey: SECRET_KEY,
+ signatureVersion: 'v4',
+})
+
+const uploadFile = async () => {
+ try {
+ const platform = process.platform
+ if (configList[platform]) {
+ let versionFileHasUploaded = false
+ for (const [index, config] of configList[platform].entries()) {
+ const fileName = `${config.appNameWithPrefix}${VERSION}${config.arch}${config.ext}`
+ const distPath = path.join(__dirname, '../dist_electron')
+ let versionFileName = config['version-file']
+ console.log('[PicList Dist] Uploading...', fileName, `${index + 1}/${configList[platform].length}`)
+ const fileBuffer = fs.readFileSync(path.join(distPath, fileName))
+ await s3.upload({
+ Bucket: BUCKET,
+ Key: `${FILE_PATH}${fileName}`,
+ Body: fileBuffer
+ }).promise()
+ // upload version file
+ if (!versionFileHasUploaded) {
+ console.log('[PicList Version File] Uploading...', versionFileName)
+ let versionFilePath
+ if (platform === 'win32') {
+ versionFilePath = path.join(distPath, 'latest.yml')
+ } else if (platform === 'darwin') {
+ versionFilePath = path.join(distPath, 'latest-mac.yml')
+ } else {
+ versionFilePath = path.join(distPath, 'latest-linux.yml')
+ }
+ const versionFileBuffer = fs.readFileSync(versionFilePath)
+ await s3.upload({
+ Bucket: BUCKET,
+ Key: `${versionFileName}`,
+ Body: versionFileBuffer
+ }).promise()
+ versionFileHasUploaded = true
+ }
+ }
+ } else {
+ console.warn('platform not supported!', platform)
+ }
+ } catch (err) {
+ console.error(err)
+ }
+}
+uploadFile()
\ No newline at end of file
diff --git a/src/background.ts b/src/background.ts
index bec85b2..c8b4acb 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -1,23 +1,3 @@
import { bootstrap } from '~/main/lifeCycle'
bootstrap.launchApp()
-
-/**
- * Auto Updater
- *
- * Uncomment the following code below and install `electron-updater` to
- * support auto updating. Code Signing with a valid certificate is required.
- * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
-*/
-
-// import { autoUpdater } from 'electron-updater'
-
-// autoUpdater.on('update-downloaded', () => {
-// autoUpdater.quitAndInstall()
-// })
-
-// app.on('ready', () => {
-// if (process.env.NODE_ENV === 'production') {
-// autoUpdater.checkForUpdates()
-// }
-// })
diff --git a/src/main.ts b/src/main.ts
index 3b99a1c..337fd94 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -7,6 +7,7 @@ import { webFrame } from 'electron'
import VueLazyLoad from 'vue3-lazyload'
import axios from 'axios'
import { mainMixin } from './renderer/utils/mainMixin'
+import ContextMenu from '@imengyu/vue3-context-menu'
import { dragMixin } from '@/utils/mixin'
import { initTalkingData } from './renderer/utils/analytics'
import db from './renderer/utils/db'
@@ -15,6 +16,8 @@ import { getConfig, saveConfig, sendToMain, triggerRPC } from '@/utils/dataSende
import { store } from '@/store'
import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
+import { createPinia } from 'pinia'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
webFrame.setVisualZoomLevelLimits(1, 1)
@@ -45,6 +48,8 @@ app.config.globalProperties.sendToMain = sendToMain
app.mixin(mainMixin)
app.mixin(dragMixin)
+const pinia = createPinia()
+pinia.use(piniaPluginPersistedstate)
app.use(VueLazyLoad, {
error: `file://${__static.replace(/\\/g, '/')}/unknown-file-type.svg`
@@ -53,7 +58,8 @@ app.use(ElementUI)
app.use(router)
app.use(store)
app.use(vue3PhotoPreview)
-
+app.use(pinia)
+app.use(ContextMenu)
app.mount('#app')
initTalkingData()
diff --git a/src/main/apis/app/remoteNotice/index.ts b/src/main/apis/app/remoteNotice/index.ts
index 9a7db6b..521f5ae 100644
--- a/src/main/apis/app/remoteNotice/index.ts
+++ b/src/main/apis/app/remoteNotice/index.ts
@@ -9,12 +9,11 @@ import path from 'path'
import axios from 'axios'
import windowManager from '../window/windowManager'
import { showNotification } from '~/main/utils/common'
-import { isDev } from '~/universal/utils/common'
// for test
-const REMOTE_NOTICE_URL = isDev ? 'http://localhost:8181/remote-notice.json' : 'https://picgo-1251750343.cos.accelerate.myqcloud.com/remote-notice.yml'
+const REMOTE_NOTICE_URL = 'https://release.piclist.cn/remote-notice.json'
-const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'picgo-remote-notice.json'
+const REMOTE_NOTICE_LOCAL_STORAGE_FILE = 'piclist-remote-notice.json'
const STORE_PATH = app.getPath('userData')
@@ -106,7 +105,6 @@ class RemoteNoticeHandler {
if (this.checkActionCount(action)) {
switch (action.type) {
case IRemoteNoticeActionType.SHOW_DIALOG: {
- // SHOW DIALOG
const currentWindow = windowManager.getAvailableWindow()
dialog.showOpenDialog(currentWindow, action.data?.options)
break
diff --git a/src/main/apis/app/system/index.ts b/src/main/apis/app/system/index.ts
index 6c1919c..a03ed5e 100644
--- a/src/main/apis/app/system/index.ts
+++ b/src/main/apis/app/system/index.ts
@@ -181,7 +181,6 @@ export function createTray () {
}
} else {
const imgUrl = img.toDataURL()
- // console.log(imgUrl)
obj.push({
width: img.getSize().width,
height: img.getSize().height,
diff --git a/src/main/apis/app/uploader/apis.ts b/src/main/apis/app/uploader/apis.ts
index fd9efd3..e3d5350 100644
--- a/src/main/apis/app/uploader/apis.ts
+++ b/src/main/apis/app/uploader/apis.ts
@@ -10,7 +10,6 @@ import db, { GalleryDB } from '~/main/apis/core/datastore'
import { handleCopyUrl } from '~/main/utils/common'
import { handleUrlEncode } from '#/utils/common'
import { T } from '~/main/i18n/index'
-// import dayjs from 'dayjs'
const handleClipboardUploading = async (): Promise => {
const useBuiltinClipboard = !!db.get('settings.useBuiltinClipboard')
diff --git a/src/main/apis/app/uploader/index.ts b/src/main/apis/app/uploader/index.ts
index 667bcd6..34e6078 100644
--- a/src/main/apis/app/uploader/index.ts
+++ b/src/main/apis/app/uploader/index.ts
@@ -11,7 +11,7 @@ import db from '~/main/apis/core/datastore'
import windowManager from 'apis/app/window/windowManager'
import { IWindowList } from '#/types/enum'
import util from 'util'
-import { IPicGo } from 'picgo'
+import { IPicGo } from 'piclist'
import { showNotification, calcDurationRange, getClipboardFilePath } from '~/main/utils/common'
import { RENAME_FILE_NAME, TALKING_DATA_EVENT } from '~/universal/events/constants'
import logger from '@core/picgo/logger'
@@ -163,6 +163,9 @@ class Uploader {
duration: Date.now() - startTime
} as IAnalyticsData)
}
+ output.forEach((item: ImgInfo) => {
+ item.config = db.get(`picBed.${item.type}`)
+ })
return output.filter(item => item.imgUrl)
} else {
return false
diff --git a/src/main/apis/app/window/windowList.ts b/src/main/apis/app/window/windowList.ts
index 99e99a7..3ba74dd 100644
--- a/src/main/apis/app/window/windowList.ts
+++ b/src/main/apis/app/window/windowList.ts
@@ -11,17 +11,10 @@ import db from '~/main/apis/core/datastore'
import { TOGGLE_SHORTKEY_MODIFIED_MODE } from '#/events/constants'
import { app } from 'electron'
import { remoteNoticeHandler } from '../remoteNotice'
-// import { i18n } from '~/main/i18n'
-// import { URLSearchParams } from 'url'
const windowList = new Map()
const handleWindowParams = (windowURL: string) => {
- // const [baseURL, hash = ''] = windowURL.split('#')
- // const search = new URLSearchParams()
- // const lang = i18n.getLanguage()
- // search.append('lang', lang)
- // return `${baseURL}?${search.toString()}#${hash}`
return windowURL
}
diff --git a/src/main/apis/app/window/windowManager.ts b/src/main/apis/app/window/windowManager.ts
index 5aa38a2..d5f000f 100644
--- a/src/main/apis/app/window/windowManager.ts
+++ b/src/main/apis/app/window/windowManager.ts
@@ -45,14 +45,6 @@ class WindowManager implements IWindowManager {
return this.windowMap.has(name)
}
- // useless
- // delete (name: IWindowList) {
- // const window = this.windowMap.get(name)
- // if (window) {
- // this.windowIdMap.delete(window.id)
- // this.windowMap.delete(name)
- // }
- // }
deleteById = (id: number) => {
const name = this.windowIdMap.get(id)
if (name) {
diff --git a/src/main/apis/core/datastore/dbChecker.ts b/src/main/apis/core/datastore/dbChecker.ts
index 9d7bdc4..6ab6fb0 100644
--- a/src/main/apis/core/datastore/dbChecker.ts
+++ b/src/main/apis/core/datastore/dbChecker.ts
@@ -1,11 +1,11 @@
import fs from 'fs-extra'
import writeFile from 'write-file-atomic'
import path from 'path'
-import { app as APP } from 'electron'
-import { getLogger } from '@core/utils/localLogger'
+import { app } from 'electron'
+import { getLogger } from '../utils/localLogger'
import dayjs from 'dayjs'
import { T } from '~/main/i18n'
-const STORE_PATH = APP.getPath('userData')
+const STORE_PATH = app.getPath('userData')
const configFilePath = path.join(STORE_PATH, 'data.json')
const configFileBackupPath = path.join(STORE_PATH, 'data.bak.json')
export const defaultConfigPath = configFilePath
@@ -79,7 +79,6 @@ function dbPathChecker (): string {
if (_configFilePath) {
return _configFilePath
}
- // defaultConfigPath
_configFilePath = defaultConfigPath
// if defaultConfig path is not exit
// do not parse the content of config
@@ -98,8 +97,8 @@ function dbPathChecker (): string {
}
return _configFilePath
} catch (e) {
- const picgoLogPath = path.join(STORE_PATH, 'picgo-gui-local.log')
- const logger = getLogger(picgoLogPath)
+ const piclistLogPath = path.join(STORE_PATH, 'piclist-gui-local.log')
+ const logger = getLogger(piclistLogPath, 'PicList')
if (!hasCheckPath) {
const optionsTpl = {
title: T('TIPS_NOTICE'),
@@ -123,8 +122,8 @@ function getGalleryDBPath (): {
dbBackupPath: string
} {
const configPath = dbPathChecker()
- const dbPath = path.join(path.dirname(configPath), 'picgo.db')
- const dbBackupPath = path.join(path.dirname(dbPath), 'picgo.bak.db')
+ const dbPath = path.join(path.dirname(configPath), 'piclist.db')
+ const dbBackupPath = path.join(path.dirname(dbPath), 'piclist.bak.db')
return {
dbPath,
dbBackupPath
diff --git a/src/main/apis/core/picgo/index.ts b/src/main/apis/core/picgo/index.ts
index e9d5d1b..6cf521a 100644
--- a/src/main/apis/core/picgo/index.ts
+++ b/src/main/apis/core/picgo/index.ts
@@ -1,6 +1,6 @@
import { dbChecker, dbPathChecker } from 'apis/core/datastore/dbChecker'
import pkg from 'root/package.json'
-import { PicGo } from 'picgo'
+import { PicGo } from 'piclist'
import db from 'apis/core/datastore'
import debounce from 'lodash/debounce'
diff --git a/src/main/apis/core/utils/localLogger.ts b/src/main/apis/core/utils/localLogger.ts
index 0083edd..3cd5518 100644
--- a/src/main/apis/core/utils/localLogger.ts
+++ b/src/main/apis/core/utils/localLogger.ts
@@ -41,9 +41,9 @@ const recreateLogFile = (logPath: string): void => {
}
/**
- * for local log before picgo inited
+ * for local log before piclist inited
*/
-const getLogger = (logPath: string) => {
+const getLogger = (logPath: string, logtype: string) => {
let hasUncathcedError = false
try {
if (!fs.existsSync(logPath)) {
@@ -64,7 +64,7 @@ const getLogger = (logPath: string) => {
return
}
try {
- let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicGo ${type.toUpperCase()}] `
+ let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [${logtype} ${type.toUpperCase()}] `
msg.forEach((item: ILogArgvTypeWithError) => {
if (typeof item === 'object' && type === 'error') {
log += `\n------Error Stack Begin------\n${util.format(item.stack)}\n-------Error Stack End------- `
diff --git a/src/main/events/picgoCoreIPC.ts b/src/main/events/picgoCoreIPC.ts
index de70b21..16ebf53 100644
--- a/src/main/events/picgoCoreIPC.ts
+++ b/src/main/events/picgoCoreIPC.ts
@@ -11,7 +11,7 @@ import { IPasteStyle, IPicGoHelperType, IWindowList } from '#/types/enum'
import shortKeyHandler from 'apis/app/shortKey/shortKeyHandler'
import picgo from '@core/picgo'
import { handleStreamlinePluginName, simpleClone } from '~/universal/utils/common'
-import { IGuiMenuItem, PicGo as PicGoCore } from 'picgo'
+import { IGuiMenuItem, PicGo as PicGoCore } from 'piclist'
import windowManager from 'apis/app/window/windowManager'
import { showNotification } from '~/main/utils/common'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
diff --git a/src/main/events/remotes/menu.ts b/src/main/events/remotes/menu.ts
index be5605b..4e0457f 100644
--- a/src/main/events/remotes/menu.ts
+++ b/src/main/events/remotes/menu.ts
@@ -11,7 +11,7 @@ import pkg from 'root/package.json'
import GuiApi from 'apis/gui'
import { PICGO_CONFIG_PLUGIN, PICGO_HANDLE_PLUGIN_DONE, PICGO_HANDLE_PLUGIN_ING, PICGO_TOGGLE_PLUGIN, SHOW_MAIN_PAGE_DONATION, SHOW_MAIN_PAGE_QRCODE } from '~/universal/events/constants'
import picgoCoreIPC from '~/main/events/picgoCoreIPC'
-import { PicGo as PicGoCore } from 'picgo'
+import { PicGo as PicGoCore } from 'piclist'
import { T } from '~/main/i18n'
import { changeCurrentUploader } from '~/main/utils/handleUploaderConfig'
diff --git a/src/main/lifeCycle/errorHandler.ts b/src/main/lifeCycle/errorHandler.ts
index 38a7e3b..38b3b7e 100644
--- a/src/main/lifeCycle/errorHandler.ts
+++ b/src/main/lifeCycle/errorHandler.ts
@@ -2,9 +2,9 @@ import path from 'path'
import { app } from 'electron'
import { getLogger } from 'apis/core/utils/localLogger'
const STORE_PATH = app.getPath('userData')
-const LOG_PATH = path.join(STORE_PATH, 'picgo-gui-local.log')
+const LOG_PATH = path.join(STORE_PATH, 'piclist-gui-local.log')
-const logger = getLogger(LOG_PATH)
+const logger = getLogger(LOG_PATH, 'PicList')
// since the error may occur in picgo-core
// so we can't use the log from picgo
diff --git a/src/main/lifeCycle/fixPath.ts b/src/main/lifeCycle/fixPath.ts
index ec6526f..0aa5b10 100644
--- a/src/main/lifeCycle/fixPath.ts
+++ b/src/main/lifeCycle/fixPath.ts
@@ -1,8 +1,8 @@
// 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
-
-const shellPath = require('shell-path')
+// @ts-nocheck
+import { shellPath } from 'shell-path'
export default function fixPath () {
if (process.platform === 'win32') {
diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts
index add21c1..1ecc7df 100644
--- a/src/main/lifeCycle/index.ts
+++ b/src/main/lifeCycle/index.ts
@@ -34,9 +34,12 @@ import bus from '@core/bus'
import logger from 'apis/core/picgo/logger'
import picgo from 'apis/core/picgo'
import fixPath from './fixPath'
+import { clearTempFolder } from '../manage/utils/common'
import { initI18n } from '~/main/utils/handleI18n'
import { remoteNoticeHandler } from 'apis/app/remoteNotice'
-
+import { manageIpcList } from '../manage/events/ipcList'
+import getManageApi from '../manage/Main'
+import UpDownTaskQueue from '../manage/datastore/upDownTaskQueue'
const isDevelopment = process.env.NODE_ENV !== 'production'
const handleStartUpFiles = (argv: string[], cwd: string) => {
@@ -64,6 +67,9 @@ class LifeCycle {
beforeOpen()
initI18n()
ipcList.listen()
+ getManageApi()
+ UpDownTaskQueue.getInstance()
+ manageIpcList.listen()
busEventList.listen()
updateShortKeyFromVersion212(db, db.get('settings.shortKey'))
await migrateGalleryFromVersion230(db, GalleryDB.getInstance(), picgo)
@@ -135,7 +141,7 @@ class LifeCycle {
openAtLogin: db.get('settings.autoStart') || false
})
if (process.platform === 'win32') {
- app.setAppUserModelId('com.molunerfinn.picgo')
+ app.setAppUserModelId('com.kuingsmile.piclist')
}
if (process.env.XDG_CURRENT_DESKTOP && process.env.XDG_CURRENT_DESKTOP.includes('Unity')) {
@@ -151,6 +157,8 @@ class LifeCycle {
})
app.on('will-quit', () => {
+ UpDownTaskQueue.getInstance().persist()
+ clearTempFolder()
globalShortcut.unregisterAll()
bus.removeAllListeners()
server.shutdown()
diff --git a/src/main/manage/Main.ts b/src/main/manage/Main.ts
new file mode 100644
index 0000000..e2419ef
--- /dev/null
+++ b/src/main/manage/Main.ts
@@ -0,0 +1,10 @@
+/* eslint-disable */
+import { manageDbChecker } from './datastore/dbChecker'
+import { ManageApi } from './manageApi'
+
+manageDbChecker()
+const getManageApi = (picBedName: string = 'placeholder'): ManageApi => {
+ return new ManageApi(picBedName)
+}
+
+export default getManageApi
diff --git a/src/main/manage/apis/aliyun.ts b/src/main/manage/apis/aliyun.ts
new file mode 100644
index 0000000..9ce1ee7
--- /dev/null
+++ b/src/main/manage/apis/aliyun.ts
@@ -0,0 +1,587 @@
+import axios from 'axios'
+import { hmacSha1Base64, getFileMimeType, gotDownload, formatError } from '../utils/common'
+import { ipcMain, IpcMainEvent } from 'electron'
+import fs from 'fs-extra'
+import { XMLParser } from 'fast-xml-parser'
+import OSS from 'ali-oss'
+import path from 'path'
+import { isImage } from '~/renderer/manage/utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import UpDownTaskQueue,
+{
+ uploadTaskSpecialStatus,
+ commonTaskStatus
+} from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+
+// 坑爹阿里云 返回数据类型标注和实际各种不一致
+class AliyunApi {
+ ctx: OSS
+ accessKeyId: string
+ accessKeySecret: string
+ timeOut = 60000
+ logger: ManageLogger
+
+ constructor (accessKeyId: string, accessKeySecret: string, logger: ManageLogger) {
+ this.ctx = new OSS({
+ accessKeyId,
+ accessKeySecret,
+ secure: true
+ })
+ this.accessKeyId = accessKeyId
+ this.accessKeySecret = accessKeySecret
+ this.logger = logger
+ }
+
+ formatFolder (item: string, slicedPrefix: string) {
+ return {
+ key: item,
+ fileSize: 0,
+ formatedTime: '',
+ fileName: item.replace(slicedPrefix, '').replace('/', ''),
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false,
+ Key: item
+ }
+ }
+
+ formatFile (item: OSS.ObjectMeta, slicedPrefix: string, urlPrefix: string): any {
+ const result = {
+ ...item,
+ key: item.name,
+ rawUrl: `${urlPrefix}/${item.name}`,
+ fileName: item.name.replace(slicedPrefix, ''),
+ fileSize: item.size,
+ formatedTime: new Date(item.lastModified).toLocaleString(),
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(item.name.replace(slicedPrefix, ''))
+ }
+ const temp = result.rawUrl
+ result.rawUrl = result.url
+ result.url = temp
+ return result
+ }
+
+ getCanonicalizedOSSHeaders (headers: IStringKeyMap) {
+ const lowerCaseHeaders = Object.keys(headers).reduce((acc, key) => {
+ acc[key.toLowerCase()] = headers[key]
+ return acc
+ }, {} as IStringKeyMap)
+ let canonicalizedOSSHeaders = ''
+ const headerKeys = Object.keys(lowerCaseHeaders).sort()
+ 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) {
+ 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)}`
+ }
+
+ getNewCtx (region: string, bucket: string) {
+ return new OSS({
+ accessKeyId: this.accessKeyId,
+ accessKeySecret: this.accessKeySecret,
+ region,
+ bucket,
+ secure: true
+ })
+ }
+
+ /**
+ * 获取存储桶列表
+ */
+ async getBucketList (): Promise {
+ const formatItem = (item: OSS.Bucket) => {
+ return {
+ Name: item.name,
+ Location: item.region,
+ CreationDate: item.creationDate
+ }
+ }
+ const res = await this.ctx.listBuckets({
+ 'max-keys': 1000
+ }) as IStringKeyMap
+ const result = [] as IStringKeyMap[]
+ let NextMarker = ''
+ if (res.res.statusCode === 200) {
+ if (res.buckets) {
+ result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
+ let isTruncated = res.isTruncated
+ NextMarker = res.nextMarker
+ while (isTruncated) {
+ const res = await this.ctx.listBuckets({
+ marker: NextMarker,
+ 'max-keys': 1000
+ }) as IStringKeyMap
+ if (res.res.statusCode === 200) {
+ if (res.buckets) {
+ result.push(...res.buckets.map((item: OSS.Bucket) => formatItem(item)))
+ isTruncated = res.isTruncated
+ NextMarker = res.nextMarker
+ } else {
+ isTruncated = false
+ }
+ } else {
+ isTruncated = false
+ }
+ }
+ return result
+ } else {
+ return []
+ }
+ } else {
+ return []
+ }
+ }
+
+ /**
+ * 获取自定义域名
+ */
+ async getBucketDomain (param: IStringKeyMap): Promise {
+ const headers = {
+ Date: new Date().toUTCString()
+ }
+ const authorization = this.authorization('GET', `/${param.bucketName}/?cname`, headers, '', '')
+ const res = await axios({
+ url: `https://${param.bucketName}.${param.region}.aliyuncs.com/?cname`,
+ method: 'GET',
+ headers: {
+ ...headers,
+ Authorization: authorization
+ }
+ })
+ if (res.status === 200) {
+ const parser = new XMLParser()
+ const result = parser.parse(res.data)
+ if (result.ListCnameResult && result.ListCnameResult.Cname) {
+ if (Array.isArray(result.ListCnameResult.Cname)) {
+ const cnameList = [] as string[]
+ result.ListCnameResult.Cname.forEach((item: IStringKeyMap) => {
+ item.Status === 'Enabled' && cnameList.push(item.Domain)
+ })
+ return cnameList
+ } else {
+ return result.ListCnameResult.Cname.Status === 'Enabled' ? [result.ListCnameResult.Cname.Domain] : []
+ }
+ } else {
+ return []
+ }
+ } else {
+ return []
+ }
+ }
+
+ /**
+ * 创建存储桶
+ * @param {Object} configMap
+ * configMap = {
+ * BucketName: string,
+ * region: string,
+ * acl: string
+ * }
+ * @description
+ * acl: private | publicRead | publicReadWrite
+ */
+ async createBucket (configMap: IStringKeyMap): Promise {
+ const client = new OSS({
+ accessKeyId: this.accessKeyId,
+ accessKeySecret: this.accessKeySecret,
+ region: configMap.region,
+ secure: true
+ })
+ const aclTransMap: IStringKeyMap = {
+ private: 'private',
+ publicRead: 'public-read',
+ publicReadWrite: 'public-read-write'
+ }
+ const res = await client.putBucket(configMap.BucketName, {
+ acl: aclTransMap[configMap.acl],
+ storageClass: 'Standard',
+ dataRedundancyType: 'LRS',
+ timeout: this.timeOut
+ })
+ return res && res.res.status === 200
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ 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
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ const client = this.getNewCtx(region, bucket)
+ do {
+ res = await client.listV2({
+ prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ delimiter: '/',
+ 'max-keys': '1000',
+ 'continuation-token': marker
+ }, {
+ timeout: this.timeOut
+ })
+ if (res && res.res.statusCode === 200) {
+ res.prefixes && res.prefixes.forEach((item: string) => {
+ result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ res.objects && res.objects.forEach((item: OSS.ObjectMeta) => {
+ item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
+ })
+ window.webContents.send('refreshFileTransferList', result)
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ marker = res.nextContinuationToken
+ } while (res.isTruncated === true && !cancelTask[0])
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 获取文件列表
+ * @param {Object} configMap
+ * configMap = {
+ * bucketName: string,
+ * bucketConfig: {
+ * Location: string
+ * },
+ * paging: boolean,
+ * prefix: string,
+ * marker: string,
+ * itemsPerPage: number,
+ * customUrl: string
+ * }
+ */
+ async getBucketFileList (configMap: IStringKeyMap): Promise {
+ const { bucketName: bucket, bucketConfig: { Location: region }, prefix, marker, itemsPerPage } = configMap
+ const slicedPrefix = prefix.slice(1)
+ const urlPrefix = configMap.customUrl || `https://${bucket}.${region}.aliyuncs.com`
+ let res = {} as any
+ const result = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ const client = this.getNewCtx(region, bucket)
+ res = await client.listV2({
+ prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ delimiter: '/',
+ 'max-keys': itemsPerPage.toString(),
+ 'continuation-token': marker
+ }, {
+ timeout: this.timeOut
+ }) as any
+ // prefixes can be null
+ // objects will be [] when no file
+ if (res && res.res.statusCode === 200) {
+ res.prefixes && res.prefixes.forEach((item: string) => {
+ result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ res.objects && res.objects.forEach((item: OSS.ObjectMeta) => {
+ item.size !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
+ })
+ result.isTruncated = res.isTruncated
+ result.nextMarker = res.nextContinuationToken === null ? '' : res.nextContinuationToken
+ result.success = true
+ return result
+ } else {
+ return result
+ }
+ }
+
+ /**
+ * 重命名文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * oldKey: string,
+ * newKey: string
+ * }
+ */
+ async renameBucketFile (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, oldKey, newKey } = configMap
+ const client = this.getNewCtx(region, bucketName)
+ const res = await client.copy(
+ newKey,
+ oldKey
+ ) as any
+ if (res && res.res.statusCode === 200) {
+ const res2 = await client.delete(oldKey) as any
+ return res2 && res2.res.statusCode === 204
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ const client = this.getNewCtx(region, bucketName)
+ const res = await client.delete(key) as any
+ return res && res.res.statusCode === 204
+ }
+
+ /**
+ * 删除文件夹
+ * @param configMap
+ */
+ async deleteBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ const client = this.getNewCtx(region, bucketName)
+ let marker
+ let isTruncated
+ const allFileList = {
+ CommonPrefixes: [] as any[],
+ Contents: [] as any[]
+ }
+ let res = await client.listV2({
+ prefix: key,
+ delimiter: '/',
+ 'max-keys': '1000'
+ }, {
+ timeout: 60000
+ }) as any
+ if (res && res.res.statusCode === 200) {
+ res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
+ res.objects.length > 0 && allFileList.Contents.push(...res.objects)
+ isTruncated = res.isTruncated
+ marker = res.nextContinuationToken
+ while (isTruncated) {
+ res = await client.listV2({
+ prefix: key,
+ delimiter: '/',
+ 'max-keys': '1000',
+ 'continuation-token': marker
+ }, {
+ timeout: this.timeOut
+ }) as any
+ if (res && res.res.statusCode === 200) {
+ res.prefixes !== null && allFileList.CommonPrefixes.push(...res.prefixes)
+ res.objects.length > 0 && allFileList.Contents.push(...res.objects)
+ isTruncated = res.isTruncated
+ marker = res.nextContinuationToken
+ } else {
+ return false
+ }
+ }
+ } else {
+ return false
+ }
+ if (allFileList.CommonPrefixes.length > 0) {
+ for (const item of allFileList.CommonPrefixes) {
+ res = await this.deleteBucketFolder({
+ bucketName,
+ region,
+ key: item
+ })
+ if (!res) {
+ return false
+ }
+ }
+ }
+ if (allFileList.Contents.length > 0) {
+ const cycle = Math.ceil(allFileList.Contents.length / 1000)
+ for (let i = 0; i < cycle; i++) {
+ res = await client.deleteMulti(
+ allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
+ return item.name
+ })
+ ) as any
+ if (!(res && res.res.statusCode === 200)) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ /**
+ * 获取预签名url
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string,
+ * expires: number,
+ * customUrl: string
+ * }
+ */
+ async getPreSignedUrl (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key, expires, customUrl } = configMap
+ const client = this.getNewCtx(region, bucketName)
+ const res = client.signatureUrl(key, {
+ expires: expires || 3600
+ })
+ return customUrl ? `${customUrl.replace(/\/$/, '')}/${key}${res.slice(res.indexOf('?'))}` : res
+ }
+
+ /**
+ * 上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ // fileArray = [{
+ // bucketName: string,
+ // region: string,
+ // key: string,
+ // filePath: string
+ // fileSize: number
+ // }]
+ const instance = UpDownTaskQueue.getInstance()
+ fileArray.forEach((item: any) => {
+ item.key.startsWith('/') && (item.key = item.key.slice(1))
+ })
+ for (const item of fileArray) {
+ const { bucketName, region, key, filePath, fileName } = item
+ const client = this.getNewCtx(region, bucketName)
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ if (instance.getUploadTask(id)) {
+ continue
+ }
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: region
+ })
+ client.multipartUpload(
+ key,
+ filePath,
+ {
+ partSize: 1 * 1024 * 1024,
+ mime: getFileMimeType(fileName),
+ progress: (p: number) => {
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ instance.updateUploadTask({
+ id,
+ progress: Math.floor(p * 100),
+ status: uploadTaskSpecialStatus.uploading
+ })
+ },
+ timeout: 60000
+ }
+ ).then((res: any) => {
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ if (res && res.res.statusCode === 200) {
+ instance.updateUploadTask({
+ id,
+ progress: 100,
+ status: uploadTaskSpecialStatus.uploaded,
+ response: JSON.stringify(res),
+ finishTime: new Date().toLocaleString()
+ })
+ } else {
+ instance.updateUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ response: JSON.stringify(res),
+ finishTime: new Date().toLocaleString()
+ })
+ }
+ }).catch((err: any) => {
+ this.logger.error(formatError(err, { class: 'AliyunApi', method: 'uploadBucketFile' }))
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ instance.updateUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ response: JSON.stringify(err),
+ finishTime: new Date().toLocaleString()
+ })
+ })
+ }
+ return true
+ }
+
+ /**
+ * 新建文件夹
+ * @param configMap
+ */
+ async createBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ const client = this.getNewCtx(region, bucketName)
+ const res = await client.put(key, Buffer.from('')) as any
+ return res && res.res.statusCode === 200
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ // fileArray = [{
+ // bucketName: string,
+ // region: string,
+ // key: string,
+ // fileName: string
+ // }]
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName } = item
+ const client = this.getNewCtx(region, bucketName)
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ const id = `${bucketName}-${region}-${key}`
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ const preSignedUrl = client.signatureUrl(key, {
+ expires: 60 * 60 * 48
+ })
+ gotDownload(instance, preSignedUrl, fileStream, id, savedFilePath, this.logger)
+ }
+ return true
+ }
+}
+
+export default AliyunApi
diff --git a/src/main/manage/apis/api.ts b/src/main/manage/apis/api.ts
new file mode 100644
index 0000000..fabfb14
--- /dev/null
+++ b/src/main/manage/apis/api.ts
@@ -0,0 +1,17 @@
+import TcyunApi from './tcyun'
+import AliyunApi from './aliyun'
+import QiniuApi from './qiniu'
+import UpyunApi from './upyun'
+import SmmsApi from './smms'
+import GithubApi from './github'
+import ImgurApi from './imgur'
+
+export default {
+ TcyunApi,
+ AliyunApi,
+ QiniuApi,
+ UpyunApi,
+ SmmsApi,
+ GithubApi,
+ ImgurApi
+}
diff --git a/src/main/manage/apis/github.ts b/src/main/manage/apis/github.ts
new file mode 100644
index 0000000..877f06b
--- /dev/null
+++ b/src/main/manage/apis/github.ts
@@ -0,0 +1,436 @@
+import got from 'got'
+import { ManageLogger } from '../utils/logger'
+import { isImage } from '~/renderer/manage/utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import { gotUpload, trimPath, gotDownload, getAgent, getOptions } from '../utils/common'
+import UpDownTaskQueue,
+{
+ commonTaskStatus
+} from '../datastore/upDownTaskQueue'
+import fs from 'fs-extra'
+import path from 'path'
+
+class GithubApi {
+ token: string
+ username: string
+ logger: ManageLogger
+ proxy: any
+ baseUrl = 'https://api.github.com'
+ commonHeaders : IStringKeyMap
+
+ constructor (token: string, username: string, proxy: string | undefined, logger: ManageLogger) {
+ this.logger = logger
+ this.token = token.startsWith('Bearer ') ? token : `Bearer ${token}`.trim()
+ this.username = username
+ this.proxy = proxy
+ this.commonHeaders = {
+ Authorization: this.token,
+ Accept: 'application/vnd.github+json'
+ }
+ }
+
+ formatFolder (item: any, slicedPrefix: string) {
+ let key = ''
+ if (slicedPrefix === '') {
+ key = `${item.path}/`
+ } else {
+ key = `${slicedPrefix}/${item.path}/`
+ }
+ return {
+ ...item,
+ Key: key,
+ key,
+ fileSize: 0,
+ formatedTime: '',
+ fileName: item.path,
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false
+ }
+ }
+
+ formatFile (item: any, slicedPrefix: string, branch: string, repo: string, cdnUrl: string | undefined) {
+ let rawUrl = ''
+ if (cdnUrl) {
+ const placeholder = ['{username}', '{repo}', '{branch}', '{path}']
+ if (placeholder.some(item => cdnUrl.includes(item))) {
+ rawUrl = cdnUrl.replace('{username}', this.username)
+ .replace('{repo}', repo)
+ .replace('{branch}', branch)
+ .replace('{path}', `${slicedPrefix}/${item.path}`)
+ } else {
+ rawUrl = `${cdnUrl}/${slicedPrefix}/${item.path}`
+ }
+ } else {
+ rawUrl = `https://raw.githubusercontent.com/${this.username}/${repo}/${branch}/${slicedPrefix}/${item.path}`
+ }
+ rawUrl = rawUrl.replace(/(? {
+ let initPage = 1
+ let res
+ const result = [] as any[]
+ do {
+ res = await got(
+ `${this.baseUrl}/user/repos`,
+ 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: item.name,
+ Location: item.id,
+ CreationDate: item.created_at
+ })
+ })
+ } else {
+ return []
+ }
+ initPage++
+ } while (res.body.length > 0)
+ return result
+ }
+
+ /**
+ * 获取branch列表
+ */
+ async getBucketDomain (param: IStringKeyMap): Promise {
+ const { bucketName: repo } = param
+ let initPage = 1
+ let res
+ const result = [] as string[]
+ do {
+ 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
+ if (res.statusCode === 200) {
+ res.body.forEach((item: any) => result.push(item.name))
+ } else {
+ return []
+ }
+ initPage++
+ } while (res.body.length > 0)
+ return result
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { bucketName: repo, customUrl: branch, prefix, cancelToken, cdnUrl } = configMap
+ const slicedPrefix = prefix.replace(/^\//, '').replace(/\/$/, '')
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ res = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${slicedPrefix}`,
+ getOptions('GET', this.commonHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ )
+ if (res && res.statusCode === 200) {
+ res.body.tree.forEach((item: any) => {
+ if (item.type === 'tree') {
+ result.fullList.push(this.formatFolder(item, slicedPrefix))
+ } else {
+ result.fullList.push(this.formatFile(item, slicedPrefix, branch, repo, cdnUrl))
+ }
+ })
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { bucketName: repo, githubBranch: branch, key, DeleteHash: sha } = configMap
+ const body = {
+ message: 'deleted by PicList',
+ sha,
+ branch
+ }
+ const res = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`,
+ getOptions('DELETE', this.commonHeaders, undefined, 'json', JSON.stringify(body), undefined, this.proxy)
+ )
+ return res.statusCode === 200
+ }
+
+ /**
+ * create a new tree to delete a folder
+ * @param configMap
+ */
+ async deleteBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName: repo, githubBranch: branch, key } = configMap
+ 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
+ if (refRes.statusCode !== 200) {
+ return false
+ }
+ const refSha = refRes.body.object.sha
+ const rootRes = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/branches/${branch}`,
+ getOptions('GET', undefined, undefined, 'json', undefined, undefined, this.proxy)
+ ) 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
+ const treeRes = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/git/trees/${branch}:${key.replace(/^\//, '').replace(/\/$/, '')}`,
+ getOptions('GET', this.commonHeaders, {
+ recursive: true
+ }, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (treeRes.statusCode !== 200) {
+ return false
+ }
+ const oldTree = treeRes.body.tree
+ const newTree = oldTree.filter((item: any) => item.type === 'blob')
+ .map((item:any) => ({
+ path: `${key.replace(/^\//, '').replace(/\/$/, '')}/${item.path}`,
+ mode: item.mode,
+ type: item.type,
+ sha: null
+ }))
+ const newTreeShaRes = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/git/trees`,
+ getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
+ base_tree: rootSha,
+ tree: newTree
+ }), undefined, this.proxy)
+ ) as any
+ if (newTreeShaRes.statusCode !== 201) {
+ return false
+ }
+ const newTreeSha = newTreeShaRes.body.sha
+ const commitRes = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/git/commits`,
+ getOptions('POST', this.commonHeaders, undefined, 'json', JSON.stringify({
+ message: 'deleted by PicList',
+ tree: newTreeSha,
+ parents: [refSha]
+ }), undefined, this.proxy)
+ ) as any
+ if (commitRes.statusCode !== 201) {
+ return false
+ }
+ const commitSha = commitRes.body.sha
+ const updateRefRes = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/git/refs/heads/${branch}`,
+ getOptions('PATCH', this.commonHeaders, undefined, 'json', JSON.stringify({
+ sha: commitSha
+ }), undefined, this.proxy)
+ ) as any
+ if (updateRefRes.statusCode !== 200) {
+ return false
+ }
+ return true
+ }
+
+ /**
+ * 获取预签名url
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string,
+ * expires: number,
+ * customUrl: string
+ * }
+ */
+ async getPreSignedUrl (configMap: IStringKeyMap): Promise {
+ const { bucketName: repo, customUrl: branch, key, rawUrl, githubPrivate: isPrivate } = configMap
+ if (!isPrivate) {
+ return rawUrl
+ }
+ const res = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/contents/${key}`,
+ getOptions('GET', this.commonHeaders, {
+ ref: branch
+ }, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (res.statusCode === 200) {
+ return res.body.download_url
+ } else {
+ return ''
+ }
+ }
+
+ /**
+ * 新建文件夹
+ * @param configMap
+ */
+ async createBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName: repo, githubBranch: branch, key } = configMap
+ const newFileKey = `${trimPath(key)}/.gitkeep`
+ const base64Content = Buffer.from('created by PicList').toString('base64')
+ const body = {
+ message: `created a new folder named ${key} by PicList`,
+ content: base64Content,
+ branch
+ }
+ const res = await got(
+ `${this.baseUrl}/repos/${this.username}/${repo}/contents/${newFileKey}`,
+ getOptions('PUT', this.commonHeaders, undefined, 'json', JSON.stringify(body), undefined, this.proxy)
+ )
+ return res.statusCode === 201
+ }
+
+ /**
+ * 上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ fileArray.forEach((item: any) => {
+ item.key.startsWith('/') && (item.key = item.key.slice(1))
+ })
+ const filteredFileArray = fileArray.filter((item: any) => item.fileSize < 100 * 1024 * 1024)
+ for (const item of filteredFileArray) {
+ const { bucketName: repo, region, githubBranch: branch, key, filePath, fileName } = item
+ const id = `${repo}-${branch}-${key}-${filePath}`
+ if (instance.getUploadTask(id)) {
+ continue
+ }
+ const trimKey = trimPath(key)
+ const base64Content = fs.readFileSync(filePath, { encoding: 'base64' })
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: repo,
+ targetFileRegion: region
+ })
+ gotUpload(
+ instance,
+ `${this.baseUrl}/repos/${this.username}/${repo}/contents/${trimKey}`,
+ 'PUT',
+ JSON.stringify({
+ message: 'uploaded by PicList',
+ branch,
+ content: base64Content
+ }),
+ this.commonHeaders,
+ id,
+ this.logger,
+ 30000,
+ false,
+ getAgent(this.proxy)
+ )
+ }
+ return true
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName: repo, customUrl: branch, key, fileName, githubPrivate, githubUrl } = item
+ const id = `${repo}-${branch}-${key}-${fileName}`
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ let downloadUrl
+ if (githubPrivate) {
+ const preSignedUrl = await this.getPreSignedUrl({
+ bucketName: repo,
+ customUrl: branch,
+ key,
+ rawUrl: githubUrl,
+ githubPrivate
+ })
+ downloadUrl = preSignedUrl
+ } else {
+ downloadUrl = githubUrl
+ }
+ gotDownload(
+ instance,
+ downloadUrl,
+ fileStream,
+ id,
+ savedFilePath,
+ this.logger,
+ undefined,
+ getAgent(this.proxy)
+ )
+ }
+ return true
+ }
+}
+
+export default GithubApi
diff --git a/src/main/manage/apis/imgur.ts b/src/main/manage/apis/imgur.ts
new file mode 100644
index 0000000..1577abb
--- /dev/null
+++ b/src/main/manage/apis/imgur.ts
@@ -0,0 +1,262 @@
+import got from 'got'
+import ManageLogger from '../utils/logger'
+import { getAgent, getOptions, gotDownload, gotUpload, getFileMimeType } from '../utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import { isImage } from '~/renderer/manage/utils/common'
+import path from 'path'
+import UpDownTaskQueue,
+{
+ commonTaskStatus
+} from '../datastore/upDownTaskQueue'
+import FormData from 'form-data'
+import fs from 'fs-extra'
+
+class ImgurApi {
+ userName: string
+ accessToken: string
+ proxy: any
+ logger: ManageLogger
+ tokenHeaders: any
+ idHeaders: any
+ baseUrl = 'https://api.imgur.com/3'
+
+ constructor (userName: string, accessToken: string, proxy: any, logger: ManageLogger) {
+ this.userName = userName
+ this.accessToken = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`
+ this.proxy = proxy
+ this.logger = logger
+ this.tokenHeaders = {
+ Authorization: this.accessToken
+ }
+ }
+
+ formatFile (item: any) {
+ return {
+ ...item,
+ Key: path.basename(item.link),
+ key: path.basename(item.link),
+ fileName: `${item.name}${path.extname(item.link)}`,
+ formatedTime: new Date(item.datetime * 1000).toLocaleString(),
+ fileSize: item.size,
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(path.basename(item.link)),
+ url: item.link,
+ sha: item.deletehash
+ }
+ }
+
+ /**
+ * get repo list
+ */
+ async getBucketList (): Promise {
+ let initPage = 0
+ let res
+ const result = [] as any[]
+ do {
+ res = await got(
+ `${this.baseUrl}/account/${this.userName}/albums/ids/${initPage}`,
+ getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (res.statusCode === 200 && res.body.success) {
+ res.body.data.forEach((item: any) => {
+ result.push(item)
+ })
+ } else {
+ return []
+ }
+ initPage++
+ } while (res.body.data.length > 0)
+ const finalResult = [] as any[]
+ for (let i = 0; i < result.length; i++) {
+ const item = result[i]
+ const res = await got(
+ `${this.baseUrl}/account/${this.userName}/album/${item}`,
+ getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (res.statusCode === 200 && res.body.success) {
+ finalResult.push({
+ ...res.body.data,
+ Name: res.body.data.title,
+ Location: res.body.data.id,
+ CreationDate: res.body.data.datetime
+ })
+ } else {
+ return []
+ }
+ }
+ finalResult.push({
+ Name: '全部',
+ Location: 'unclassified',
+ CreationDate: new Date().getTime()
+ })
+ return finalResult
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { bucketConfig: { Location: albumHash }, cancelToken } = configMap
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ if (albumHash !== 'unclassified') {
+ res = await got(
+ `${this.baseUrl}/account/${this.userName}/album/${albumHash}`,
+ getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (res.statusCode === 200 && res.body.success) {
+ res.body.data.images.forEach((item: any) => {
+ result.fullList.push(this.formatFile(item))
+ })
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ } else {
+ let initPage = 0
+ do {
+ res = await got(
+ `${this.baseUrl}/account/${this.userName}/images/${initPage}`,
+ getOptions('GET', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ ) as any
+ if (res.statusCode === 200 && res.body.success) {
+ res.body.data.forEach((item: any) => {
+ result.fullList.push(this.formatFile(item))
+ })
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ initPage++
+ } while (res.body.data.length > 0)
+ }
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { DeleteHash: deleteHash } = configMap
+ const res = await got(
+ `${this.baseUrl}/account/${this.userName}/image/${deleteHash}`,
+ getOptions('DELETE', this.tokenHeaders, undefined, 'json', undefined, undefined, this.proxy)
+ ) as any
+ return res.statusCode === 200 && res.body.success
+ }
+
+ /**
+ * 上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ fileArray.forEach((item: any) => {
+ item.key = item.key.replace(/^\/+/, '')
+ })
+ for (const item of fileArray) {
+ const { bucketName, region: albumHash, key, fileName, filePath, fileSize } = item
+ const id = `${albumHash}-${key}-${filePath}`
+ if (instance.getUploadTask(id) || fileSize > 1024 * 1024 * 200) {
+ continue
+ }
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: albumHash
+ })
+ const form = new FormData()
+ form.append('type', 'file')
+ form.append('description', 'uploaded by PicList')
+ form.append('name', path.basename(key, path.extname(key)))
+ if (fileSize > 1024 * 1024 * 10) {
+ form.append('video', fs.createReadStream(filePath), {
+ filename: path.basename(key),
+ contentType: getFileMimeType(fileName)
+ })
+ } else {
+ form.append('image', fs.createReadStream(filePath), {
+ filename: path.basename(key),
+ contentType: getFileMimeType(fileName)
+ })
+ }
+ albumHash !== 'unclassified' && form.append('album', albumHash)
+ const headers = form.getHeaders()
+ headers.Authorization = this.accessToken
+ gotUpload(
+ instance,
+ `${this.baseUrl}/image`,
+ 'POST',
+ form,
+ headers,
+ id,
+ this.logger,
+ 30000,
+ false,
+ getAgent(this.proxy)
+ )
+ }
+ return true
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName, githubUrl: url } = item
+ const id = `${bucketName}-${region}-${key}-${fileName}`
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ gotDownload(
+ instance,
+ url,
+ fileStream,
+ id,
+ savedFilePath,
+ this.logger,
+ undefined,
+ getAgent(this.proxy)
+ )
+ }
+ return true
+ }
+}
+
+export default ImgurApi
diff --git a/src/main/manage/apis/qiniu.ts b/src/main/manage/apis/qiniu.ts
new file mode 100644
index 0000000..53db286
--- /dev/null
+++ b/src/main/manage/apis/qiniu.ts
@@ -0,0 +1,655 @@
+import axios from 'axios'
+import { hmacSha1Base64, getFileMimeType, gotDownload, formatError } from '../utils/common'
+import fs from 'fs-extra'
+import qiniu from 'qiniu/index'
+import path from 'path'
+import { isImage } from '~/renderer/manage/utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import UpDownTaskQueue,
+{
+ uploadTaskSpecialStatus,
+ commonTaskStatus
+} from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+
+class QiniuApi {
+ mac: qiniu.auth.digest.Mac
+ accessKey: string
+ secretKey: string
+ commonType = 'application/x-www-form-urlencoded'
+ host = 'uc.qiniuapi.com'
+ logger: ManageLogger
+
+ hostList = {
+ getBucketList: 'https://uc.qiniuapi.com/buckets',
+ getBucketDomain: 'https://uc.qiniuapi.com/v2/domains'
+ }
+
+ constructor (accessKey: string, secretKey: string, logger: ManageLogger) {
+ this.mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
+ this.accessKey = accessKey
+ this.secretKey = secretKey
+ this.logger = logger
+ }
+
+ formatFolder (item: string, slicedPrefix: string) {
+ return {
+ Key: item,
+ key: item,
+ fileSize: 0,
+ fileName: item.replace(slicedPrefix, '').replace('/', ''),
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false
+ }
+ }
+
+ formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
+ return {
+ ...item,
+ fileName: item.key.replace(slicedPrefix, ''),
+ url: `${urlPrefix}/${item.key}`,
+ fileSize: item.fsize,
+ formatedTime: new Date(parseInt(item.putTime.toString().slice(0, -7), 10)).toLocaleString(),
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(item.key.replace(slicedPrefix, ''))
+ }
+ }
+
+ authorization (
+ method: string,
+ urlPath: string,
+ host: string,
+ body: string,
+ query: string,
+ contentType: string,
+ xQiniuHeaders?: IStringKeyMap
+ ) {
+ let signStr = `${method.toUpperCase()} ${urlPath}`
+ query && (signStr += `?${query}`)
+ signStr += `\nHost: ${host}`
+ contentType && (signStr += `\nContent-Type: ${contentType}`)
+ let xQiniuHeaderStr = ''
+ if (xQiniuHeaders) {
+ const xQiniuHeaderKeys = Object.keys(xQiniuHeaders).sort()
+ xQiniuHeaderKeys.forEach((key) => {
+ xQiniuHeaderStr += `\n${key}:${xQiniuHeaders[key]}`
+ })
+ signStr += xQiniuHeaderStr
+ }
+ signStr += '\n\n'
+ if (contentType !== 'application/octet-stream' && body) {
+ signStr += body
+ }
+ return `Qiniu ${this.accessKey}:${hmacSha1Base64(this.secretKey, signStr).replace(/\+/g, '-').replace(/\//g, '_')}`
+ }
+
+ /**
+ * 获取存储桶列表
+ */
+ async getBucketList (): Promise {
+ const host = this.hostList.getBucketList
+ const authorization = qiniu.util.generateAccessToken(this.mac, host, undefined)
+ const res = await axios.get(host, {
+ headers: {
+ Authorization: authorization,
+ 'Content-Type': this.commonType
+ },
+ timeout: 10000
+ })
+ if (res && res.status === 200) {
+ if (res.data && res.data.length) {
+ const result = [] as any[]
+ for (let i = 0; i < res.data.length; i++) {
+ const info = await this.getBucketInfo({ bucketName: res.data[i] })
+ if (!info.success) {
+ return []
+ }
+ result.push({
+ Name: res.data[i],
+ Location: info.zone,
+ CreationDate: new Date().toISOString(),
+ Private: info.private
+ })
+ }
+ return result
+ } else {
+ return []
+ }
+ } else {
+ return []
+ }
+ }
+
+ /**
+ * 获取存储桶详细信息
+ */
+ async getBucketInfo (param: IStringKeyMap): Promise {
+ const { bucketName } = param
+ const urlPath = `/v2/bucketInfo?bucket=${bucketName}&fs=true`
+ const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
+ const res = await axios({
+ method: 'post',
+ url: `https://${this.host}/v2/bucketInfo`,
+ params: {
+ bucket: bucketName,
+ fs: true
+ },
+ headers: {
+ Authorization: authorization,
+ 'Content-Type': 'application/json',
+ Host: this.host
+ },
+ timeout: 10000
+ })
+ if (res && res.status === 200) {
+ return {
+ success: true,
+ private: res.data.private,
+ zone: res.data.zone
+ }
+ } else {
+ return {
+ success: false
+ }
+ }
+ }
+
+ /**
+ * 获取自定义域名
+ */
+ async getBucketDomain (param: IStringKeyMap): Promise {
+ const { bucketName } = param
+ const host = this.hostList.getBucketDomain
+ const authorization = qiniu.util.generateAccessToken(this.mac, `${host}?tbl=${bucketName}`, undefined)
+ const res = await axios.get(host, {
+ params: {
+ tbl: bucketName
+ },
+ headers: {
+ Authorization: authorization,
+ 'Content-Type': this.commonType
+ },
+ timeout: 10000
+ })
+ if (res && res.status === 200) {
+ return res.data && res.data.length ? res.data : []
+ } else {
+ return []
+ }
+ }
+
+ /**
+ * 修改存储桶权限
+ */
+ async setBucketAclPolicy (param: IStringKeyMap): Promise {
+ // 0: 公开访问 1: 私有访问
+ const { bucketName } = param
+ let { isPrivate } = param
+ isPrivate = isPrivate ? 1 : 0
+ const urlPath = `/private?bucket=${bucketName}&private=${isPrivate}`
+ const authorization = this.authorization('POST', urlPath, this.host, '', '', this.commonType)
+ const res = await axios({
+ method: 'post',
+ url: `https://${this.host}/private`,
+ params: {
+ bucket: bucketName,
+ private: isPrivate
+ },
+ headers: {
+ Authorization: authorization,
+ 'Content-Type': this.commonType,
+ Host: this.host
+ },
+ timeout: 10000
+ })
+ return res && res.status === 200
+ }
+
+ /**
+ * 创建存储桶
+ * @param {Object} configMap
+ * configMap = {
+ * BucketName: string,
+ * region: string,
+ * acl: boolean // 是否公开访问
+ * }
+ */
+ async createBucket (configMap: IStringKeyMap): Promise {
+ const { BucketName, region } = configMap
+ const { acl } = configMap
+ const urlPath = `/mkbucketv3/${BucketName}/region/${region}`
+ const authorization = this.authorization('POST', urlPath, this.host, '', '', 'application/json')
+ const res = await axios({
+ method: 'post',
+ url: `https://${this.host}${urlPath}`,
+ headers: {
+ Authorization: authorization,
+ 'Content-Type': 'application/json',
+ Host: this.host
+ },
+ timeout: 10000
+ })
+ if (res && res.status === 200) {
+ const changeAclRes = await this.setBucketAclPolicy({
+ bucketName: BucketName,
+ isPrivate: !acl
+ })
+ return changeAclRes
+ } else {
+ return false
+ }
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { bucketName: bucket, prefix, cancelToken, customUrl: urlPrefix } = configMap
+ let marker = undefined as any
+ const slicedPrefix = prefix.slice(1)
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ const config = new qiniu.conf.Config()
+ const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
+ do {
+ res = await new Promise((resolve, reject) => {
+ bucketManager.listPrefix(bucket, {
+ prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ delimiter: '/',
+ marker,
+ limit: 1000
+ }, (err: any, respBody: any, respInfo: any) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ })
+ if (res && res.respInfo.statusCode === 200) {
+ res.respBody && res.respBody.commonPrefixes && res.respBody.commonPrefixes.forEach((item: any) => {
+ result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ 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)
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ marker = res.respBody.marker
+ } while (res.respBody && res.respBody.marker && !cancelTask[0])
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 获取文件列表
+ * @param {Object} configMap
+ * configMap = {
+ * bucketName: string,
+ * bucketConfig: {
+ * Location: string
+ * },
+ * paging: boolean,
+ * prefix: string,
+ * marker: string,
+ * itemsPerPage: number,
+ * customUrl: string
+ * }
+ */
+ async getBucketFileList (configMap: IStringKeyMap): Promise {
+ const { bucketName: bucket, prefix, marker, itemsPerPage, customUrl: urlPrefix } = configMap
+ const slicedPrefix = prefix.slice(1)
+ const config = new qiniu.conf.Config()
+ const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
+ let res = {} as any
+ const result = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ res = await new Promise((resolve, reject) => {
+ bucketManager.listPrefix(bucket, {
+ limit: itemsPerPage,
+ prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ marker,
+ delimiter: '/'
+ }, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ })
+ if (res && res.respInfo.statusCode === 200) {
+ if (res.respBody && res.respBody.commonPrefixes) {
+ res.respBody.commonPrefixes.forEach((item: string) => {
+ result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ }
+ if (res.respBody && res.respBody.items) {
+ res.respBody.items.forEach((item: any) => {
+ item.fsize !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
+ })
+ }
+ result.isTruncated = !!(res.respBody && res.respBody.marker)
+ result.nextMarker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
+ result.success = true
+ return result
+ } else {
+ return result
+ }
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ 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) => {
+ bucketManager.delete(bucketName, key, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ if (res && res.respInfo.statusCode === 200) {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * 删除文件夹
+ * @param configMap
+ */
+ async deleteBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, key } = configMap
+ const config = new qiniu.conf.Config()
+ const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
+ let marker = ''
+ let isTruncated = true
+ const allFileList = {
+ Contents: [] as any[]
+ }
+ do {
+ const res = await new Promise((resolve, reject) => {
+ bucketManager.listPrefix(bucketName, {
+ prefix: key,
+ marker,
+ limit: 1000
+ }, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ if (res && res.respInfo.statusCode === 200) {
+ if (res.respBody && res.respBody.items) {
+ allFileList.Contents = allFileList.Contents.concat(res.respBody.items)
+ }
+ isTruncated = !!(res.respBody && res.respBody.marker)
+ marker = res.respBody && res.respBody.marker ? res.respBody.marker : ''
+ } else {
+ return false
+ }
+ } while (isTruncated)
+ const cycleNum = Math.ceil(allFileList.Contents.length / 1000)
+ for (let i = 0; i < cycleNum; i++) {
+ 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) => {
+ bucketManager.batch(deleteOps, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ if (!(res && res.respInfo.statusCode === 200)) {
+ return false
+ }
+ }
+ return true
+ }
+
+ /**
+ * 重命名文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * oldKey: string,
+ * newKey: string
+ * }
+ */
+ async renameBucketFile (configMap: IStringKeyMap): Promise {
+ 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, {
+ force: true
+ }, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ return res && res.respInfo.statusCode === 200
+ }
+
+ /**
+ * 获取预签名url
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string,
+ * expires: number,
+ * customUrl: string
+ * }
+ */
+ async getPreSignedUrl (configMap: IStringKeyMap): Promise {
+ const { key, expires, customUrl } = configMap
+ const config = new qiniu.conf.Config()
+ const bucketManager = new qiniu.rs.BucketManager(this.mac, config)
+ const urlPrefix = customUrl
+ const expiration = parseInt(Date.now() / 1000 + expires)
+ const res = bucketManager.privateDownloadUrl(urlPrefix, key, expiration)
+ return res
+ }
+
+ /**
+ * 上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ fileArray.forEach((item: any) => {
+ item.key = item.key.replace(/^\/+/, '')
+ })
+ for (const item of fileArray) {
+ const { bucketName, region, key, filePath, fileName } = item
+ instance.addUploadTask({
+ id: `${bucketName}-${region}-${key}-${filePath}`,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: region
+ })
+ const config = new qiniu.conf.Config()
+ const resumeUploader = new qiniu.resume_up.ResumeUploader(config)
+ const putExtra = new qiniu.resume_up.PutExtra()
+ const uploadToken = new qiniu.rs.PutPolicy({
+ scope: `${bucketName}:${key}`,
+ expires: 36000
+ }).uploadToken(this.mac)
+ putExtra.fname = key
+ putExtra.params = {}
+ putExtra.mimeType = getFileMimeType(fileName)
+ putExtra.version = 'v2'
+ putExtra.partSize = 4 * 1024 * 1024
+ putExtra.progressCallback = (uploadBytes, totalBytes) => {
+ const progress = Math.floor(uploadBytes / totalBytes * 100)
+ instance.updateUploadTask({
+ id: `${bucketName}-${region}-${key}-${filePath}`,
+ progress,
+ status: uploadTaskSpecialStatus.uploading
+ })
+ }
+ resumeUploader.putFile(uploadToken, key, filePath, putExtra, (respErr, respBody, respInfo) => {
+ if (respErr) {
+ this.logger.error(formatError(respErr, { class: 'Qiniu', method: 'uploadBucketFile' }))
+ instance.updateUploadTask({
+ id: `${bucketName}-${region}-${key}-${filePath}`,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ finishTime: new Date().toLocaleString()
+ })
+ return
+ }
+ if (respInfo.statusCode === 200) {
+ instance.updateUploadTask({
+ id: `${bucketName}-${region}-${key}-${filePath}`,
+ progress: 100,
+ status: uploadTaskSpecialStatus.uploaded,
+ response: JSON.stringify(respBody),
+ finishTime: new Date().toLocaleString()
+ })
+ } else {
+ instance.updateUploadTask({
+ id: `${bucketName}-${region}-${key}-${filePath}`,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ finishTime: new Date().toLocaleString()
+ })
+ }
+ })
+ }
+ return true
+ }
+
+ /**
+ * 新建文件夹
+ * @param configMap
+ */
+ async createBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, key } = configMap
+ const putPolicy = new qiniu.rs.PutPolicy({
+ scope: `${bucketName}:${key}`
+ })
+ 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) => {
+ FormUploader.put(uploadToken, key, '', putExtra, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ if (res && res.respInfo.statusCode === 200) {
+ return true
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName, customUrl } = item
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ const id = `${bucketName}-${region}-${key}`
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ const preSignedUrl = await this.getPreSignedUrl({ key, expires: 36000, customUrl })
+ gotDownload(instance, preSignedUrl, fileStream, id, savedFilePath, this.logger)
+ }
+ return true
+ }
+}
+
+export default QiniuApi
diff --git a/src/main/manage/apis/smms.ts b/src/main/manage/apis/smms.ts
new file mode 100644
index 0000000..8696bec
--- /dev/null
+++ b/src/main/manage/apis/smms.ts
@@ -0,0 +1,248 @@
+import { isImage } from '@/manage/utils/common'
+import axios, { AxiosInstance } from 'axios'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import FormData from 'form-data'
+import fs from 'fs-extra'
+import { getFileMimeType, gotUpload, gotDownload } from '../utils/common'
+import path from 'path'
+import UpDownTaskQueue, { commonTaskStatus } from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+
+class SmmsApi {
+ baseUrl = 'https://smms.app/api/v2'
+ token: string
+ axiosInstance: AxiosInstance
+ logger: ManageLogger
+
+ constructor (token: string, logger: ManageLogger) {
+ this.token = token
+ this.axiosInstance = axios.create({
+ baseURL: this.baseUrl,
+ timeout: 30000,
+ headers: {
+ Authorization: this.token
+ }
+ })
+ this.logger = logger
+ }
+
+ formatFile (item: any) {
+ return {
+ ...item,
+ Key: item.path,
+ key: item.path,
+ fileName: item.filename,
+ fileSize: item.size,
+ formatedTime: new Date(item.created_at).toLocaleString(),
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(item.storename),
+ sha: item.hash,
+ downloadUrl: item.url
+ }
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { cancelToken } = configMap
+ let marker = 1
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ do {
+ res = await this.axiosInstance(
+ '/upload_history',
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ },
+ params: {
+ page: marker
+ }
+ })
+ if (res && res.status === 200 && res.data && res.data.success) {
+ if (res.data.Count === 0) {
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ } else {
+ res.data.data.forEach((item: any) => {
+ result.fullList.push(this.formatFile(item))
+ })
+ window.webContents.send('refreshFileTransferList', result)
+ }
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ marker++
+ } while (!cancelTask[0] && res && res.status === 200 && res.data && res.data.success && res.data.CurrentPage < res.data.TotalPages)
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 获取文件列表
+ * @param {Object} configMap
+ * configMap = {
+ * bucketName: string,
+ * bucketConfig: {
+ * Location: string
+ * },
+ * paging: boolean,
+ * prefix: string,
+ * marker: string,
+ * itemsPerPage: number,
+ * customUrl: string
+ * }
+ */
+ async getBucketFileList (configMap: IStringKeyMap): Promise {
+ const { currentPage } = configMap
+ let res = {} as any
+ const result = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ res = await this.axiosInstance(
+ '/upload_history',
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ },
+ params: {
+ page: currentPage
+ }
+ }
+ )
+ if (res && res.status === 200 && res.data && res.data.success) {
+ if (res.data.Count === 0) {
+ result.success = true
+ return result
+ }
+ res.data.data.forEach((item: any) => {
+ result.fullList.push(this.formatFile(item))
+ })
+ result.isTruncated = res.data.CurrentPage < res.data.TotalPages
+ result.nextMarker = res.data.CurrentPage + 1
+ result.success = true
+ return result
+ } else {
+ return result
+ }
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string,
+ * DeleteHash: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { DeleteHash } = configMap
+ const params = {
+ hash: DeleteHash,
+ format: 'json'
+ }
+ const res = await this.axiosInstance(
+ `/delete/${DeleteHash}`,
+ {
+ method: 'GET',
+ params
+ }
+ )
+ return res && res.status === 200 && res.data && res.data.success
+ }
+
+ /**
+ * 上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, filePath, fileName } = item
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ if (instance.getUploadTask(id)) {
+ continue
+ }
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: region
+ })
+ const form = new FormData()
+ form.append('format', 'json')
+ form.append('smfile', fs.createReadStream(filePath), {
+ filename: path.basename(fileName),
+ contentType: getFileMimeType(fileName)
+ })
+ const headers = form.getHeaders()
+ headers.Authorization = this.token
+ const url = `${this.baseUrl}/upload`
+ gotUpload(instance, url, 'POST', form, headers, id, this.logger)
+ }
+ return true
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName, downloadUrl: preSignedUrl } = item
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ const id = `${bucketName}-${region}-${key}`
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ gotDownload(instance, preSignedUrl, fileStream, id, savedFilePath, this.logger)
+ }
+ return true
+ }
+}
+
+export default SmmsApi
diff --git a/src/main/manage/apis/tcyun.ts b/src/main/manage/apis/tcyun.ts
new file mode 100644
index 0000000..4ec8c00
--- /dev/null
+++ b/src/main/manage/apis/tcyun.ts
@@ -0,0 +1,523 @@
+import COS from 'cos-nodejs-sdk-v5'
+import fs from 'fs-extra'
+import path from 'path'
+import { isImage } from '~/renderer/manage/utils/common'
+import { handleUrlEncode } from '~/universal/utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import { formatError, getFileMimeType } from '../utils/common'
+import UpDownTaskQueue,
+{
+ uploadTaskSpecialStatus,
+ commonTaskStatus,
+ downloadTaskSpecialStatus
+} from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+
+class TcyunApi {
+ ctx: COS
+ logger: ManageLogger
+
+ constructor (secretId: string, secretKey: string, logger: ManageLogger) {
+ this.ctx = new COS({
+ SecretId: secretId,
+ SecretKey: secretKey
+ })
+ this.logger = logger
+ }
+
+ formatFolder (item: {Prefix: string}, slicedPrefix: string): any {
+ return {
+ ...item,
+ key: item.Prefix,
+ fileSize: 0,
+ formatedTime: '',
+ fileName: item.Prefix.replace(slicedPrefix, '').replace('/', ''),
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false
+ }
+ }
+
+ formatFile (item: COS.CosObject, slicedPrefix: string, urlPrefix: string): any {
+ return {
+ ...item,
+ key: item.Key,
+ fileName: item.Key.replace(slicedPrefix, ''),
+ fileSize: parseInt(item.Size),
+ formatedTime: new Date(item.LastModified).toLocaleString(),
+ isDir: false,
+ checked: false,
+ isImage: isImage(item.Key),
+ match: false,
+ url: `${urlPrefix}/${item.Key}`
+ }
+ }
+
+ /**
+ * 获取存储桶列表
+ */
+ async getBucketList (): Promise {
+ const res = await this.ctx.getService({})
+ return res && res.Buckets ? res.Buckets : []
+ }
+
+ /**
+ * 获取自定义域名
+ */
+ async getBucketDomain (param: IStringKeyMap): Promise {
+ const { bucketName, region } = param
+ const res = await this.ctx.getBucketDomain({
+ Bucket: bucketName,
+ Region: region
+ })
+ const result = [] as string[]
+ if (res && res.statusCode === 200) {
+ if (res.DomainRule && res.DomainRule.length > 0) {
+ res.DomainRule.forEach((item: any) => {
+ if (item.Status === 'ENABLED') {
+ result.push(item.Name)
+ }
+ })
+ return result
+ } else {
+ return []
+ }
+ } else {
+ return []
+ }
+ }
+
+ /**
+ * 创建存储桶
+ * @param {Object} configMap
+ * configMap = {
+ * BucketName: string,
+ * region: string,
+ * acl: string
+ * }
+ * @description
+ * acl: private | publicRead | publicReadWrite
+ */
+ async createBucket (configMap: IStringKeyMap): Promise < boolean > {
+ const aclTransMap: IStringKeyMap = {
+ private: 'private',
+ publicRead: 'public-read',
+ publicReadWrite: 'public-read-write'
+ }
+ const res = await this.ctx.putBucket({
+ ACL: aclTransMap[configMap.acl],
+ Bucket: configMap.BucketName,
+ Region: configMap.region
+ })
+ return res && res.statusCode === 200
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise < any > {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const bucket = configMap.bucketName
+ const region = configMap.bucketConfig.Location
+ const prefix = configMap.prefix as string
+ const slicedPrefix = prefix.slice(1, prefix.length)
+ const urlPrefix = configMap.customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
+ let marker
+ const cancelToken = configMap.cancelToken as string
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as COS.GetBucketResult
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ do {
+ res = await this.ctx.getBucket({
+ Bucket: bucket,
+ Region: region,
+ Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ Delimiter: '/',
+ Marker: marker
+ })
+ if (res && res.statusCode === 200) {
+ res.CommonPrefixes.forEach((item: { Prefix: string}) =>
+ result.fullList.push(this.formatFolder(item, slicedPrefix)))
+ res.Contents.forEach((item: COS.CosObject) =>
+ parseInt(item.Size) !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)))
+ window.webContents.send('refreshFileTransferList', result)
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ marker = res.NextMarker
+ } while (res.IsTruncated === 'true' && !cancelTask[0])
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 获取文件列表
+ * @param {Object} configMap
+ * configMap = {
+ * bucketName: string,
+ * bucketConfig: {
+ * Location: string
+ * },
+ * paging: boolean,
+ * prefix: string,
+ * marker: string,
+ * itemsPerPage: number,
+ * customUrl: string
+ * }
+ */
+ async getBucketFileList (configMap: IStringKeyMap): Promise {
+ const bucket = configMap.bucketName
+ const region = configMap.bucketConfig.Location
+ const prefix = configMap.prefix as string
+ const slicedPrefix = prefix.slice(1)
+ const urlPrefix = configMap.customUrl || `https://${bucket}.cos.${region}.myqcloud.com`
+ const marker = configMap.marker as string
+ const itemsPerPage = configMap.itemsPerPage as number
+ let res = {} as COS.GetBucketResult
+ const result = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ res = await this.ctx.getBucket({
+ Bucket: bucket,
+ Region: region,
+ Prefix: slicedPrefix === '' ? undefined : slicedPrefix,
+ Delimiter: '/',
+ Marker: marker,
+ MaxKeys: itemsPerPage
+ })
+ if (res && res.statusCode === 200) {
+ res.CommonPrefixes.forEach((item: { Prefix: string}) =>
+ result.fullList.push(this.formatFolder(item, slicedPrefix)))
+ res.Contents.forEach((item: COS.CosObject) =>
+ parseInt(item.Size) !== 0 && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix)))
+ result.isTruncated = res.IsTruncated === 'true'
+ result.nextMarker = res.NextMarker || ''
+ result.success = true
+ return result
+ } else {
+ return result
+ }
+ }
+
+ /**
+ * 重命名文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * oldKey: string,
+ * newKey: string
+ * }
+ */
+ async renameBucketFile (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, oldKey, newKey } = configMap
+ const res = await this.ctx.putObjectCopy({
+ Bucket: bucketName,
+ Region: region,
+ Key: newKey,
+ CopySource: handleUrlEncode(`${bucketName}.cos.${region}.myqcloud.com/${oldKey}`)
+ })
+ if (res && res.statusCode === 200) {
+ const res2 = await this.ctx.deleteObject({
+ Bucket: bucketName,
+ Region: region,
+ Key: oldKey
+ })
+ return res2 && res2.statusCode === 204
+ } else {
+ return false
+ }
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ const res = await this.ctx.deleteObject({
+ Bucket: bucketName,
+ Region: region,
+ Key: key
+ })
+ return res && res.statusCode === 204
+ }
+
+ /**
+ * 删除文件夹
+ * @param configMap
+ */
+ async deleteBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ let marker
+ let isTruncated
+ const allFileList = {
+ CommonPrefixes: [] as any[],
+ Contents: [] as any[]
+ }
+ let res = await this.ctx.getBucket({
+ Bucket: bucketName,
+ Region: region,
+ Prefix: key,
+ Delimiter: '/',
+ MaxKeys: 1000
+ })
+ if (res && res.statusCode === 200) {
+ res.CommonPrefixes.length > 0 && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
+ res.Contents.length > 0 && allFileList.Contents.push(...res.Contents)
+ isTruncated = res.IsTruncated
+ marker = res.NextMarker
+ while (isTruncated === 'true') {
+ res = await this.ctx.getBucket({
+ Bucket: bucketName,
+ Region: region,
+ Prefix: key,
+ Delimiter: '/',
+ Marker: marker,
+ MaxKeys: 1000
+ }) as any
+ if (res && res.statusCode === 200) {
+ res.CommonPrefixes.length > 0 && allFileList.CommonPrefixes.push(...res.CommonPrefixes)
+ res.Contents.length > 0 && allFileList.Contents.push(...res.Contents)
+ isTruncated = res.IsTruncated
+ marker = res.NextMarker
+ } else {
+ return false
+ }
+ }
+ } else {
+ return false
+ }
+ if (allFileList.CommonPrefixes.length > 0) {
+ for (const item of allFileList.CommonPrefixes) {
+ res = await this.deleteBucketFolder({
+ bucketName,
+ region,
+ key: item.Prefix
+ }) as any
+ if (!res) {
+ return false
+ }
+ }
+ }
+ if (allFileList.Contents.length > 0) {
+ const cycle = Math.ceil(allFileList.Contents.length / 1000)
+ for (let i = 0; i < cycle; i++) {
+ res = await this.ctx.deleteMultipleObject({
+ Bucket: bucketName,
+ Region: region,
+ Objects: allFileList.Contents.slice(i * 1000, (i + 1) * 1000).map((item: any) => {
+ return {
+ Key: item.Key
+ }
+ })
+ }) as any
+ if (!(res && res.statusCode === 200)) {
+ return false
+ }
+ }
+ }
+ return true
+ }
+
+ /**
+ * 获取预签名url
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string,
+ * expires: number,
+ * customUrl: string
+ * }
+ */
+ async getPreSignedUrl (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key, expires, customUrl } = configMap
+ 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
+ }
+
+ /**
+ * 高级上传文件
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ // fileArray = [{
+ // bucketName: string,
+ // region: string,
+ // key: string,
+ // filePath: string
+ // fileSize: number
+ // }]
+ const instance = UpDownTaskQueue.getInstance()
+ const files = [] as any[]
+ for (const item of fileArray) {
+ const { bucketName, region, key, filePath, fileSize, fileName } = item
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ if (instance.getUploadTask(id)) {
+ continue
+ }
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: region
+ })
+ files.push({
+ Bucket: bucketName,
+ Region: region,
+ Key: key,
+ FilePath: filePath,
+ ContentType: getFileMimeType(filePath),
+ Body: fileSize > 1048576 ? fs.createReadStream(filePath) : undefined,
+ onProgress: (progress: any) => {
+ const cancelToken = ''
+ instance.updateUploadTask({
+ id,
+ progress: Math.floor(progress.percent * 100),
+ status: uploadTaskSpecialStatus.uploading,
+ cancelToken
+ })
+ },
+ onFileFinish: (err: any, data: any) => {
+ if (data) {
+ instance.updateUploadTask({
+ id,
+ progress: 100,
+ status: uploadTaskSpecialStatus.uploaded,
+ response: typeof data === 'object' ? JSON.stringify(data) : String(data),
+ finishTime: new Date().toLocaleString()
+ })
+ } else {
+ this.logger.error(formatError(err, { method: 'uploadBucketFile', class: 'TcyunApi' }))
+ instance.updateUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ response: typeof err === 'object' ? JSON.stringify(err) : String(err),
+ finishTime: new Date().toLocaleString()
+ })
+ }
+ }
+ })
+ this.ctx.uploadFiles({
+ files
+ })
+ }
+ return true
+ }
+
+ /**
+ * 新建文件夹
+ * @param configMap
+ */
+ async createBucketFolder (configMap: IStringKeyMap): Promise {
+ const { bucketName, region, key } = configMap
+ const res = await this.ctx.putObject({
+ Bucket: bucketName,
+ Region: region,
+ Key: key,
+ Body: ''
+ })
+ return res && res.statusCode === 200
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ // fileArray = [{
+ // bucketName: string,
+ // region: string,
+ // key: string,
+ // fileName: string
+ // }]
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName } = item
+ const id = `${bucketName}-${region}-${key}`
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: path.join(downloadPath, fileName)
+ })
+ this.ctx.downloadFile({
+ Bucket: bucketName,
+ Region: region,
+ Key: key,
+ RetryTimes: 3,
+ ChunkSize: 1024 * 1024 * 1,
+ FilePath: path.join(downloadPath, fileName),
+ onProgress: (progress: any) => {
+ instance.updateDownloadTask({
+ id,
+ progress: Math.floor(progress.percent * 100),
+ status: downloadTaskSpecialStatus.downloading
+ })
+ }
+ }).then((res: any) => {
+ instance.updateDownloadTask({
+ id,
+ progress: res && res.statusCode === 200 ? 100 : 0,
+ status: res && res.statusCode === 200 ? downloadTaskSpecialStatus.downloaded : commonTaskStatus.failed,
+ 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' }))
+ instance.updateDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ response: typeof err === 'object' ? JSON.stringify(err) : String(err),
+ finishTime: new Date().toLocaleString()
+ })
+ })
+ }
+ return true
+ }
+}
+
+export default TcyunApi
diff --git a/src/main/manage/apis/upyun.ts b/src/main/manage/apis/upyun.ts
new file mode 100644
index 0000000..9f0f594
--- /dev/null
+++ b/src/main/manage/apis/upyun.ts
@@ -0,0 +1,388 @@
+// @ts-ignore
+import Upyun from 'upyun'
+import { md5, hmacSha1Base64, getFileMimeType, gotDownload, gotUpload } from '../utils/common'
+import { isImage } from '~/renderer/manage/utils/common'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+import axios from 'axios'
+import FormData from 'form-data'
+import fs from 'fs-extra'
+import path from 'path'
+import UpDownTaskQueue,
+{
+ commonTaskStatus
+} from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+
+class UpyunApi {
+ ser: Upyun.Service
+ cli: Upyun.Client
+ bucket: string
+ operator: string
+ password: string
+ stopMarker = 'g2gCZAAEbmV4dGQAA2VvZg'
+ logger: ManageLogger
+
+ constructor (bucket: string, operator: string, password: string, logger: ManageLogger) {
+ this.ser = new Upyun.Service(bucket, operator, password)
+ this.cli = new Upyun.Client(this.ser)
+ this.bucket = bucket
+ this.operator = operator
+ this.password = password
+ this.logger = logger
+ }
+
+ formatFolder (item: any, slicedPrefix: string) {
+ return {
+ ...item,
+ key: `${slicedPrefix}${item.name}/`,
+ fileSize: 0,
+ formatedTime: '',
+ fileName: item.name,
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false,
+ Key: `${slicedPrefix}${item.name}/`
+ }
+ }
+
+ formatFile (item: any, slicedPrefix: string, urlPrefix: string) {
+ return {
+ ...item,
+ fileName: item.name,
+ fileSize: item.size,
+ formatedTime: new Date(parseInt(item.time) * 1000).toLocaleString(),
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(item.name),
+ url: `${urlPrefix}/${slicedPrefix}${item.name}`,
+ key: `${slicedPrefix}${item.name}`
+ }
+ }
+
+ authorization (
+ method: string,
+ uri: string,
+ contentMd5: string,
+ operator: string,
+ password: string
+ ) {
+ const passwordMd5 = md5(password, 'hex')
+ const date = new Date().toUTCString()
+ const upperMethod = method.toUpperCase()
+ let stringToSign = ''
+ const codedUri = encodeURI(uri)
+ if (contentMd5 === '') {
+ stringToSign = `${upperMethod}&${codedUri}&${date}`
+ } else {
+ stringToSign = `${upperMethod}&${codedUri}&${date}&${contentMd5}`
+ }
+ const signature = hmacSha1Base64(passwordMd5, stringToSign)
+ return `UPYUN ${operator}:${signature}`
+ }
+
+ /**
+ * 获取空间列表
+ */
+ async getBucketList (): Promise {
+ return this.bucket
+ }
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { bucketName: bucket, prefix, cancelToken } = configMap
+ const slicedPrefix = prefix.slice(1)
+ const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
+ let marker = ''
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ do {
+ res = await this.cli.listDir(prefix, {
+ limit: 10000,
+ iter: marker
+ })
+ if (res) {
+ res.files && res.files.forEach((item: any) => {
+ item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
+ item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ window.webContents.send('refreshFileTransferList', result)
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ marker = res.next
+ } while (!cancelTask[0] && res.next !== this.stopMarker)
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+
+ /**
+ * 获取文件列表
+ * @param {Object} configMap
+ * configMap = {
+ * bucketName: string,
+ * bucketConfig: {
+ * Location: string
+ * },
+ * paging: boolean,
+ * prefix: string,
+ * marker: string,
+ * itemsPerPage: number,
+ * customUrl: string
+ * }
+ */
+ async getBucketFileList (configMap: IStringKeyMap): Promise {
+ const { bucketName: bucket, prefix, marker, itemsPerPage } = configMap
+ const slicedPrefix = prefix.slice(1)
+ const urlPrefix = configMap.customUrl || `http://${bucket}.test.upcdn.net`
+ let res = {} as any
+ const result = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ res = await this.cli.listDir(prefix, {
+ limit: itemsPerPage,
+ iter: marker || ''
+ })
+ if (res) {
+ res.files && res.files.forEach((item: any) => {
+ item.type === 'N' && result.fullList.push(this.formatFile(item, slicedPrefix, urlPrefix))
+ item.type === 'F' && result.fullList.push(this.formatFolder(item, slicedPrefix))
+ })
+ result.isTruncated = res.next !== this.stopMarker
+ result.nextMarker = res.next
+ result.success = true
+ return result
+ } else {
+ return result
+ }
+ }
+
+ /**
+ * 重命名文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * oldKey: string,
+ * newKey: string
+ * }
+ */
+ async renameBucketFile (configMap: IStringKeyMap): Promise {
+ const oldKey = configMap.oldKey
+ let newKey = configMap.newKey
+ const method = 'PUT'
+ if (newKey.endsWith('/')) {
+ newKey = newKey.slice(0, -1)
+ }
+ const xUpyunMoveSource = `/${this.bucket}/${oldKey}`
+ const uri = `/${this.bucket}/${newKey}`
+ const authorization = this.authorization(method, uri, '', this.operator, this.password)
+ const headers = {
+ Authorization: authorization,
+ 'X-Upyun-Move-Source': xUpyunMoveSource,
+ 'Content-Length': 0,
+ Date: new Date().toUTCString()
+ }
+ const res = await axios({
+ method,
+ url: `http://v0.api.upyun.com${uri}`,
+ headers
+ })
+ return res.status === 200
+ }
+
+ /**
+ * 删除文件
+ * @param configMap
+ * configMap = {
+ * bucketName: string,
+ * region: string,
+ * key: string
+ * }
+ */
+ async deleteBucketFile (configMap: IStringKeyMap): Promise {
+ const { key } = configMap
+ const res = await this.cli.deleteFile(key)
+ return res
+ }
+
+ /**
+ * delete bucket folder
+ * @param configMap
+ */
+ async deleteBucketFolder (configMap: IStringKeyMap): Promise {
+ const { key } = configMap
+ let marker = ''
+ let isTruncated
+ const allFileList = {
+ CommonPrefixes: [] as any[],
+ Contents: [] as any[]
+ }
+ do {
+ const res = await this.cli.listDir(key, {
+ limit: 10000,
+ iter: marker
+ })
+ if (res) {
+ res.files.forEach((item: any) => {
+ item.type === 'N' && allFileList.Contents.push({
+ ...item,
+ key: `${key}${item.name}`
+ })
+ item.type === 'F' && allFileList.CommonPrefixes.push({
+ ...item,
+ key: `${key}${item.name}/`
+ })
+ })
+ marker = res.next
+ isTruncated = res.next !== this.stopMarker
+ } else {
+ return false
+ }
+ } while (isTruncated)
+ if (allFileList.Contents.length > 0) {
+ let success = false
+ for (let i = 0; i < allFileList.Contents.length; i++) {
+ const item = allFileList.Contents[i]
+ success = await this.cli.deleteFile(item.key)
+ if (!success) {
+ return false
+ }
+ }
+ }
+ if (allFileList.CommonPrefixes.length > 0) {
+ for (const item of allFileList.CommonPrefixes) {
+ const res = await this.deleteBucketFolder({
+ key: item.key
+ })
+ if (!res) {
+ return false
+ }
+ }
+ }
+ const deleteSelf = await this.cli.deleteFile(key)
+ if (!deleteSelf) {
+ return false
+ }
+ return true
+ }
+
+ /**
+ * upload file to bucket
+ * axiso:onUploadProgress not work in nodejs , use got instead
+ * @param configMap
+ */
+ async uploadBucketFile (configMap: IStringKeyMap): Promise {
+ const { fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ fileArray.forEach((item: any) => {
+ item.key = item.key.replace(/^\/+/, '')
+ })
+ for (const item of fileArray) {
+ const { bucketName, region, key, filePath, fileName, fileSize } = item
+ const id = `${bucketName}-${region}-${key}-${filePath}`
+ if (instance.getUploadTask(id)) {
+ continue
+ }
+ instance.addUploadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ sourceFilePath: filePath,
+ targetFilePath: key,
+ targetFileBucket: bucketName,
+ targetFileRegion: region
+ })
+ const date = new Date().toUTCString()
+ const uri = `/${key}`
+ const method = 'POST'
+ const uplpadPolicy = {
+ bucket: bucketName,
+ 'save-key': uri,
+ expiration: Math.floor(Date.now() / 1000) + 2592000,
+ date,
+ 'content-length': fileSize
+ }
+ const base64Policy = Buffer.from(JSON.stringify(uplpadPolicy)).toString('base64')
+ const stringToSign = `${method}&/${bucketName}&${date}&${base64Policy}`
+ const signature = hmacSha1Base64(md5(this.password, 'hex'), stringToSign)
+ const authorization = `UPYUN ${this.operator}:${signature}`
+ const form = new FormData()
+ form.append('policy', base64Policy)
+ form.append('authorization', authorization)
+ form.append('file', fs.createReadStream(filePath), {
+ filename: path.basename(key),
+ contentType: getFileMimeType(fileName)
+ })
+ const headers = form.getHeaders()
+ headers.Host = 'v0.api.upyun.com'
+ headers.Date = date
+ headers.Authorization = authorization
+ gotUpload(instance, `http://v0.api.upyun.com/${bucketName}`, method, form, headers, id, this.logger)
+ }
+ return true
+ }
+
+ /**
+ * 新建文件夹
+ * @param configMap
+ */
+ async createBucketFolder (configMap: IStringKeyMap): Promise {
+ const { key } = configMap
+ const res = await this.cli.makeDir(`/${key}`)
+ return res
+ }
+
+ /**
+ * 下载文件
+ * @param configMap
+ */
+ async downloadBucketFile (configMap: IStringKeyMap): Promise {
+ const { downloadPath, fileArray } = configMap
+ const instance = UpDownTaskQueue.getInstance()
+ for (const item of fileArray) {
+ const { bucketName, region, key, fileName, customUrl } = item
+ const savedFilePath = path.join(downloadPath, fileName)
+ const fileStream = fs.createWriteStream(savedFilePath)
+ const id = `${bucketName}-${region}-${key}`
+ if (instance.getDownloadTask(id)) {
+ continue
+ }
+ instance.addDownloadTask({
+ id: `${bucketName}-${region}-${key}`,
+ progress: 0,
+ status: commonTaskStatus.queuing,
+ sourceFileName: fileName,
+ targetFilePath: savedFilePath
+ })
+ const preSignedUrl = `${customUrl}/${key}`
+ gotDownload(instance, preSignedUrl, fileStream, id, savedFilePath, this.logger)
+ }
+ return true
+ }
+}
+
+export default UpyunApi
diff --git a/src/main/manage/datastore/db.ts b/src/main/manage/datastore/db.ts
new file mode 100644
index 0000000..a48b614
--- /dev/null
+++ b/src/main/manage/datastore/db.ts
@@ -0,0 +1,66 @@
+/* eslint-disable */
+import { JSONStore } from '@picgo/store'
+import { IJSON } from '@picgo/store/dist/types'
+import { ManageApiType, ManageConfigType } from '~/universal/types/manage'
+
+class ManageDB {
+ private readonly ctx: ManageApiType
+ private readonly db: JSONStore
+ constructor (ctx: ManageApiType) {
+ this.ctx = ctx
+ this.db = new JSONStore(this.ctx.configPath)
+ let initParams: IStringKeyMap = {
+ picBed: {},
+ settings: {},
+ currentPicBed: 'placeholder'
+ }
+ for (let key in initParams) {
+ if (!this.db.has(key)) {
+ try {
+ this.db.set(key, initParams[key])
+ } catch (e: any) {
+ this.ctx.logger.error(e)
+ throw e
+ }
+ }
+ }
+ }
+
+ read (flush?: boolean): IJSON {
+ return this.db.read(flush)
+ }
+
+ get (key: string = ''): any {
+ this.read(true)
+ return this.db.get(key)
+ }
+
+ set (key: string, value: any): void {
+ this.read(true)
+ return this.db.set(key, value)
+ }
+
+ has (key: string): boolean {
+ this.read(true)
+ return this.db.has(key)
+ }
+
+ unset (key: string, value: any): boolean {
+ this.read(true)
+ return this.db.unset(key, value)
+ }
+
+ saveConfig (config: Partial): void {
+ Object.keys(config).forEach((name: string) => {
+ this.set(name, config[name])
+ })
+ }
+
+ removeConfig (config: ManageConfigType): void {
+ Object.keys(config).forEach((name: string) => {
+ this.unset(name, config[name])
+ })
+ }
+}
+
+export default ManageDB
diff --git a/src/main/manage/datastore/dbChecker.ts b/src/main/manage/datastore/dbChecker.ts
new file mode 100644
index 0000000..3c81376
--- /dev/null
+++ b/src/main/manage/datastore/dbChecker.ts
@@ -0,0 +1,116 @@
+import fs from 'fs-extra'
+import writeFile from 'write-file-atomic'
+import path from 'path'
+import { app } from 'electron'
+import { getLogger } from '@core/utils/localLogger'
+import dayjs from 'dayjs'
+import { T } from '~/main/i18n'
+
+const STORE_PATH = app.getPath('userData')
+const manageConfigFilePath = path.join(STORE_PATH, 'manage.json')
+export const defaultManageConfigPath = manageConfigFilePath
+const manageConfigFileBackupPath = path.join(STORE_PATH, 'manage.bak.json')
+let _configFilePath = ''
+let hasCheckPath = false
+
+const errorMsg = {
+ broken: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_DEFAULT'),
+ brokenButBackup: T('TIPS_PICGO_CONFIG_FILE_BROKEN_WITH_BACKUP')
+}
+
+/** ensure notification list */
+if (!global.notificationList) global.notificationList = []
+
+function manageDbChecker () {
+ if (process.type !== 'renderer') {
+ const manageConfigFilePath = managePathChecker()
+ if (!fs.existsSync(manageConfigFilePath)) {
+ return
+ }
+ let configFile: string = '{}'
+ const optionsTpl = {
+ title: T('TIPS_NOTICE'),
+ body: ''
+ }
+ // config save bak
+ try {
+ configFile = fs.readFileSync(manageConfigFilePath, { encoding: 'utf-8' })
+ JSON.parse(configFile)
+ } catch (e) {
+ fs.unlinkSync(manageConfigFilePath)
+ if (fs.existsSync(manageConfigFileBackupPath)) {
+ try {
+ configFile = fs.readFileSync(manageConfigFileBackupPath, { encoding: 'utf-8' })
+ JSON.parse(configFile)
+ 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')
+ })}`
+ global.notificationList.push(optionsTpl)
+ return
+ } catch (e) {
+ optionsTpl.body = errorMsg.broken
+ global.notificationList.push(optionsTpl)
+ return
+ }
+ }
+ optionsTpl.body = errorMsg.broken
+ global.notificationList.push(optionsTpl)
+ return
+ }
+ writeFile.sync(manageConfigFileBackupPath, configFile, { encoding: 'utf-8' })
+ }
+}
+
+/**
+ * Get manage config path
+ */
+function managePathChecker (): string {
+ if (_configFilePath) {
+ return _configFilePath
+ }
+ // defaultConfigPath
+ _configFilePath = defaultManageConfigPath
+ // if defaultConfig path is not exit
+ // do not parse the content of config
+ if (!fs.existsSync(defaultManageConfigPath)) {
+ return _configFilePath
+ }
+ try {
+ const configString = fs.readFileSync(defaultManageConfigPath, { encoding: 'utf-8' })
+ const config = JSON.parse(configString)
+ const userConfigPath: string = config.configPath || ''
+ if (userConfigPath) {
+ if (fs.existsSync(userConfigPath) && userConfigPath.endsWith('.json')) {
+ _configFilePath = userConfigPath
+ return _configFilePath
+ }
+ }
+ return _configFilePath
+ } catch (e) {
+ const manageLogPath = path.join(STORE_PATH, 'manage-gui-local.log')
+ const logger = getLogger(manageLogPath, 'Manage')
+ if (!hasCheckPath) {
+ const optionsTpl = {
+ title: T('TIPS_NOTICE'),
+ body: T('TIPS_CUSTOM_CONFIG_FILE_PATH_ERROR')
+ }
+ global.notificationList?.push(optionsTpl)
+ hasCheckPath = true
+ }
+ logger('error', e)
+ _configFilePath = defaultManageConfigPath
+ return _configFilePath
+ }
+}
+
+function managePathDir () {
+ return path.dirname(managePathChecker())
+}
+
+export {
+ managePathChecker,
+ managePathDir,
+ manageDbChecker
+}
diff --git a/src/main/manage/datastore/upDownTaskQueue.ts b/src/main/manage/datastore/upDownTaskQueue.ts
new file mode 100644
index 0000000..019d824
--- /dev/null
+++ b/src/main/manage/datastore/upDownTaskQueue.ts
@@ -0,0 +1,212 @@
+// a singleton class to manage the up/down task queue
+// qiniu tcyun aliyun smms imgur github upyun
+
+import path from 'path'
+import { app } from 'electron'
+import fs from 'fs-extra'
+export enum commonTaskStatus {
+ queuing = 'queuing',
+ failed = 'failed',
+ canceled = 'canceled',
+ paused = 'paused'
+}
+
+export enum uploadTaskSpecialStatus {
+ uploading = 'uploading',
+ uploaded = 'uploaded'
+}
+
+export enum downloadTaskSpecialStatus {
+ downloading = 'downloading',
+ downloaded = 'downloaded',
+}
+
+export type uploadTaskStatus = commonTaskStatus | uploadTaskSpecialStatus
+type downloadTaskStatus = commonTaskStatus | downloadTaskSpecialStatus
+
+export interface IUploadTask {
+ id: string
+ progress: number
+ status: uploadTaskStatus
+ sourceFilePath: string
+ sourceFileName: string
+ targetFilePath: string
+ targetFileBucket?: string
+ response?: any
+ cancelToken?: string
+ timeConsuming?: number
+ alias?: string
+ [other: string]: any
+}
+
+export interface IDownloadTask {
+ id: string
+ progress: number
+ status: downloadTaskStatus
+ sourceFileUrl?: string
+ sourceFileName?: string
+ sourceConfig?: IStringKeyMap
+ targetFilePath?: string
+ response?: any
+ cancelToken?: string
+ timeConsuming?: number
+ reseumConfig?: IStringKeyMap
+ alias?: string
+ [other: string]: any
+}
+
+class UpDownTaskQueue {
+ /* eslint-disable */
+ private static instance: UpDownTaskQueue
+ /* eslint-enable */
+ private uploadTaskQueue = []
+
+ private downloadTaskQueue = []
+
+ private persistPath = path.join(app.getPath('userData'), 'UpDownTaskQueue.json')
+
+ private constructor () {
+ this.restore()
+ }
+
+ public static getInstance () {
+ if (!UpDownTaskQueue.instance) {
+ UpDownTaskQueue.instance = new UpDownTaskQueue()
+ }
+ return UpDownTaskQueue.instance
+ }
+
+ getUploadTaskQueue () {
+ return UpDownTaskQueue.getInstance().uploadTaskQueue
+ }
+
+ getDownloadTaskQueue () {
+ return UpDownTaskQueue.getInstance().downloadTaskQueue
+ }
+
+ getUploadTask (taskId: string) {
+ return UpDownTaskQueue.getInstance().uploadTaskQueue.find(item => item.id === taskId)
+ }
+
+ getAllUploadTask () {
+ return UpDownTaskQueue.getInstance().uploadTaskQueue
+ }
+
+ addUploadTask (task: IUploadTask) {
+ UpDownTaskQueue.getInstance().uploadTaskQueue.push(task)
+ }
+
+ updateUploadTask (task: Partial) {
+ const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === task.id)
+ if (taskIndex !== -1) {
+ const taskKeys = Object.keys(task)
+ taskKeys.forEach(key => {
+ if (key !== 'id') {
+ UpDownTaskQueue.getInstance().uploadTaskQueue[taskIndex][key] = task[key]
+ }
+ })
+ }
+ }
+
+ removeUploadTask (taskId: string) {
+ const taskIndex = UpDownTaskQueue.getInstance().uploadTaskQueue.findIndex(item => item.id === taskId)
+ if (taskIndex !== -1) {
+ UpDownTaskQueue.getInstance().uploadTaskQueue.splice(taskIndex, 1)
+ }
+ }
+
+ removeDownloadTask (taskId: string) {
+ const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === taskId)
+ if (taskIndex !== -1) {
+ UpDownTaskQueue.getInstance().downloadTaskQueue.splice(taskIndex, 1)
+ }
+ }
+
+ getDownloadTask (taskId: string) {
+ return UpDownTaskQueue.getInstance().downloadTaskQueue.find(item => item.id === taskId)
+ }
+
+ getAllDownloadTask () {
+ return UpDownTaskQueue.getInstance().downloadTaskQueue
+ }
+
+ addDownloadTask (task: IDownloadTask) {
+ UpDownTaskQueue.getInstance().downloadTaskQueue.push(task)
+ }
+
+ updateDownloadTask (task: Partial) {
+ const taskIndex = UpDownTaskQueue.getInstance().downloadTaskQueue.findIndex(item => item.id === task.id)
+ if (taskIndex !== -1) {
+ const taskKeys = Object.keys(task)
+ taskKeys.forEach(key => {
+ if (key !== 'id') {
+ UpDownTaskQueue.getInstance().downloadTaskQueue[taskIndex][key] = task[key]
+ }
+ })
+ }
+ }
+
+ clearUploadTaskQueue () {
+ UpDownTaskQueue.getInstance().uploadTaskQueue = []
+ }
+
+ removeUploadedTask () {
+ 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)
+ }
+
+ clearDownloadTaskQueue () {
+ UpDownTaskQueue.getInstance().downloadTaskQueue = []
+ }
+
+ clearAllTaskQueue () {
+ this.clearUploadTaskQueue()
+ this.clearDownloadTaskQueue()
+ }
+
+ persist () {
+ try {
+ this.checkPersistPath()
+ fs.writeFileSync(this.persistPath, JSON.stringify({
+ uploadTaskQueue: this.uploadTaskQueue,
+ downloadTaskQueue: this.downloadTaskQueue
+ }))
+ } catch (e) {
+ console.log(e)
+ }
+ }
+
+ private restore () {
+ try {
+ this.checkPersistPath()
+ const persistData = JSON.parse(fs.readFileSync(this.persistPath, { encoding: 'utf-8' }))
+ this.uploadTaskQueue = persistData.uploadTaskQueue
+ this.downloadTaskQueue = persistData.downloadTaskQueue
+ } catch (e) {
+ this.uploadTaskQueue = []
+ this.downloadTaskQueue = []
+ }
+ }
+
+ private checkPersistPath () {
+ if (!fs.existsSync(this.persistPath)) {
+ 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({
+ uploadTaskQueue: this.uploadTaskQueue,
+ downloadTaskQueue: this.downloadTaskQueue
+ }))
+ }
+ }
+}
+
+export default UpDownTaskQueue
diff --git a/src/main/manage/events/constants.ts b/src/main/manage/events/constants.ts
new file mode 100644
index 0000000..d1c05f5
--- /dev/null
+++ b/src/main/manage/events/constants.ts
@@ -0,0 +1,3 @@
+export const PICLIST_MANAGE_GET_CONFIG = 'PICLIST_MANAGE_GET_CONFIG'
+export const PICLIST_MANAGE_SAVE_CONFIG = 'PICLIST_MANAGE_SAVE_CONFIG'
+export const PICLIST_MANAGE_REMOVE_CONFIG = 'PICLIST_MANAGE_REMOVE_CONFIG'
diff --git a/src/main/manage/events/ipcList.ts b/src/main/manage/events/ipcList.ts
new file mode 100644
index 0000000..d52d436
--- /dev/null
+++ b/src/main/manage/events/ipcList.ts
@@ -0,0 +1,142 @@
+import manageCoreIPC from './manageCoreIPC'
+import { ManageApi } from '../manageApi'
+import { ipcMain, IpcMainInvokeEvent, dialog, app, shell } from 'electron'
+import UpDownTaskQueue from '../datastore/upDownTaskQueue'
+import { downloadFileFromUrl } from '../utils/common'
+import path from 'path'
+import fs from 'fs-extra'
+
+export const manageIpcList = {
+ listen () {
+ manageCoreIPC.listen()
+
+ ipcMain.handle('getBucketList', async (_evt: IpcMainInvokeEvent, currentPicBed: string) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.getBucketList()
+ })
+
+ ipcMain.handle('createBucket', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.createBucket(param)
+ })
+
+ ipcMain.handle('getBucketFileList', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.getBucketFileList(param)
+ })
+
+ ipcMain.handle('getBucketDomain', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ const result = await manage.getBucketDomain(param)
+ return result
+ })
+
+ ipcMain.handle('setBucketAclPolicy', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.setBucketAclPolicy(param)
+ })
+
+ ipcMain.handle('renameBucketFile', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.renameBucketFile(param)
+ })
+
+ ipcMain.handle('deleteBucketFile', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.deleteBucketFile(param)
+ })
+
+ ipcMain.handle('deleteBucketFolder', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.deleteBucketFolder(param)
+ })
+
+ ipcMain.on('getBucketListBackstage', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.getBucketListBackstage(param)
+ })
+
+ ipcMain.handle('openFileSelectDialog', async () => {
+ const res = await dialog.showOpenDialog({
+ properties: ['openFile', 'multiSelections']
+ })
+ if (res.canceled) {
+ return []
+ } else {
+ return res.filePaths
+ }
+ })
+
+ ipcMain.handle('getPreSignedUrl', async (_evt: 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 (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.uploadBucketFile(param)
+ })
+
+ ipcMain.on('downloadBucketFile', async (_evt: IpcMainInvokeEvent, currentPicBed: string, param: IStringKeyMap) => {
+ const manage = new ManageApi(currentPicBed)
+ return manage.downloadBucketFile(param)
+ })
+
+ ipcMain.handle('createBucketFolder', async (_evt: 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 (_evt: IpcMainInvokeEvent, path: string | undefined) => {
+ if (path) {
+ shell.showItemInFolder(path)
+ } else {
+ shell.openPath(app.getPath('downloads'))
+ }
+ })
+
+ ipcMain.on('OpenLocalFile', async (_evt: IpcMainInvokeEvent, fullPath: string) => {
+ fs.existsSync(fullPath) ? shell.showItemInFolder(fullPath) : shell.openPath(path.dirname(fullPath))
+ })
+
+ ipcMain.handle('downloadFileFromUrl', async (_evt: IpcMainInvokeEvent, urls: string[]) => {
+ const res = await downloadFileFromUrl(urls)
+ return res
+ })
+ }
+}
diff --git a/src/main/manage/events/manageCoreIPC.ts b/src/main/manage/events/manageCoreIPC.ts
new file mode 100644
index 0000000..696ef0f
--- /dev/null
+++ b/src/main/manage/events/manageCoreIPC.ts
@@ -0,0 +1,35 @@
+import {
+ IpcMainEvent,
+ ipcMain
+} from 'electron'
+import getManageApi from '../Main'
+import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from './constants'
+
+const manageApi = getManageApi()
+
+const handleManageGetConfig = () => {
+ ipcMain.on(PICLIST_MANAGE_GET_CONFIG, (event: IpcMainEvent, key: string | undefined, callbackId: string) => {
+ const result = manageApi.getConfig(key)
+ event.sender.send(PICLIST_MANAGE_GET_CONFIG, result, callbackId)
+ })
+}
+
+const handleManageSaveConfig = () => {
+ ipcMain.on(PICLIST_MANAGE_SAVE_CONFIG, (_event: IpcMainEvent, data: any) => {
+ manageApi.saveConfig(data)
+ })
+}
+
+const handleManageRemoveConfig = () => {
+ ipcMain.on(PICLIST_MANAGE_REMOVE_CONFIG, (_event: IpcMainEvent, key: string, propName: string) => {
+ manageApi.removeConfig(key, propName)
+ })
+}
+
+export default {
+ listen () {
+ handleManageGetConfig()
+ handleManageSaveConfig()
+ handleManageRemoveConfig()
+ }
+}
diff --git a/src/main/manage/manageApi.ts b/src/main/manage/manageApi.ts
new file mode 100644
index 0000000..dc7e383
--- /dev/null
+++ b/src/main/manage/manageApi.ts
@@ -0,0 +1,529 @@
+
+import fs from 'fs-extra'
+import path from 'path'
+import { EventEmitter } from 'events'
+import { managePathChecker } from './datastore/dbChecker'
+import {
+ ManageApiType,
+ ManageConfigType,
+ ManageError,
+ PicBedMangeConfig
+} from '~/universal/types/manage'
+import ManageDB from './datastore/db'
+import { ManageLogger } from './utils/logger'
+import { get, set, unset } from 'lodash'
+import { homedir } from 'os'
+import { isInputConfigValid, formatError } from './utils/common'
+import API from './apis/api'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain } from 'electron'
+
+export class ManageApi extends EventEmitter implements ManageApiType {
+ private _config!: Partial
+ private db!: ManageDB
+ currentPicBed: string
+ configPath: string
+ baseDir!: string
+ logger: ManageLogger
+ currentPicBedConfig: PicBedMangeConfig
+
+ constructor (currentPicBed: string = '') {
+ super()
+ this.currentPicBed = currentPicBed || (this.getConfig('currentPicBed') ?? 'placeholder')
+ this.configPath = managePathChecker()
+ this.initConfigPath()
+ this.logger = new ManageLogger(this)
+ this.initconfig()
+ this.currentPicBedConfig = this.getPicBedConfig(this.currentPicBed)
+ }
+
+ getMsgParam (method: string) {
+ return {
+ class: 'ManageApi',
+ method,
+ picbedName: this.currentPicBedConfig.picBedName
+ }
+ }
+
+ errorMsg (err: any, param: IStringKeyMap) {
+ this.logger.error(formatError(err, param))
+ }
+
+ createClient () {
+ const name = this.currentPicBedConfig.picBedName
+ switch (name) {
+ case 'tcyun':
+ return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger)
+ case 'aliyun':
+ return new API.AliyunApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.accessKeySecret, this.logger)
+ case 'qiniu':
+ return new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger)
+ case 'upyun':
+ return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger)
+ case 'smms':
+ return new API.SmmsApi(this.currentPicBedConfig.token, this.logger)
+ case 'github':
+ 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)
+ default:
+ return {} as any
+ }
+ }
+
+ private getPicBedConfig (picBedName: string): PicBedMangeConfig {
+ return this.getConfig(`picBed.${picBedName}`)
+ }
+
+ private initConfigPath (): void {
+ if (this.configPath === '') {
+ this.configPath = `${homedir()}/.piclist/manage.json`
+ }
+ if (path.extname(this.configPath).toUpperCase() !== '.JSON') {
+ this.configPath = ''
+ throw Error('The configuration file only supports JSON format.')
+ }
+ this.baseDir = path.dirname(this.configPath)
+ const exist = fs.pathExistsSync(this.configPath)
+ if (!exist) {
+ fs.ensureFileSync(this.configPath)
+ }
+ }
+
+ private initconfig (): void {
+ this.db = new ManageDB(this)
+ this._config = this.db.read(true) as ManageConfigType
+ }
+
+ getConfig (name?: string): T {
+ if (!name) {
+ return this._config as unknown as T
+ } else {
+ return get(this._config, name)
+ }
+ }
+
+ saveConfig (config: IStringKeyMap): void {
+ if (!isInputConfigValid(config)) {
+ this.logger.warn(
+ 'the format of config is invalid, please provide object'
+ )
+ return
+ }
+ this.setConfig(config)
+ this.db.saveConfig(config)
+ }
+
+ removeConfig (key: string, propName: string): void {
+ if (!key || !propName) {
+ return
+ }
+ this.unsetConfig(key, propName)
+ this.db.unset(key, propName)
+ }
+
+ setConfig (config: IStringKeyMap): void {
+ if (!isInputConfigValid(config)) {
+ this.logger.warn(
+ 'the format of config is invalid, please provide object'
+ )
+ return
+ }
+ Object.keys(config).forEach((name: string) => {
+ set(this._config, name, config[name])
+ })
+ }
+
+ unsetConfig (key: string, propName: string): void {
+ if (!key || !propName) return
+ unset(this.getConfig(key), propName)
+ }
+
+ async getBucketList (
+ param?: IStringKeyMap | undefined
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'github':
+ case 'imgur':
+ try {
+ client = this.createClient()
+ return await client.getBucketList()
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('getBucketList'))
+ return []
+ }
+ case 'upyun':
+ return [{
+ Name: this.currentPicBedConfig.bucketName,
+ Location: 'upyun',
+ CreationDate: new Date().toISOString()
+ }]
+ case 'smms':
+ return [{
+ Name: 'smms',
+ Location: 'smms',
+ CreationDate: new Date().toISOString()
+ }]
+ default:
+ console.log(param)
+ return []
+ }
+ }
+
+ async getBucketInfo (
+ param?: IStringKeyMap | undefined
+ ): Promise {
+ console.log(param)
+ return {}
+ }
+
+ async getBucketDomain (
+ param: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'github':
+ try {
+ client = this.createClient() as any
+ return await client.getBucketDomain(param)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('getBucketDomain'))
+ return []
+ }
+ case 'upyun':
+ return [this.currentPicBedConfig.customUrl]
+ case 'smms':
+ return ['https://smms.app']
+ case 'imgur':
+ return ['https://imgur.com']
+ default:
+ return []
+ }
+ }
+
+ async createBucket (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ try {
+ client = this.createClient() as any
+ return await client.createBucket(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('createBucket'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async deleteBucket (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return false
+ }
+
+ async getOperatorList (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return []
+ }
+
+ async addOperator (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return false
+ }
+
+ async deleteOperator (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return false
+ }
+
+ async getBucketAclPolicy (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return {}
+ }
+
+ async setBucketAclPolicy (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'qiniu':
+ try {
+ client = new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger)
+ return await client.setBucketAclPolicy(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('setBucketAclPolicy'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ /**
+ * 后台更新bucket文件列表
+ * @param param
+ * @returns
+ */
+ async getBucketListBackstage (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ let window
+ const defaultResult = {
+ fullList: [],
+ success: false,
+ finished: true
+ }
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'smms':
+ case 'github':
+ case 'imgur':
+ try {
+ client = this.createClient() as any
+ return await client.getBucketListBackstage(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('getBucketListBackstage'))
+ window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ window.webContents.send('refreshFileTransferList', defaultResult)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return {}
+ }
+ default:
+ window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ window.webContents.send('refreshFileTransferList', defaultResult)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return {}
+ }
+ }
+
+ /**
+ * 获取文件夹列表
+ * 结果统一进行格式化 文件夹提取到最前
+ * key: 完整路径
+ * fileName: 文件名
+ * formatedTime: 格式化时间
+ * isDir: 是否是文件夹
+ * fileSize: 文件大小
+ **/
+ async getBucketFileList (
+ param?: IStringKeyMap
+ ): Promise {
+ const defaultResponse = {
+ fullList: [],
+ isTruncated: false,
+ nextMarker: '',
+ success: false
+ }
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'smms':
+ try {
+ client = this.createClient()
+ return await client.getBucketFileList(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('getBucketFileList'))
+ return defaultResponse
+ }
+ default:
+ return defaultResponse
+ }
+ }
+
+ async deleteBucketFile (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'smms':
+ case 'github':
+ case 'imgur':
+ try {
+ client = this.createClient() as any
+ const res = await client.deleteBucketFile(param!)
+ return res
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('deleteBucketFile'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async deleteBucketFolder (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'github':
+ try {
+ client = this.createClient() as any
+ return await client.deleteBucketFolder(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('deleteBucketFolder'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async renameBucketFile (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ try {
+ client = this.createClient() as any
+ return await client.renameBucketFile(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('renameBucketFile'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async downloadBucketFile (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'smms':
+ case 'github':
+ case 'imgur':
+ try {
+ client = this.createClient() as any
+ const res = await client.downloadBucketFile(param!)
+ return res
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('downloadBucketFile'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async copyMoveBucketFile (
+ param?: IStringKeyMap
+ ): Promise {
+ console.log(param)
+ return false
+ }
+
+ async createBucketFolder (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'github':
+ try {
+ client = this.createClient() as any
+ return await client.createBucketFolder(param!)
+ } catch (error) {
+ this.errorMsg(error, this.getMsgParam('createBucketFolder'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async uploadBucketFile (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'upyun':
+ case 'smms':
+ case 'github':
+ case 'imgur':
+ try {
+ client = this.createClient() as any
+ return await client.uploadBucketFile(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('uploadBucketFile'))
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ async getPreSignedUrl (
+ param?: IStringKeyMap
+ ): Promise {
+ let client
+ switch (this.currentPicBedConfig.picBedName) {
+ case 'tcyun':
+ case 'aliyun':
+ case 'qiniu':
+ case 'github':
+ try {
+ client = this.createClient() as any
+ return await client.getPreSignedUrl(param!)
+ } catch (error: any) {
+ this.errorMsg(error, this.getMsgParam('getPreSignedUrl'))
+ return 'error'
+ }
+ default:
+ return 'error'
+ }
+ }
+}
diff --git a/src/main/manage/utils/common.ts b/src/main/manage/utils/common.ts
new file mode 100644
index 0000000..8e2728e
--- /dev/null
+++ b/src/main/manage/utils/common.ts
@@ -0,0 +1,272 @@
+import fs from 'fs-extra'
+import path from 'path'
+import mime from 'mime-types'
+import axios from 'axios'
+import { app } from 'electron'
+import crypto from 'crypto'
+import got, { RequestError } from 'got'
+import { Stream } from 'stream'
+import { promisify } from 'util'
+import UpDownTaskQueue,
+{
+ uploadTaskSpecialStatus,
+ commonTaskStatus,
+ downloadTaskSpecialStatus
+} from '../datastore/upDownTaskQueue'
+import { ManageLogger } from '../utils/logger'
+import { formatHttpProxy } from '@/manage/utils/common'
+import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
+
+export const getFSFile = async (
+ filePath: string,
+ stream: boolean = false
+): Promise => {
+ try {
+ return {
+ extension: path.extname(filePath),
+ fileName: path.basename(filePath),
+ buffer: stream
+ ? fs.createReadStream(filePath)
+ : await fs.readFile(filePath),
+ success: true
+ }
+ } catch (e) {
+ return {
+ success: false
+ }
+ }
+}
+
+export const isInputConfigValid = (config: any): boolean => {
+ if (
+ typeof config === 'object' &&
+ !Array.isArray(config) &&
+ Object.keys(config).length > 0
+ ) {
+ return true
+ }
+ return false
+}
+
+export const getFileMimeType = (filePath: string): string => {
+ return mime.lookup(filePath) || 'application/octet-stream'
+}
+
+const checkTempFolderExist = async () => {
+ const tempPath = path.join(app.getPath('downloads'), 'piclistTemp')
+ try {
+ await fs.access(tempPath)
+ } catch (e) {
+ await fs.mkdir(tempPath)
+ }
+}
+
+export const downloadFileFromUrl = async (urls: string[]) => {
+ const tempPath = path.join(app.getPath('downloads'), 'piclistTemp')
+ await checkTempFolderExist()
+ const result = [] as string[]
+ for (let i = 0; i < urls.length; i++) {
+ const finishDownload = promisify(Stream.finished)
+ const fileName = path.basename(urls[i]).split('?')[0]
+ const filePath = path.join(tempPath, fileName)
+ const writer = fs.createWriteStream(filePath)
+ const res = await axios({
+ method: 'get',
+ url: urls[i],
+ responseType: 'stream'
+ })
+ res.data.pipe(writer)
+ await finishDownload(writer)
+ result.push(filePath)
+ }
+ return result
+}
+
+export const clearTempFolder = () => fs.emptyDirSync(path.join(app.getPath('downloads'), 'piclistTemp'))
+
+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 gotDownload = async (
+ instance: UpDownTaskQueue,
+ preSignedUrl: string,
+ fileStream: fs.WriteStream,
+ id : string,
+ savedFilePath: string,
+ logger?: ManageLogger,
+ param?: any,
+ agent: any = {}
+) => {
+ got(
+ preSignedUrl,
+ {
+ timeout: {
+ request: 30000
+ },
+ isStream: true,
+ throwHttpErrors: false,
+ searchParams: param,
+ agent
+ }
+ )
+ .on('downloadProgress', (progress: any) => {
+ instance.updateDownloadTask({
+ id,
+ progress: Math.floor(progress.percent * 100),
+ status: downloadTaskSpecialStatus.downloading
+ })
+ })
+ .pipe(fileStream)
+ .on('finish', () => {
+ instance.updateDownloadTask({
+ id,
+ progress: 100,
+ status: downloadTaskSpecialStatus.downloaded,
+ finishTime: new Date().toLocaleString()
+ })
+ })
+ .on('error', (err: any) => {
+ logger && logger.error(formatError(err, { method: 'gotDownload' }))
+ fs.remove(savedFilePath)
+ instance.updateDownloadTask({
+ id,
+ progress: 0,
+ status: commonTaskStatus.failed,
+ response: formatError(err, { method: 'gotDownload' }),
+ finishTime: new Date().toLocaleString()
+ })
+ })
+}
+
+export const gotUpload = async (
+ instance: UpDownTaskQueue,
+ url: string,
+ method: 'PUT' | 'POST',
+ body: any,
+ headers: any,
+ id: string,
+ logger?: ManageLogger,
+ timeout: number = 30000,
+ throwHttpErrors: boolean = false,
+ agent: any = {}
+) => {
+ got(
+ url,
+ {
+ headers,
+ method,
+ body,
+ timeout: {
+ request: timeout
+ },
+ throwHttpErrors,
+ agent
+ }
+ )
+ .on('uploadProgress', (progress: any) => {
+ instance.updateUploadTask({
+ id,
+ progress: Math.floor(progress.percent * 100),
+ status: uploadTaskSpecialStatus.uploading
+ })
+ })
+ .then((res: any) => {
+ instance.updateUploadTask({
+ id,
+ progress: res && (res.statusCode === 200 || res.statusCode === 201) ? 100 : 0,
+ status: res && (res.statusCode === 200 || res.statusCode === 201) ? uploadTaskSpecialStatus.uploaded : commonTaskStatus.failed,
+ finishTime: new Date().toLocaleString()
+ })
+ })
+ .catch((err: any) => {
+ logger && logger.error(formatError(err, { method: 'gotUpload' }))
+ instance.updateUploadTask({
+ id,
+ progress: 0,
+ response: formatError(err, { method: 'gotUpload' }),
+ status: commonTaskStatus.failed,
+ finishTime: new Date().toLocaleString()
+ })
+ })
+}
+
+export const formatError = (err: any, params:IStringKeyMap) => {
+ if (err instanceof RequestError) {
+ return {
+ ...params,
+ message: err.message ?? '',
+ name: 'RequestError',
+ code: err.code,
+ stack: err.stack ?? '',
+ timings: err.timings ?? {}
+ }
+ } else if (err instanceof Error) {
+ return {
+ ...params,
+ name: err.name ?? '',
+ message: err.message ?? '',
+ stack: err.stack ?? ''
+ }
+ } else {
+ if (typeof err === 'object') {
+ return JSON.stringify(err) + JSON.stringify(params)
+ } else {
+ return String(err) + JSON.stringify(params)
+ }
+ }
+}
+
+export const trimPath = (path: string) => path.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/')
+
+export const getAgent = (proxy:any, https: boolean = true) => {
+ const formatProxy = formatHttpProxy(proxy, 'string') as any
+ const opt = {
+ keepAlive: true,
+ keepAliveMsecs: 1000,
+ maxSockets: 256,
+ maxFreeSockets: 256,
+ scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
+ proxy: formatProxy.replace('127.0.0.1', 'localhost')
+ }
+ if (https) {
+ return formatProxy
+ ? {
+ https: new HttpsProxyAgent(opt)
+ }
+ : {}
+ } else {
+ return formatProxy
+ ? {
+ http: new HttpProxyAgent(opt)
+ }
+ : {}
+ }
+}
+
+export function getOptions (
+ method?: string,
+ headers?: IStringKeyMap,
+ searchParams?: IStringKeyMap,
+ responseType?: string,
+ body?: any,
+ timeout?: number,
+ proxy?: any
+) {
+ const options = {
+ method: method?.toUpperCase(),
+ headers,
+ searchParams,
+ agent: getAgent(proxy),
+ timeout: {
+ request: timeout || 30000
+ },
+ body,
+ throwHttpErrors: false,
+ responseType
+ } as IStringKeyMap
+ Object.keys(options).forEach(key => {
+ options[key] === undefined && delete options[key]
+ })
+ return options
+}
diff --git a/src/main/manage/utils/constants.ts b/src/main/manage/utils/constants.ts
new file mode 100644
index 0000000..716e296
--- /dev/null
+++ b/src/main/manage/utils/constants.ts
@@ -0,0 +1,68 @@
+const AliyunAreaCodeName : IStringKeyMap = {
+ 'oss-cn-hangzhou': '华东1(杭州)',
+ 'oss-cn-shanghai': '华东2(上海)',
+ 'oss-cn-nanjing': '华东5(南京本地地域)',
+ 'oss-cn-fuzhou': '华东6(福州本地地域)',
+ 'oss-cn-qingdao': '华北1(青岛)',
+ 'oss-cn-beijing': '华北2(北京)',
+ 'oss-cn-zhangjiakou': '华北3(张家口)',
+ 'oss-cn-huhehaote': '华北5(呼和浩特)',
+ 'oss-cn-wulanchabu': '华北6(乌兰察布)',
+ 'oss-cn-shenzhen': '华南1(深圳)',
+ 'oss-cn-heyuan': '华南2(河源)',
+ 'oss-cn-guangzhou': '华南3(广州)',
+ 'oss-cn-chengdu': '西南1(成都)',
+ 'oss-cn-hongkong': '中国(香港)',
+ 'oss-us-west-1': '美国(硅谷)',
+ 'oss-us-east-1': '美国(弗吉尼亚)',
+ 'oss-ap-northeast-1': '日本(东京)',
+ 'oss-ap-northeast-2': '韩国(首尔)',
+ 'oss-ap-southeast-1': '新加坡',
+ 'oss-ap-southeast-2': '澳大利亚(悉尼)',
+ 'oss-ap-southeast-3': '马来西亚(吉隆坡)',
+ 'oss-ap-southeast-5': '印度尼西亚(雅加达)',
+ 'oss-ap-southeast-6': '菲律宾(马尼拉)',
+ 'oss-ap-southeast-7': '泰国(曼谷)',
+ 'oss-ap-south-1': '印度(孟买)',
+ 'oss-eu-central-1': '德国(法兰克福)',
+ 'oss-eu-west-1': '英国(伦敦)',
+ 'oss-me-east-1': '阿联酋(迪拜)'
+}
+
+const QiniuAreaCodeName : IStringKeyMap = {
+ z0: '华东-浙江',
+ 'cn-east-2': '华东 浙江2',
+ z1: '华北-河北',
+ z2: '华南-广东',
+ na0: '北美-洛杉矶',
+ as0: '亚太-新加坡',
+ 'ap-northeast-1': '亚太-首尔'
+}
+
+const TencentAreaCodeName : IStringKeyMap = {
+ 'ap-beijing-1': '北京一区',
+ 'ap-beijing': '北京',
+ 'ap-nanjing': '南京',
+ 'ap-shanghai': '上海',
+ 'ap-guangzhou': '广州',
+ 'ap-chengdu': '成都',
+ 'ap-chongqing': '重庆',
+ 'ap-shenzhen-fsi': '深圳金融',
+ 'ap-shagnhai-fsi': '上海金融',
+ 'ap-beijing-fsi': '北京金融',
+ 'ap-hongkong': '香港',
+ 'ap-singapore': '新加坡',
+ 'ap-mumbai': '孟买',
+ 'ap-jakarta': '雅加达',
+ 'ap-seoul': '首尔',
+ 'ap-bangkok': '曼谷',
+ 'ap-tokyo': '东京',
+ 'na-siliconvalley': '硅谷(美西)',
+ 'na-ashburn': '弗吉尼亚(美东)',
+ 'na-toronto': '多伦多',
+ 'sa-saopaulo': '圣保罗',
+ 'eu-frankfurt': '法兰克福',
+ 'eu-moscow': '莫斯科'
+}
+
+export { AliyunAreaCodeName, QiniuAreaCodeName, TencentAreaCodeName }
diff --git a/src/main/manage/utils/logger.ts b/src/main/manage/utils/logger.ts
new file mode 100644
index 0000000..58991c3
--- /dev/null
+++ b/src/main/manage/utils/logger.ts
@@ -0,0 +1,165 @@
+import chalk from 'chalk'
+import dayjs from 'dayjs'
+import fs from 'fs-extra'
+import path from 'path'
+import util from 'util'
+import { ILogType } from '#/types/enum'
+import { ILogColor, ILogger } from 'piclist/dist/types'
+import { ManageApiType, Undefinable } from '~/universal/types/manage'
+import { enforceNumber, isDev } from '#/utils/common'
+
+export class ManageLogger implements ILogger {
+ private readonly level = {
+ [ILogType.success]: 'green',
+ [ILogType.info]: 'blue',
+ [ILogType.warn]: 'yellow',
+ [ILogType.error]: 'red'
+ }
+
+ private readonly ctx: ManageApiType
+ private logLevel!: string
+ private logPath!: string
+
+ constructor (ctx: ManageApiType) {
+ this.ctx = ctx
+ }
+
+ private handleLog (type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
+ const logHeader = chalk[this.level[type] as ILogColor](
+ `[PicList ${type.toUpperCase()}]`
+ )
+ console.log(logHeader, ...msg)
+ this.logLevel = this.ctx.getConfig('settings.logLevel')
+ this.logPath =
+ this.ctx.getConfig>('settings.logPath') ||
+ path.join(this.ctx.baseDir, './manage.log')
+ setTimeout(() => {
+ try {
+ const result = this.checkLogFileIsLarge(this.logPath)
+ if (result.isLarge) {
+ const warningMsg = `Log file is too large (> ${
+ result.logFileSizeLimit! / 1024 / 1024 || '10'
+ } MB), recreate log file`
+ console.log(chalk.yellow('[PicList WARN]:'), warningMsg)
+ this.recreateLogFile(this.logPath)
+ msg.unshift(warningMsg)
+ }
+ this.handleWriteLog(this.logPath, type, ...msg)
+ } catch (e) {
+ console.error('[PicList Error] on checking log file size', e)
+ }
+ }, 0)
+ }
+
+ private checkLogFileIsLarge (logPath: string): {
+ isLarge: boolean
+ logFileSize?: number
+ logFileSizeLimit?: number
+ } {
+ if (fs.existsSync(logPath)) {
+ const logFileSize = fs.statSync(logPath).size
+ const logFileSizeLimit =
+ enforceNumber(
+ this.ctx.getConfig>(
+ 'settings.logFileSizeLimit'
+ ) || 10
+ ) *
+ 1024 *
+ 1024
+ return {
+ isLarge: logFileSize > logFileSizeLimit,
+ logFileSize,
+ logFileSizeLimit
+ }
+ }
+ fs.ensureFileSync(logPath)
+ return {
+ isLarge: false
+ }
+ }
+
+ private recreateLogFile (logPath: string): void {
+ if (fs.existsSync(logPath)) {
+ fs.unlinkSync(logPath)
+ fs.createFileSync(logPath)
+ }
+ }
+
+ private 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()}] `
+ msg.forEach((item: ILogArgvTypeWithError) => {
+ if (item instanceof Error && type === 'error') {
+ log += `\n------Error Stack Begin------\n${util.format(
+ item?.stack
+ )}\n-------Error Stack End------- `
+ } else {
+ if (typeof item === 'object') {
+ if (item?.stack) {
+ log = log + `\n------Error Stack Begin------\n${util.format(
+ item.stack
+ )}\n-------Error Stack End------- `
+ }
+ item = JSON.stringify(item, (key, value) => {
+ if (key === 'stack') {
+ return undefined
+ }
+ return value
+ }, 2)
+ }
+ log += `${item as string} `
+ }
+ })
+ log += '\n'
+ fs.appendFileSync(logPath, log)
+ }
+ } catch (e) {
+ console.error('[PicList Error] on writing log file', e)
+ }
+ }
+
+ private checkLogLevel (
+ type: string,
+ level: undefined | string | string[]
+ ): boolean {
+ if (level === undefined || level === 'all') {
+ return true
+ }
+ if (Array.isArray(level)) {
+ return level.some((item: string) => item === type || item === 'all')
+ } else {
+ return type === level
+ }
+ }
+
+ success (...msq: ILogArgvType[]): void {
+ return this.handleLog(ILogType.success, ...msq)
+ }
+
+ info (...msq: ILogArgvType[]): void {
+ return this.handleLog(ILogType.info, ...msq)
+ }
+
+ error (...msq: ILogArgvTypeWithError[]): void {
+ return this.handleLog(ILogType.error, ...msq)
+ }
+
+ warn (...msq: ILogArgvType[]): void {
+ return this.handleLog(ILogType.warn, ...msq)
+ }
+
+ debug (...msq: ILogArgvType[]): void {
+ if (isDev) {
+ this.handleLog(ILogType.info, ...msq)
+ }
+ }
+}
+
+export default ManageLogger
diff --git a/src/main/migrate/index.ts b/src/main/migrate/index.ts
index 97ad47f..f29bea6 100644
--- a/src/main/migrate/index.ts
+++ b/src/main/migrate/index.ts
@@ -2,7 +2,7 @@ import { DBStore } from '@picgo/store'
import ConfigStore from '~/main/apis/core/datastore'
import path from 'path'
import fse from 'fs-extra'
-import { PicGo as PicGoCore } from 'picgo'
+import { PicGo as PicGoCore } from 'piclist'
import { T } from '~/main/i18n'
// from v2.1.2
const updateShortKeyFromVersion212 = (db: typeof ConfigStore, shortKeyConfig: IShortKeyConfigs | IOldShortKeyConfigs) => {
diff --git a/src/main/server/index.ts b/src/main/server/index.ts
index f8ac34a..a586149 100644
--- a/src/main/server/index.ts
+++ b/src/main/server/index.ts
@@ -48,7 +48,7 @@ class Server {
if (request.method === 'POST') {
if (!routers.getHandler(request.url!)) {
- logger.warn(`[PicGo Server] don't support [${request.url}] url`)
+ logger.warn(`[PicList Server] don't support [${request.url}] url`)
handleResponse({
response,
statusCode: 404,
@@ -66,7 +66,7 @@ class Server {
try {
postObj = (body === '') ? {} : JSON.parse(body)
} catch (err: any) {
- logger.error('[PicGo Server]', err)
+ logger.error('[PicList Server]', err)
return handleResponse({
response,
body: {
@@ -75,7 +75,7 @@ class Server {
}
})
}
- logger.info('[PicGo Server] get the request', body)
+ logger.info('[PicList Server] get the request', body)
const handler = routers.getHandler(request.url!)
handler!({
...postObj,
@@ -84,7 +84,7 @@ class Server {
})
}
} else {
- logger.warn(`[PicGo Server] don't support [${request.method}] method`)
+ logger.warn(`[PicList Server] don't support [${request.method}] method`)
response.statusCode = 404
response.end()
}
@@ -92,7 +92,7 @@ class Server {
// port as string is a bug
private listen = (port: number | string) => {
- logger.info(`[PicGo Server] is listening at ${port}`)
+ logger.info(`[PicList Server] is listening at ${port}`)
if (typeof port === 'string') {
port = parseInt(port, 10)
}
@@ -103,7 +103,7 @@ class Server {
await axios.post(ensureHTTPLink(`${this.config.host}:${port}/heartbeat`))
this.shutdown(true)
} catch (e) {
- logger.warn(`[PicGo Server] ${port} is busy, trying with port ${(port as number) + 1}`)
+ logger.warn(`[PicList Server] ${port} is busy, trying with port ${(port as number) + 1}`)
// fix a bug: not write an increase number to config file
// to solve the auto number problem
this.listen((port as number) + 1)
@@ -122,7 +122,7 @@ class Server {
shutdown (hasStarted?: boolean) {
this.httpServer.close()
if (!hasStarted) {
- logger.info('[PicGo Server] shutdown')
+ logger.info('[PicList Server] shutdown')
}
}
diff --git a/src/main/server/routerManager.ts b/src/main/server/routerManager.ts
index 5d58922..3504fa3 100644
--- a/src/main/server/routerManager.ts
+++ b/src/main/server/routerManager.ts
@@ -8,7 +8,7 @@ import { uploadChoosedFiles, uploadClipboardFiles } from 'apis/app/uploader/apis
import path from 'path'
import { dbPathDir } from 'apis/core/datastore/dbChecker'
const STORE_PATH = dbPathDir()
-const LOG_PATH = path.join(STORE_PATH, 'picgo.log')
+const LOG_PATH = path.join(STORE_PATH, 'piclist.log')
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
@@ -22,9 +22,9 @@ router.post('/upload', async ({
try {
if (list.length === 0) {
// upload with clipboard
- logger.info('[PicGo Server] upload clipboard file')
+ logger.info('[PicList Server] upload clipboard file')
const res = await uploadClipboardFiles()
- logger.info('[PicGo Server] upload result:', res)
+ logger.info('[PicList Server] upload result:', res)
if (res) {
handleResponse({
response,
@@ -43,7 +43,7 @@ router.post('/upload', async ({
})
}
} else {
- logger.info('[PicGo Server] upload files in list')
+ logger.info('[PicList Server] upload files in list')
// upload with files
const pathList = list.map(item => {
return {
@@ -52,7 +52,7 @@ router.post('/upload', async ({
})
const win = windowManager.getAvailableWindow()
const res = await uploadChoosedFiles(win.webContents, pathList)
- logger.info('[PicGo Server] upload result', res.join(' ; '))
+ logger.info('[PicList Server] upload result', res.join(' ; '))
if (res.length) {
handleResponse({
response,
diff --git a/src/main/server/utils.ts b/src/main/server/utils.ts
index 7adf4c1..ba6c4e7 100644
--- a/src/main/server/utils.ts
+++ b/src/main/server/utils.ts
@@ -19,7 +19,7 @@ export const handleResponse = ({
body?: any
}) => {
if (body?.success === false) {
- logger.warn('[PicGo Server] upload failed, see picgo.log for more detail ↑')
+ logger.warn('[PicList Server] upload failed, see piclist.log for more detail ↑')
}
response.writeHead(statusCode, header)
response.write(JSON.stringify(body))
diff --git a/src/main/utils/beforeOpen.ts b/src/main/utils/beforeOpen.ts
index fd1a807..e124c4a 100644
--- a/src/main/utils/beforeOpen.ts
+++ b/src/main/utils/beforeOpen.ts
@@ -4,7 +4,6 @@ import os from 'os'
import { dbPathChecker } from 'apis/core/datastore/dbChecker'
import yaml from 'js-yaml'
import { i18nManager } from '~/main/i18n'
-// import { ILocales } from '~/universal/types/i18n'
const configPath = dbPathChecker()
const CONFIG_DIR = path.dirname(configPath)
@@ -21,12 +20,12 @@ function beforeOpen () {
* macOS 右键菜单
*/
function resolveMacWorkFlow () {
- const dest = `${os.homedir()}/Library/Services/Upload pictures with PicGo.workflow`
+ const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow`
if (fs.existsSync(dest)) {
return true
} else {
try {
- fs.copySync(path.join(__static, 'Upload pictures with PicGo.workflow'), dest)
+ fs.copySync(path.join(__static, 'Upload pictures with PicList.workflow'), dest)
} catch (e) {
console.log(e)
}
diff --git a/src/main/utils/handleArgv.ts b/src/main/utils/handleArgv.ts
index 39f1517..3e80c1f 100644
--- a/src/main/utils/handleArgv.ts
+++ b/src/main/utils/handleArgv.ts
@@ -1,6 +1,6 @@
import path from 'path'
import fs from 'fs-extra'
-import { Logger } from 'picgo'
+import { Logger } from 'piclist'
import { isUrl } from '~/universal/utils/common'
interface IResultFileObject {
path: string
diff --git a/src/main/utils/updateChecker.ts b/src/main/utils/updateChecker.ts
index 85c6d97..eb7ca24 100644
--- a/src/main/utils/updateChecker.ts
+++ b/src/main/utils/updateChecker.ts
@@ -7,7 +7,7 @@ import { getLatestVersion } from '#/utils/getLatestVersion'
const version = pkg.version
// const releaseUrl = 'https://api.github.com/repos/Molunerfinn/PicGo/releases'
// const releaseUrlBackup = 'https://picgo-1251750343.cos.ap-chengdu.myqcloud.com'
-const downloadUrl = 'https://github.com/Molunerfinn/PicGo/releases/latest'
+const downloadUrl = 'https://github.com/Kuingsmile/PicList/releases/latest'
const checkVersion = async () => {
let showTip = db.get('settings.showUpdateTip')
@@ -16,8 +16,7 @@ const checkVersion = async () => {
showTip = true
}
if (showTip) {
- const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
- const res: string = await getLatestVersion(isCheckBetaUpdate)
+ const res: string = await getLatestVersion()
if (res !== '') {
const latest = res
const result = compareVersion2Update(version, latest)
@@ -49,12 +48,6 @@ const checkVersion = async () => {
// if true -> update else return false
const compareVersion2Update = (current: string, latest: string) => {
try {
- if (latest.includes('beta')) {
- const isCheckBetaUpdate = db.get('settings.checkBetaUpdate') !== false
- if (!isCheckBetaUpdate) {
- return false
- }
- }
return lt(current, latest)
} catch (e) {
return false
diff --git a/src/renderer/App.vue b/src/renderer/App.vue
index ef17ed0..b7b3799 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/App.vue
@@ -8,7 +8,7 @@
import { useStore } from '@/hooks/useStore'
import { onBeforeMount, onMounted, onUnmounted } from 'vue'
import { getConfig } from './utils/dataSender'
-import type { IConfig } from 'picgo'
+import type { IConfig } from 'piclist'
import bus from './utils/bus'
import { FORCE_UPDATE } from '~/universal/events/constants'
diff --git a/src/renderer/apis/aliyun.ts b/src/renderer/apis/aliyun.ts
new file mode 100644
index 0000000..dba2acb
--- /dev/null
+++ b/src/renderer/apis/aliyun.ts
@@ -0,0 +1,25 @@
+import OSS from 'ali-oss'
+
+export default class AliyunApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { fileName, config: { accessKeyId, accessKeySecret, bucket, area, path } } = configMap
+ try {
+ const client = new OSS({
+ accessKeyId,
+ accessKeySecret,
+ bucket,
+ region: area
+ })
+ let key
+ if (path === '/' || !path) {
+ key = fileName
+ } else {
+ key = `${path.replace(/^\//, '').replace(/\/$/, '')}/${fileName}`
+ }
+ const result = await client.delete(key) as any
+ return result.res.status === 204
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/allApi.ts b/src/renderer/apis/allApi.ts
new file mode 100644
index 0000000..8f7a970
--- /dev/null
+++ b/src/renderer/apis/allApi.ts
@@ -0,0 +1,27 @@
+import SmmsApi from './smms'
+import TcyunApi from './tcyun'
+import AliyunApi from './aliyun'
+import QiniuApi from './qiniu'
+import ImgurApi from './imgur'
+import GithubApi from './github'
+import UpyunApi from './upyun'
+
+const apiMap: IStringKeyMap = {
+ smms: SmmsApi,
+ tcyun: TcyunApi,
+ aliyun: AliyunApi,
+ qiniu: QiniuApi,
+ imgur: ImgurApi,
+ github: GithubApi,
+ upyun: UpyunApi
+}
+
+export default class ALLApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ if (apiMap[configMap.type] !== undefined) {
+ return await apiMap[configMap.type].delete(configMap)
+ } else {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/github.ts b/src/renderer/apis/github.ts
new file mode 100644
index 0000000..f98ff1a
--- /dev/null
+++ b/src/renderer/apis/github.ts
@@ -0,0 +1,31 @@
+import { Octokit } from '@octokit/rest'
+
+export default class GithubApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { fileName, hash, config: { repo, token, branch, path } } = configMap
+ const owner = repo.split('/')[0]
+ const repoName = repo.split('/')[1]
+ const octokit = new Octokit({
+ auth: token
+ })
+ let key
+ if (path === '/' || !path) {
+ key = fileName
+ } else {
+ key = `${path.replace(/^\//, '').replace(/\/$/, '')}/${fileName}`
+ }
+ try {
+ const result = await octokit.rest.repos.deleteFile({
+ owner,
+ repo: repoName,
+ path: key,
+ message: `delete ${fileName} by PicList`,
+ sha: hash,
+ branch
+ })
+ return result.status === 200
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/imgur.ts b/src/renderer/apis/imgur.ts
new file mode 100644
index 0000000..7cdc46f
--- /dev/null
+++ b/src/renderer/apis/imgur.ts
@@ -0,0 +1,21 @@
+import axios from 'axios'
+
+export default class ImgurApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const clientId = configMap.config.clientId
+ const { hash } = configMap
+ const fullUrl = `https://api.imgur.com/3/image/${hash}`
+ const headers = {
+ Authorization: `Client-ID ${clientId}`
+ }
+ try {
+ const res = await axios.delete(fullUrl, {
+ headers,
+ timeout: 10000
+ })
+ return res.status === 200
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/qiniu.ts b/src/renderer/apis/qiniu.ts
new file mode 100644
index 0000000..5ad5980
--- /dev/null
+++ b/src/renderer/apis/qiniu.ts
@@ -0,0 +1,33 @@
+import Qiniu from 'qiniu'
+
+export default class QiniuApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { fileName, config: { accessKey, secretKey, bucket, path } } = configMap
+ const mac = new Qiniu.auth.digest.Mac(accessKey, secretKey)
+ const qiniuConfig = new Qiniu.conf.Config()
+ try {
+ const bucketManager = new Qiniu.rs.BucketManager(mac, qiniuConfig)
+ let key = ''
+ if (path === '/' || !path) {
+ key = fileName
+ } else {
+ key = `${path.replace(/^\//, '').replace(/\/$/, '')}/${fileName}`
+ }
+ const res = await new Promise((resolve, reject) => {
+ bucketManager.delete(bucket, key, (err, respBody, respInfo) => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve({
+ respBody,
+ respInfo
+ })
+ }
+ })
+ }) as any
+ return res && res.respInfo.statusCode === 200
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/smms.ts b/src/renderer/apis/smms.ts
new file mode 100644
index 0000000..c159095
--- /dev/null
+++ b/src/renderer/apis/smms.ts
@@ -0,0 +1,23 @@
+import axios from 'axios'
+
+export default class SmmsApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { hash, config: { token } } = configMap
+ if (!hash || !token) {
+ return false
+ } else {
+ const res = await axios.get(
+ `https://smms.app/api/v2/delete/${hash}`, {
+ headers: {
+ Authorization: token
+ },
+ params: {
+ hash,
+ format: 'json'
+ },
+ timeout: 10000
+ })
+ return res.status === 200
+ }
+ }
+}
diff --git a/src/renderer/apis/tcyun.ts b/src/renderer/apis/tcyun.ts
new file mode 100644
index 0000000..32eb150
--- /dev/null
+++ b/src/renderer/apis/tcyun.ts
@@ -0,0 +1,27 @@
+import COS from 'cos-nodejs-sdk-v5'
+
+export default class TcyunApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { fileName, config: { secretId, secretKey, bucket, area, path } } = configMap
+ try {
+ const cos = new COS({
+ SecretId: secretId,
+ SecretKey: secretKey
+ })
+ let key
+ if (path === '/' || !path) {
+ key = `/${fileName}`
+ } else {
+ key = `/${path.replace(/^\//, '').replace(/\/$/, '')}${fileName}`
+ }
+ const result = await cos.deleteObject({
+ Bucket: bucket,
+ Region: area,
+ Key: key
+ })
+ return result.statusCode === 204
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/apis/upyun.ts b/src/renderer/apis/upyun.ts
new file mode 100644
index 0000000..5a1d070
--- /dev/null
+++ b/src/renderer/apis/upyun.ts
@@ -0,0 +1,22 @@
+// @ts-ignore
+import Upyun from 'upyun'
+
+export default class UpyunApi {
+ static async delete (configMap: IStringKeyMap): Promise {
+ const { fileName, config: { bucket, operator, password, path } } = configMap
+ try {
+ const service = new Upyun.Service(bucket, operator, password)
+ const client = new Upyun.Client(service)
+ let key
+ if (path === '/' || !path) {
+ key = fileName
+ } else {
+ key = `${path.replace(/^\//, '').replace(/\/$/, '')}/${fileName}`
+ }
+ const result = await client.deleteFile(key)
+ return result
+ } catch (error) {
+ return false
+ }
+ }
+}
diff --git a/src/renderer/layouts/Main.vue b/src/renderer/layouts/Main.vue
index 6591648..64b3b5b 100644
--- a/src/renderer/layouts/Main.vue
+++ b/src/renderer/layouts/Main.vue
@@ -51,11 +51,11 @@
{{ $T('UPLOAD_AREA') }}
-
+
-
+
- {{ $T('PICBEDS_MANAGE') }}
+ 管理页面
@@ -105,8 +105,8 @@
- {{ $T('PICGO_SPONSOR_TEXT') }}
+ {{ $T('PICLIST_SPONSOR_TEXT') }}
{
type
}
})
- // if (this.$builtInPicBed.includes(picBed)) {
- // this.$router.push({
- // name: picBed
- // })
- // } else {
- // this.$router.push({
- // name: 'others',
- // params: {
- // type: picBed
- // }
- // })
- // }
}
}
@@ -332,7 +321,7 @@ function openMiniWindow () {
function handleCopyPicBedConfig () {
clipboard.writeText(picBedConfigString.value)
- $message.success(T('COPY_PICBED_CONFIG_SUCCEED'))
+ $message.success($T('COPY_PICBED_CONFIG_SUCCEED'))
}
function getPicBeds (event: IpcRendererEvent, picBeds: IPicBedType[]) {
diff --git a/src/renderer/manage/ManageMain.vue b/src/renderer/manage/ManageMain.vue
deleted file mode 100644
index c3bfe47..0000000
--- a/src/renderer/manage/ManageMain.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- {{ test }}
-
-
-
-
-
-
diff --git a/src/renderer/manage/pages/assets/aliyun.png b/src/renderer/manage/pages/assets/aliyun.png
new file mode 100644
index 0000000..44b9f47
Binary files /dev/null and b/src/renderer/manage/pages/assets/aliyun.png differ
diff --git a/src/renderer/manage/pages/assets/github.png b/src/renderer/manage/pages/assets/github.png
new file mode 100644
index 0000000..10737d4
Binary files /dev/null and b/src/renderer/manage/pages/assets/github.png differ
diff --git a/src/renderer/manage/pages/assets/icons/3g2.png b/src/renderer/manage/pages/assets/icons/3g2.png
new file mode 100644
index 0000000..2fa0612
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/3g2.png differ
diff --git a/src/renderer/manage/pages/assets/icons/3gp.png b/src/renderer/manage/pages/assets/icons/3gp.png
new file mode 100644
index 0000000..2df8ba0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/3gp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/7z.png b/src/renderer/manage/pages/assets/icons/7z.png
new file mode 100644
index 0000000..35f4994
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/7z.png differ
diff --git a/src/renderer/manage/pages/assets/icons/_blank.png b/src/renderer/manage/pages/assets/icons/_blank.png
new file mode 100644
index 0000000..ae53a4e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/_blank.png differ
diff --git a/src/renderer/manage/pages/assets/icons/_page.png b/src/renderer/manage/pages/assets/icons/_page.png
new file mode 100644
index 0000000..b8d155e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/_page.png differ
diff --git a/src/renderer/manage/pages/assets/icons/aac.png b/src/renderer/manage/pages/assets/icons/aac.png
new file mode 100644
index 0000000..200db51
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/aac.png differ
diff --git a/src/renderer/manage/pages/assets/icons/accdb.png b/src/renderer/manage/pages/assets/icons/accdb.png
new file mode 100644
index 0000000..347d4d0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/accdb.png differ
diff --git a/src/renderer/manage/pages/assets/icons/adt.png b/src/renderer/manage/pages/assets/icons/adt.png
new file mode 100644
index 0000000..1d8f90d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/adt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ai.png b/src/renderer/manage/pages/assets/icons/ai.png
new file mode 100644
index 0000000..c1810f5
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ai.png differ
diff --git a/src/renderer/manage/pages/assets/icons/aiff.png b/src/renderer/manage/pages/assets/icons/aiff.png
new file mode 100644
index 0000000..f9f1faf
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/aiff.png differ
diff --git a/src/renderer/manage/pages/assets/icons/aly.png b/src/renderer/manage/pages/assets/icons/aly.png
new file mode 100644
index 0000000..c7b0ce0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/aly.png differ
diff --git a/src/renderer/manage/pages/assets/icons/amiga.png b/src/renderer/manage/pages/assets/icons/amiga.png
new file mode 100644
index 0000000..9dab7ca
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/amiga.png differ
diff --git a/src/renderer/manage/pages/assets/icons/amr.png b/src/renderer/manage/pages/assets/icons/amr.png
new file mode 100644
index 0000000..141dfef
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/amr.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ape.png b/src/renderer/manage/pages/assets/icons/ape.png
new file mode 100644
index 0000000..8eec427
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ape.png differ
diff --git a/src/renderer/manage/pages/assets/icons/apk.png b/src/renderer/manage/pages/assets/icons/apk.png
new file mode 100644
index 0000000..3162c45
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/apk.png differ
diff --git a/src/renderer/manage/pages/assets/icons/arj.png b/src/renderer/manage/pages/assets/icons/arj.png
new file mode 100644
index 0000000..8d200b2
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/arj.png differ
diff --git a/src/renderer/manage/pages/assets/icons/asf.png b/src/renderer/manage/pages/assets/icons/asf.png
new file mode 100644
index 0000000..936ead5
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/asf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/asm.png b/src/renderer/manage/pages/assets/icons/asm.png
new file mode 100644
index 0000000..79f28b9
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/asm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/asx.png b/src/renderer/manage/pages/assets/icons/asx.png
new file mode 100644
index 0000000..f6eb4fc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/asx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/au.png b/src/renderer/manage/pages/assets/icons/au.png
new file mode 100644
index 0000000..1d33a36
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/au.png differ
diff --git a/src/renderer/manage/pages/assets/icons/avc.png b/src/renderer/manage/pages/assets/icons/avc.png
new file mode 100644
index 0000000..85e4d93
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/avc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/avi.png b/src/renderer/manage/pages/assets/icons/avi.png
new file mode 100644
index 0000000..6ddfcea
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/avi.png differ
diff --git a/src/renderer/manage/pages/assets/icons/avs.png b/src/renderer/manage/pages/assets/icons/avs.png
new file mode 100644
index 0000000..c5796c9
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/avs.png differ
diff --git a/src/renderer/manage/pages/assets/icons/bak.png b/src/renderer/manage/pages/assets/icons/bak.png
new file mode 100644
index 0000000..bb94fe3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/bak.png differ
diff --git a/src/renderer/manage/pages/assets/icons/bas.png b/src/renderer/manage/pages/assets/icons/bas.png
new file mode 100644
index 0000000..027d5fe
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/bas.png differ
diff --git a/src/renderer/manage/pages/assets/icons/bat.png b/src/renderer/manage/pages/assets/icons/bat.png
new file mode 100644
index 0000000..3b32532
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/bat.png differ
diff --git a/src/renderer/manage/pages/assets/icons/bmp.png b/src/renderer/manage/pages/assets/icons/bmp.png
new file mode 100644
index 0000000..0efcc32
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/bmp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/bom.png b/src/renderer/manage/pages/assets/icons/bom.png
new file mode 100644
index 0000000..7098165
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/bom.png differ
diff --git a/src/renderer/manage/pages/assets/icons/c.png b/src/renderer/manage/pages/assets/icons/c.png
new file mode 100644
index 0000000..249e6c7
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/c.png differ
diff --git a/src/renderer/manage/pages/assets/icons/cda.png b/src/renderer/manage/pages/assets/icons/cda.png
new file mode 100644
index 0000000..5dd192f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/cda.png differ
diff --git a/src/renderer/manage/pages/assets/icons/cdr.png b/src/renderer/manage/pages/assets/icons/cdr.png
new file mode 100644
index 0000000..fbc728b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/cdr.png differ
diff --git a/src/renderer/manage/pages/assets/icons/chm.png b/src/renderer/manage/pages/assets/icons/chm.png
new file mode 100644
index 0000000..94bb993
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/chm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/class.png b/src/renderer/manage/pages/assets/icons/class.png
new file mode 100644
index 0000000..13429d1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/class.png differ
diff --git a/src/renderer/manage/pages/assets/icons/cmd.png b/src/renderer/manage/pages/assets/icons/cmd.png
new file mode 100644
index 0000000..427f086
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/cmd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/com.png b/src/renderer/manage/pages/assets/icons/com.png
new file mode 100644
index 0000000..39c7b46
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/com.png differ
diff --git a/src/renderer/manage/pages/assets/icons/cpp.png b/src/renderer/manage/pages/assets/icons/cpp.png
new file mode 100644
index 0000000..0a6cd8b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/cpp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/css.png b/src/renderer/manage/pages/assets/icons/css.png
new file mode 100644
index 0000000..8035b9d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/css.png differ
diff --git a/src/renderer/manage/pages/assets/icons/csv.png b/src/renderer/manage/pages/assets/icons/csv.png
new file mode 100644
index 0000000..05a08dc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/csv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dart.png b/src/renderer/manage/pages/assets/icons/dart.png
new file mode 100644
index 0000000..47e4b7b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dart.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dat.png b/src/renderer/manage/pages/assets/icons/dat.png
new file mode 100644
index 0000000..971e364
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dat.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ddb.png b/src/renderer/manage/pages/assets/icons/ddb.png
new file mode 100644
index 0000000..0fcbb1c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ddb.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dif.png b/src/renderer/manage/pages/assets/icons/dif.png
new file mode 100644
index 0000000..61c3dba
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dif.png differ
diff --git a/src/renderer/manage/pages/assets/icons/divx.png b/src/renderer/manage/pages/assets/icons/divx.png
new file mode 100644
index 0000000..54b7c10
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/divx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dll.png b/src/renderer/manage/pages/assets/icons/dll.png
new file mode 100644
index 0000000..140e1f3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dll.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dmg.png b/src/renderer/manage/pages/assets/icons/dmg.png
new file mode 100644
index 0000000..94a38ae
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dmg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/doc.png b/src/renderer/manage/pages/assets/icons/doc.png
new file mode 100644
index 0000000..aff8234
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/doc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/docm.png b/src/renderer/manage/pages/assets/icons/docm.png
new file mode 100644
index 0000000..8fffaa8
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/docm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/docx.png b/src/renderer/manage/pages/assets/icons/docx.png
new file mode 100644
index 0000000..6824077
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/docx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dot.png b/src/renderer/manage/pages/assets/icons/dot.png
new file mode 100644
index 0000000..5052be4
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dot.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dotm.png b/src/renderer/manage/pages/assets/icons/dotm.png
new file mode 100644
index 0000000..eedf78d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dotm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dotx.png b/src/renderer/manage/pages/assets/icons/dotx.png
new file mode 100644
index 0000000..b0220e3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dotx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dsl.png b/src/renderer/manage/pages/assets/icons/dsl.png
new file mode 100644
index 0000000..4214ae0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dsl.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dv.png b/src/renderer/manage/pages/assets/icons/dv.png
new file mode 100644
index 0000000..1ab0c31
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dvd.png b/src/renderer/manage/pages/assets/icons/dvd.png
new file mode 100644
index 0000000..1e4e08f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dvd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dvdaudio.png b/src/renderer/manage/pages/assets/icons/dvdaudio.png
new file mode 100644
index 0000000..3f34ca0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dvdaudio.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dwg.png b/src/renderer/manage/pages/assets/icons/dwg.png
new file mode 100644
index 0000000..de4fe53
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dwg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/dxf.png b/src/renderer/manage/pages/assets/icons/dxf.png
new file mode 100644
index 0000000..2ed2465
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/dxf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/emf.png b/src/renderer/manage/pages/assets/icons/emf.png
new file mode 100644
index 0000000..c8397b7
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/emf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/env.png b/src/renderer/manage/pages/assets/icons/env.png
new file mode 100644
index 0000000..4dda6b1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/env.png differ
diff --git a/src/renderer/manage/pages/assets/icons/eot.png b/src/renderer/manage/pages/assets/icons/eot.png
new file mode 100644
index 0000000..9dcce58
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/eot.png differ
diff --git a/src/renderer/manage/pages/assets/icons/eps.png b/src/renderer/manage/pages/assets/icons/eps.png
new file mode 100644
index 0000000..cc32db8
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/eps.png differ
diff --git a/src/renderer/manage/pages/assets/icons/exe.png b/src/renderer/manage/pages/assets/icons/exe.png
new file mode 100644
index 0000000..60e5f6b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/exe.png differ
diff --git a/src/renderer/manage/pages/assets/icons/exif.png b/src/renderer/manage/pages/assets/icons/exif.png
new file mode 100644
index 0000000..81f3096
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/exif.png differ
diff --git a/src/renderer/manage/pages/assets/icons/fakesmms.png b/src/renderer/manage/pages/assets/icons/fakesmms.png
new file mode 100644
index 0000000..4631b7e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/fakesmms.png differ
diff --git a/src/renderer/manage/pages/assets/icons/flc.png b/src/renderer/manage/pages/assets/icons/flc.png
new file mode 100644
index 0000000..0c97f9f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/flc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/fli.png b/src/renderer/manage/pages/assets/icons/fli.png
new file mode 100644
index 0000000..0dd55bd
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/fli.png differ
diff --git a/src/renderer/manage/pages/assets/icons/flv.png b/src/renderer/manage/pages/assets/icons/flv.png
new file mode 100644
index 0000000..73bc4cc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/flv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/folder.png b/src/renderer/manage/pages/assets/icons/folder.png
new file mode 100644
index 0000000..9d97671
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/folder.png differ
diff --git a/src/renderer/manage/pages/assets/icons/fon.png b/src/renderer/manage/pages/assets/icons/fon.png
new file mode 100644
index 0000000..7ebb73c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/fon.png differ
diff --git a/src/renderer/manage/pages/assets/icons/font.png b/src/renderer/manage/pages/assets/icons/font.png
new file mode 100644
index 0000000..5775948
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/font.png differ
diff --git a/src/renderer/manage/pages/assets/icons/for.png b/src/renderer/manage/pages/assets/icons/for.png
new file mode 100644
index 0000000..d8ab231
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/for.png differ
diff --git a/src/renderer/manage/pages/assets/icons/fpx.png b/src/renderer/manage/pages/assets/icons/fpx.png
new file mode 100644
index 0000000..676ff8c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/fpx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/fv.png b/src/renderer/manage/pages/assets/icons/fv.png
new file mode 100644
index 0000000..d1db743
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/fv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/gif.png b/src/renderer/manage/pages/assets/icons/gif.png
new file mode 100644
index 0000000..07e601f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/gif.png differ
diff --git a/src/renderer/manage/pages/assets/icons/gitingore.png b/src/renderer/manage/pages/assets/icons/gitingore.png
new file mode 100644
index 0000000..783c072
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/gitingore.png differ
diff --git a/src/renderer/manage/pages/assets/icons/gitkeep.png b/src/renderer/manage/pages/assets/icons/gitkeep.png
new file mode 100644
index 0000000..38cb5c0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/gitkeep.png differ
diff --git a/src/renderer/manage/pages/assets/icons/gz.png b/src/renderer/manage/pages/assets/icons/gz.png
new file mode 100644
index 0000000..8cf9d75
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/gz.png differ
diff --git a/src/renderer/manage/pages/assets/icons/h.png b/src/renderer/manage/pages/assets/icons/h.png
new file mode 100644
index 0000000..f5cc5e5
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/h.png differ
diff --git a/src/renderer/manage/pages/assets/icons/hdri.png b/src/renderer/manage/pages/assets/icons/hdri.png
new file mode 100644
index 0000000..00fa619
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/hdri.png differ
diff --git a/src/renderer/manage/pages/assets/icons/hlp.png b/src/renderer/manage/pages/assets/icons/hlp.png
new file mode 100644
index 0000000..441d3f9
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/hlp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/hpp.png b/src/renderer/manage/pages/assets/icons/hpp.png
new file mode 100644
index 0000000..ddcc8bf
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/hpp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/htm.png b/src/renderer/manage/pages/assets/icons/htm.png
new file mode 100644
index 0000000..5bdfb06
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/htm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/html.png b/src/renderer/manage/pages/assets/icons/html.png
new file mode 100644
index 0000000..454cd9f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/html.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ico.png b/src/renderer/manage/pages/assets/icons/ico.png
new file mode 100644
index 0000000..f029bab
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ico.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ics.png b/src/renderer/manage/pages/assets/icons/ics.png
new file mode 100644
index 0000000..7a0f5c0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ics.png differ
diff --git a/src/renderer/manage/pages/assets/icons/int.png b/src/renderer/manage/pages/assets/icons/int.png
new file mode 100644
index 0000000..30f0329
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/int.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ipynb.png b/src/renderer/manage/pages/assets/icons/ipynb.png
new file mode 100644
index 0000000..a2778b3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ipynb.png differ
diff --git a/src/renderer/manage/pages/assets/icons/iso.png b/src/renderer/manage/pages/assets/icons/iso.png
new file mode 100644
index 0000000..729fa7f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/iso.png differ
diff --git a/src/renderer/manage/pages/assets/icons/java.png b/src/renderer/manage/pages/assets/icons/java.png
new file mode 100644
index 0000000..0d46a4a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/java.png differ
diff --git a/src/renderer/manage/pages/assets/icons/jpeg.png b/src/renderer/manage/pages/assets/icons/jpeg.png
new file mode 100644
index 0000000..4262c4e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/jpeg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/jpg.png b/src/renderer/manage/pages/assets/icons/jpg.png
new file mode 100644
index 0000000..4262c4e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/jpg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/js.png b/src/renderer/manage/pages/assets/icons/js.png
new file mode 100644
index 0000000..507661c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/js.png differ
diff --git a/src/renderer/manage/pages/assets/icons/json.png b/src/renderer/manage/pages/assets/icons/json.png
new file mode 100644
index 0000000..362ef40
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/json.png differ
diff --git a/src/renderer/manage/pages/assets/icons/key.png b/src/renderer/manage/pages/assets/icons/key.png
new file mode 100644
index 0000000..44ab47e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/key.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ksp.png b/src/renderer/manage/pages/assets/icons/ksp.png
new file mode 100644
index 0000000..a76fb10
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ksp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/less.png b/src/renderer/manage/pages/assets/icons/less.png
new file mode 100644
index 0000000..2d7b56f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/less.png differ
diff --git a/src/renderer/manage/pages/assets/icons/lib.png b/src/renderer/manage/pages/assets/icons/lib.png
new file mode 100644
index 0000000..390082a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/lib.png differ
diff --git a/src/renderer/manage/pages/assets/icons/lic.png b/src/renderer/manage/pages/assets/icons/lic.png
new file mode 100644
index 0000000..bd28dbc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/lic.png differ
diff --git a/src/renderer/manage/pages/assets/icons/license.png b/src/renderer/manage/pages/assets/icons/license.png
new file mode 100644
index 0000000..0f5b2f1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/license.png differ
diff --git a/src/renderer/manage/pages/assets/icons/log.png b/src/renderer/manage/pages/assets/icons/log.png
new file mode 100644
index 0000000..7899d19
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/log.png differ
diff --git a/src/renderer/manage/pages/assets/icons/lst.png b/src/renderer/manage/pages/assets/icons/lst.png
new file mode 100644
index 0000000..a74d27d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/lst.png differ
diff --git a/src/renderer/manage/pages/assets/icons/lua.png b/src/renderer/manage/pages/assets/icons/lua.png
new file mode 100644
index 0000000..f7d2b67
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/lua.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mac.png b/src/renderer/manage/pages/assets/icons/mac.png
new file mode 100644
index 0000000..776f6fd
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mac.png differ
diff --git a/src/renderer/manage/pages/assets/icons/map.png b/src/renderer/manage/pages/assets/icons/map.png
new file mode 100644
index 0000000..88e944b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/map.png differ
diff --git a/src/renderer/manage/pages/assets/icons/markdown.png b/src/renderer/manage/pages/assets/icons/markdown.png
new file mode 100644
index 0000000..6122564
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/markdown.png differ
diff --git a/src/renderer/manage/pages/assets/icons/md.png b/src/renderer/manage/pages/assets/icons/md.png
new file mode 100644
index 0000000..6122564
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/md.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mdf.png b/src/renderer/manage/pages/assets/icons/mdf.png
new file mode 100644
index 0000000..81bf563
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mdf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mht.png b/src/renderer/manage/pages/assets/icons/mht.png
new file mode 100644
index 0000000..dadaf7a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mht.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mhtml.png b/src/renderer/manage/pages/assets/icons/mhtml.png
new file mode 100644
index 0000000..5b0be8f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mhtml.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mid.png b/src/renderer/manage/pages/assets/icons/mid.png
new file mode 100644
index 0000000..fc50598
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mid.png differ
diff --git a/src/renderer/manage/pages/assets/icons/midi.png b/src/renderer/manage/pages/assets/icons/midi.png
new file mode 100644
index 0000000..a32159f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/midi.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mkv.png b/src/renderer/manage/pages/assets/icons/mkv.png
new file mode 100644
index 0000000..327a6f1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mkv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mmf.png b/src/renderer/manage/pages/assets/icons/mmf.png
new file mode 100644
index 0000000..3ebedde
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mmf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mod.png b/src/renderer/manage/pages/assets/icons/mod.png
new file mode 100644
index 0000000..89ec567
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mod.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mov.png b/src/renderer/manage/pages/assets/icons/mov.png
new file mode 100644
index 0000000..03f2350
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mov.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mp2.png b/src/renderer/manage/pages/assets/icons/mp2.png
new file mode 100644
index 0000000..7e4a87a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mp2.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mp3.png b/src/renderer/manage/pages/assets/icons/mp3.png
new file mode 100644
index 0000000..568a51c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mp3.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mp4.png b/src/renderer/manage/pages/assets/icons/mp4.png
new file mode 100644
index 0000000..c83c923
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mp4.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpa.png b/src/renderer/manage/pages/assets/icons/mpa.png
new file mode 100644
index 0000000..de4a45f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpa.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpe.png b/src/renderer/manage/pages/assets/icons/mpe.png
new file mode 100644
index 0000000..42fe795
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpe.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpeg.png b/src/renderer/manage/pages/assets/icons/mpeg.png
new file mode 100644
index 0000000..c8eb903
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpeg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpeg1.png b/src/renderer/manage/pages/assets/icons/mpeg1.png
new file mode 100644
index 0000000..cccc2bc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpeg1.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpeg2.png b/src/renderer/manage/pages/assets/icons/mpeg2.png
new file mode 100644
index 0000000..c2d4168
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpeg2.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mpg.png b/src/renderer/manage/pages/assets/icons/mpg.png
new file mode 100644
index 0000000..e106159
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mpg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mppro.png b/src/renderer/manage/pages/assets/icons/mppro.png
new file mode 100644
index 0000000..fee4469
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mppro.png differ
diff --git a/src/renderer/manage/pages/assets/icons/msg.png b/src/renderer/manage/pages/assets/icons/msg.png
new file mode 100644
index 0000000..f4df81f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/msg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mts.png b/src/renderer/manage/pages/assets/icons/mts.png
new file mode 100644
index 0000000..43e11ad
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mts.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mux.png b/src/renderer/manage/pages/assets/icons/mux.png
new file mode 100644
index 0000000..9cac3ee
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mux.png differ
diff --git a/src/renderer/manage/pages/assets/icons/mv.png b/src/renderer/manage/pages/assets/icons/mv.png
new file mode 100644
index 0000000..253170f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/mv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/navi.png b/src/renderer/manage/pages/assets/icons/navi.png
new file mode 100644
index 0000000..1ec5e97
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/navi.png differ
diff --git a/src/renderer/manage/pages/assets/icons/obj.png b/src/renderer/manage/pages/assets/icons/obj.png
new file mode 100644
index 0000000..cd9bbf2
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/obj.png differ
diff --git a/src/renderer/manage/pages/assets/icons/odf.png b/src/renderer/manage/pages/assets/icons/odf.png
new file mode 100644
index 0000000..8173771
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/odf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ods.png b/src/renderer/manage/pages/assets/icons/ods.png
new file mode 100644
index 0000000..a3c5cc2
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ods.png differ
diff --git a/src/renderer/manage/pages/assets/icons/odt.png b/src/renderer/manage/pages/assets/icons/odt.png
new file mode 100644
index 0000000..1e72fd4
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/odt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ogg.png b/src/renderer/manage/pages/assets/icons/ogg.png
new file mode 100644
index 0000000..7d3a645
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ogg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/one.png b/src/renderer/manage/pages/assets/icons/one.png
new file mode 100644
index 0000000..e32a037
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/one.png differ
diff --git a/src/renderer/manage/pages/assets/icons/otf.png b/src/renderer/manage/pages/assets/icons/otf.png
new file mode 100644
index 0000000..8722992
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/otf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/otp.png b/src/renderer/manage/pages/assets/icons/otp.png
new file mode 100644
index 0000000..b419dc6
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/otp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ots.png b/src/renderer/manage/pages/assets/icons/ots.png
new file mode 100644
index 0000000..712b039
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ots.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ott.png b/src/renderer/manage/pages/assets/icons/ott.png
new file mode 100644
index 0000000..2540efd
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ott.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pas.png b/src/renderer/manage/pages/assets/icons/pas.png
new file mode 100644
index 0000000..180fb91
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pas.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pcd.png b/src/renderer/manage/pages/assets/icons/pcd.png
new file mode 100644
index 0000000..eb2bcbe
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pcd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pcx.png b/src/renderer/manage/pages/assets/icons/pcx.png
new file mode 100644
index 0000000..34370f7
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pcx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pdf.png b/src/renderer/manage/pages/assets/icons/pdf.png
new file mode 100644
index 0000000..b288c3f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pdf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/php.png b/src/renderer/manage/pages/assets/icons/php.png
new file mode 100644
index 0000000..615591e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/php.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pic.png b/src/renderer/manage/pages/assets/icons/pic.png
new file mode 100644
index 0000000..2e038f1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pic.png differ
diff --git a/src/renderer/manage/pages/assets/icons/png.png b/src/renderer/manage/pages/assets/icons/png.png
new file mode 100644
index 0000000..374333c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/png.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ppt.png b/src/renderer/manage/pages/assets/icons/ppt.png
new file mode 100644
index 0000000..42f8895
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ppt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pptx.png b/src/renderer/manage/pages/assets/icons/pptx.png
new file mode 100644
index 0000000..64e1438
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pptx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/proe.png b/src/renderer/manage/pages/assets/icons/proe.png
new file mode 100644
index 0000000..63ace85
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/proe.png differ
diff --git a/src/renderer/manage/pages/assets/icons/prt.png b/src/renderer/manage/pages/assets/icons/prt.png
new file mode 100644
index 0000000..7993b90
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/prt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/psd.png b/src/renderer/manage/pages/assets/icons/psd.png
new file mode 100644
index 0000000..4351dd3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/psd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/py.png b/src/renderer/manage/pages/assets/icons/py.png
new file mode 100644
index 0000000..9e5668f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/py.png differ
diff --git a/src/renderer/manage/pages/assets/icons/pyc.png b/src/renderer/manage/pages/assets/icons/pyc.png
new file mode 100644
index 0000000..cbb7ce4
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/pyc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/qsv.png b/src/renderer/manage/pages/assets/icons/qsv.png
new file mode 100644
index 0000000..88d4b20
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/qsv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/qt.png b/src/renderer/manage/pages/assets/icons/qt.png
new file mode 100644
index 0000000..4deac47
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/qt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/quicktime.png b/src/renderer/manage/pages/assets/icons/quicktime.png
new file mode 100644
index 0000000..b72491a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/quicktime.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ra.png b/src/renderer/manage/pages/assets/icons/ra.png
new file mode 100644
index 0000000..5994f56
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ra.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ram.png b/src/renderer/manage/pages/assets/icons/ram.png
new file mode 100644
index 0000000..e314be0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ram.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rar.png b/src/renderer/manage/pages/assets/icons/rar.png
new file mode 100644
index 0000000..ad4b879
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rar.png differ
diff --git a/src/renderer/manage/pages/assets/icons/raw.png b/src/renderer/manage/pages/assets/icons/raw.png
new file mode 100644
index 0000000..a6098c9
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/raw.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rb.png b/src/renderer/manage/pages/assets/icons/rb.png
new file mode 100644
index 0000000..5b4a52b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rb.png differ
diff --git a/src/renderer/manage/pages/assets/icons/realaudio.png b/src/renderer/manage/pages/assets/icons/realaudio.png
new file mode 100644
index 0000000..6725dd1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/realaudio.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rm.png b/src/renderer/manage/pages/assets/icons/rm.png
new file mode 100644
index 0000000..e38cbe6
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rmvb.png b/src/renderer/manage/pages/assets/icons/rmvb.png
new file mode 100644
index 0000000..7cd97a6
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rmvb.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rp.png b/src/renderer/manage/pages/assets/icons/rp.png
new file mode 100644
index 0000000..9efccd8
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/rtf.png b/src/renderer/manage/pages/assets/icons/rtf.png
new file mode 100644
index 0000000..99510b9
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/rtf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/s48.png b/src/renderer/manage/pages/assets/icons/s48.png
new file mode 100644
index 0000000..a00dc24
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/s48.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sacd.png b/src/renderer/manage/pages/assets/icons/sacd.png
new file mode 100644
index 0000000..f0d3351
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sacd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sass.png b/src/renderer/manage/pages/assets/icons/sass.png
new file mode 100644
index 0000000..27ebf76
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sass.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sch.png b/src/renderer/manage/pages/assets/icons/sch.png
new file mode 100644
index 0000000..12ee11f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sch.png differ
diff --git a/src/renderer/manage/pages/assets/icons/scss.png b/src/renderer/manage/pages/assets/icons/scss.png
new file mode 100644
index 0000000..33b47f4
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/scss.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sh.png b/src/renderer/manage/pages/assets/icons/sh.png
new file mode 100644
index 0000000..47f99e3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sh.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sql.png b/src/renderer/manage/pages/assets/icons/sql.png
new file mode 100644
index 0000000..1742560
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sql.png differ
diff --git a/src/renderer/manage/pages/assets/icons/stp.png b/src/renderer/manage/pages/assets/icons/stp.png
new file mode 100644
index 0000000..9dea3bc
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/stp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/svcd.png b/src/renderer/manage/pages/assets/icons/svcd.png
new file mode 100644
index 0000000..708c7ef
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/svcd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/svg.png b/src/renderer/manage/pages/assets/icons/svg.png
new file mode 100644
index 0000000..ddc477d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/svg.png differ
diff --git a/src/renderer/manage/pages/assets/icons/swf.png b/src/renderer/manage/pages/assets/icons/swf.png
new file mode 100644
index 0000000..02770be
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/swf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/sys.png b/src/renderer/manage/pages/assets/icons/sys.png
new file mode 100644
index 0000000..a28488d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/sys.png differ
diff --git a/src/renderer/manage/pages/assets/icons/tga.png b/src/renderer/manage/pages/assets/icons/tga.png
new file mode 100644
index 0000000..2a97d3d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/tga.png differ
diff --git a/src/renderer/manage/pages/assets/icons/tgz.png b/src/renderer/manage/pages/assets/icons/tgz.png
new file mode 100644
index 0000000..2572e1f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/tgz.png differ
diff --git a/src/renderer/manage/pages/assets/icons/tiff.png b/src/renderer/manage/pages/assets/icons/tiff.png
new file mode 100644
index 0000000..a44d071
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/tiff.png differ
diff --git a/src/renderer/manage/pages/assets/icons/tmp.png b/src/renderer/manage/pages/assets/icons/tmp.png
new file mode 100644
index 0000000..7c3762d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/tmp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ts.png b/src/renderer/manage/pages/assets/icons/ts.png
new file mode 100644
index 0000000..8e3c119
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ts.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ttc.png b/src/renderer/manage/pages/assets/icons/ttc.png
new file mode 100644
index 0000000..723439d
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ttc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ttf.png b/src/renderer/manage/pages/assets/icons/ttf.png
new file mode 100644
index 0000000..0d9c8fa
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ttf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/txt.png b/src/renderer/manage/pages/assets/icons/txt.png
new file mode 100644
index 0000000..36c466c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/txt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/ufo.png b/src/renderer/manage/pages/assets/icons/ufo.png
new file mode 100644
index 0000000..8f895da
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/ufo.png differ
diff --git a/src/renderer/manage/pages/assets/icons/unknown.png b/src/renderer/manage/pages/assets/icons/unknown.png
new file mode 100644
index 0000000..5420638
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/unknown.png differ
diff --git a/src/renderer/manage/pages/assets/icons/vcd.png b/src/renderer/manage/pages/assets/icons/vcd.png
new file mode 100644
index 0000000..92c6658
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/vcd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/vob.png b/src/renderer/manage/pages/assets/icons/vob.png
new file mode 100644
index 0000000..56e58d1
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/vob.png differ
diff --git a/src/renderer/manage/pages/assets/icons/voc.png b/src/renderer/manage/pages/assets/icons/voc.png
new file mode 100644
index 0000000..53d8fa7
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/voc.png differ
diff --git a/src/renderer/manage/pages/assets/icons/vqf.png b/src/renderer/manage/pages/assets/icons/vqf.png
new file mode 100644
index 0000000..05a2b84
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/vqf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/vue.png b/src/renderer/manage/pages/assets/icons/vue.png
new file mode 100644
index 0000000..3ff9f4b
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/vue.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wav.png b/src/renderer/manage/pages/assets/icons/wav.png
new file mode 100644
index 0000000..523b9b6
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wav.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wdl.png b/src/renderer/manage/pages/assets/icons/wdl.png
new file mode 100644
index 0000000..92e34f3
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wdl.png differ
diff --git a/src/renderer/manage/pages/assets/icons/webm.png b/src/renderer/manage/pages/assets/icons/webm.png
new file mode 100644
index 0000000..b62035e
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/webm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/webp.png b/src/renderer/manage/pages/assets/icons/webp.png
new file mode 100644
index 0000000..3a6ceea
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/webp.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wki.png b/src/renderer/manage/pages/assets/icons/wki.png
new file mode 100644
index 0000000..de05ba8
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wki.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wma.png b/src/renderer/manage/pages/assets/icons/wma.png
new file mode 100644
index 0000000..50079e2
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wma.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wmf.png b/src/renderer/manage/pages/assets/icons/wmf.png
new file mode 100644
index 0000000..05d1f82
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wmf.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wmv.png b/src/renderer/manage/pages/assets/icons/wmv.png
new file mode 100644
index 0000000..845793a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wmv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wmvhd.png b/src/renderer/manage/pages/assets/icons/wmvhd.png
new file mode 100644
index 0000000..c7806aa
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wmvhd.png differ
diff --git a/src/renderer/manage/pages/assets/icons/woff.png b/src/renderer/manage/pages/assets/icons/woff.png
new file mode 100644
index 0000000..96ec297
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/woff.png differ
diff --git a/src/renderer/manage/pages/assets/icons/woff2.png b/src/renderer/manage/pages/assets/icons/woff2.png
new file mode 100644
index 0000000..9c99a16
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/woff2.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wps.png b/src/renderer/manage/pages/assets/icons/wps.png
new file mode 100644
index 0000000..17ea80a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wps.png differ
diff --git a/src/renderer/manage/pages/assets/icons/wpt.png b/src/renderer/manage/pages/assets/icons/wpt.png
new file mode 100644
index 0000000..3ed6cac
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/wpt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/x_t.png b/src/renderer/manage/pages/assets/icons/x_t.png
new file mode 100644
index 0000000..26eaf9f
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/x_t.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xls.png b/src/renderer/manage/pages/assets/icons/xls.png
new file mode 100644
index 0000000..c66931c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xls.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xlsm.png b/src/renderer/manage/pages/assets/icons/xlsm.png
new file mode 100644
index 0000000..cdf2af0
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xlsm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xlsx.png b/src/renderer/manage/pages/assets/icons/xlsx.png
new file mode 100644
index 0000000..5dd5905
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xlsx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xlt.png b/src/renderer/manage/pages/assets/icons/xlt.png
new file mode 100644
index 0000000..573b954
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xlt.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xltm.png b/src/renderer/manage/pages/assets/icons/xltm.png
new file mode 100644
index 0000000..af0b96a
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xltm.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xltx.png b/src/renderer/manage/pages/assets/icons/xltx.png
new file mode 100644
index 0000000..4950047
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xltx.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xmind.png b/src/renderer/manage/pages/assets/icons/xmind.png
new file mode 100644
index 0000000..5f1b3ce
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xmind.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xml.png b/src/renderer/manage/pages/assets/icons/xml.png
new file mode 100644
index 0000000..9fcfced
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xml.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xv.png b/src/renderer/manage/pages/assets/icons/xv.png
new file mode 100644
index 0000000..db8bb49
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xv.png differ
diff --git a/src/renderer/manage/pages/assets/icons/xvid.png b/src/renderer/manage/pages/assets/icons/xvid.png
new file mode 100644
index 0000000..1bff154
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/xvid.png differ
diff --git a/src/renderer/manage/pages/assets/icons/yaml.png b/src/renderer/manage/pages/assets/icons/yaml.png
new file mode 100644
index 0000000..373f983
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/yaml.png differ
diff --git a/src/renderer/manage/pages/assets/icons/yml.png b/src/renderer/manage/pages/assets/icons/yml.png
new file mode 100644
index 0000000..373f983
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/yml.png differ
diff --git a/src/renderer/manage/pages/assets/icons/z.png b/src/renderer/manage/pages/assets/icons/z.png
new file mode 100644
index 0000000..8073e03
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/z.png differ
diff --git a/src/renderer/manage/pages/assets/icons/zip.png b/src/renderer/manage/pages/assets/icons/zip.png
new file mode 100644
index 0000000..238768c
Binary files /dev/null and b/src/renderer/manage/pages/assets/icons/zip.png differ
diff --git a/src/renderer/manage/pages/assets/imgur.png b/src/renderer/manage/pages/assets/imgur.png
new file mode 100644
index 0000000..4631b7e
Binary files /dev/null and b/src/renderer/manage/pages/assets/imgur.png differ
diff --git a/src/renderer/manage/pages/assets/qiniu.png b/src/renderer/manage/pages/assets/qiniu.png
new file mode 100644
index 0000000..6c86264
Binary files /dev/null and b/src/renderer/manage/pages/assets/qiniu.png differ
diff --git a/src/renderer/manage/pages/assets/smms.png b/src/renderer/manage/pages/assets/smms.png
new file mode 100644
index 0000000..97bb61c
Binary files /dev/null and b/src/renderer/manage/pages/assets/smms.png differ
diff --git a/src/renderer/manage/pages/assets/tcyun.png b/src/renderer/manage/pages/assets/tcyun.png
new file mode 100644
index 0000000..382ad53
Binary files /dev/null and b/src/renderer/manage/pages/assets/tcyun.png differ
diff --git a/src/renderer/manage/pages/assets/upyun.png b/src/renderer/manage/pages/assets/upyun.png
new file mode 100644
index 0000000..14b53da
Binary files /dev/null and b/src/renderer/manage/pages/assets/upyun.png differ
diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue
new file mode 100644
index 0000000..25e0e13
--- /dev/null
+++ b/src/renderer/manage/pages/bucketPage.vue
@@ -0,0 +1,2681 @@
+/*
+ *UI布局和部分样式代码参考了https://github.com/willnewii/qiniuClient
+ *感谢作者@willnewii
+ */
+
+
+
+
+
+
+
+
+
+
+
+
+ 排序
+
+
+
+ 文件名
+
+
+ 文件大小
+
+
+ 文件类型
+
+
+ 上传时间
+
+
+ 选中状态
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 确定
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制JSON格式信息
+
+
+
+
+ {{ key }}:
+
+
+ {{ value }}
+
+
+
+
+
+ 加载中,点击取消
+
+
+
+
+
+
+ 拖放上传支持递归上传文件夹
+
+ 或:点击选择文件(不支持文件夹)
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ isLoadingUploadPanelFiles? '读取文件中': '上传' }}
+
+
+
+ 清空
+
+
+
+
+
+
+
+
+ 复制上传任务信息
+
+
+ 清空已完成任务
+
+
+ 清空所有任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制上传任务信息
+
+
+ 清空已完成任务
+
+
+ 清空所有任务
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制下载任务信息
+
+
+ 清空已完成任务
+
+
+ 清空所有任务
+
+
+ 打开下载目录
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制下载任务信息
+
+
+ 清空已完成任务
+
+
+ 清空所有任务
+
+
+ 打开下载目录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/manage/pages/emptyPage.vue b/src/renderer/manage/pages/emptyPage.vue
new file mode 100644
index 0000000..730c709
--- /dev/null
+++ b/src/renderer/manage/pages/emptyPage.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/src/renderer/manage/pages/logIn.vue b/src/renderer/manage/pages/logIn.vue
new file mode 100644
index 0000000..2ce24b8
--- /dev/null
+++ b/src/renderer/manage/pages/logIn.vue
@@ -0,0 +1,520 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.alias }}
+
+
+
+
+
+
+
+ 进入
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 导入
+
+
+ {{ i }}
+
+
+
+
+ 保存
+
+
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/manage/pages/manageMain.vue b/src/renderer/manage/pages/manageMain.vue
new file mode 100644
index 0000000..3ebef69
--- /dev/null
+++ b/src/renderer/manage/pages/manageMain.vue
@@ -0,0 +1,562 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 返回首页
+
+
+
+
+
+
+
+ {{ item.alias }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ '-' + currentPagePicBedConfig.appId }}
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/manage/pages/manageSetting.vue b/src/renderer/manage/pages/manageSetting.vue
new file mode 100644
index 0000000..e53378a
--- /dev/null
+++ b/src/renderer/manage/pages/manageSetting.vue
@@ -0,0 +1,558 @@
+
+
+
+ 管理页面设置
+
+
+
+
+
+
+
+ 每次进入新目录时,是否自动刷新文件列表
+
+
+
+
+
+
+
+
+
+
+
+
+ 清空文件列表缓存数据库 已占用:
+ {{ formatFileSize(dbSize) === ''? 0 : formatFileSize(dbSize) }}
+ 剩余可用:
+ {{ dbSizeAvailableRate }} %
+
+
+
+
+
+
+
+
+
+
+ 清空
+
+
+
+
+
+
+ 图片显示为原图而非默认文件格式图标(需要存储桶可公开访问)
+
+
+
+
+
+
+ 为自定义域名开启强制HTTPS
+
+
+
+
+
+
+
+
+
+
+
+ 文件搜索时,是否忽略大小写
+
+
+
+
+
+
+
+
+
+
+
+ 上传文件时间戳重命名--(优先级最高)
+
+
+
+
+
+
+
+
+
+
+
+ 上传文件随机字符串重命名--(优先级中)
+
+
+
+
+
+
+
+
+
+
+
+ 上传文件自定义重命名--(优先级最低)
+
+
+
+
+
+
+
+
+
+
+ 自定义重命名格式,占位符请参考下表,可自由组合
+
+
+
+
+
+
+
+
+
+ 预签名链接过期时间(单位:秒)
+
+
+
+ 选择默认复制的链接格式
+
+
+
+ Markdown
+
+
+ Markdown(带链接)
+
+
+ 原始链接
+
+
+ HTML格式
+
+
+ BBCode格式
+
+
+ 自定义格式
+
+
+
+ 自定义链接格式($url为链接,$fileName为文件名)
+
+
+
+
+ 选择下载目录
+
+
+
+
+
+
+
+
+ 选择目录
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/manage/store/bucketFileDb.ts b/src/renderer/manage/store/bucketFileDb.ts
new file mode 100644
index 0000000..6691911
--- /dev/null
+++ b/src/renderer/manage/store/bucketFileDb.ts
@@ -0,0 +1,50 @@
+import Dexie, { Table } from 'dexie'
+
+/*
+ * create a database for bucket file cache
+ *database name: bucketFileDb
+ *structure:
+ - table: picBedName
+ - key: alias-bucketName-prefix
+ - value: from fullList
+ - primaryKey: key
+*/
+
+export interface IFileCache {
+ key: string,
+ value: any
+}
+
+/**
+ * new picbed will add a plist suffix to distinguish from picgo
+ */
+export class FileCacheDb extends Dexie {
+ tcyun: Table
+ aliyun: Table
+ qiniu: Table
+ github: Table
+ smms: Table
+ upyun: Table
+ imgur: Table
+ s3plist: Table
+
+ constructor () {
+ super('bucketFileDb')
+ const tableNames = ['tcyun', 'aliyun', 'qiniu', 'github', 'smms', 'upyun', 'imgur', 's3plist']
+ const tableNamesMap = tableNames.reduce((acc, cur) => {
+ acc[cur] = '&key, value'
+ return acc
+ }, {} as IStringKeyMap)
+ this.version(1).stores(tableNamesMap)
+ this.tcyun = this.table('tcyun')
+ this.aliyun = this.table('aliyun')
+ this.qiniu = this.table('qiniu')
+ this.github = this.table('github')
+ this.smms = this.table('smms')
+ this.upyun = this.table('upyun')
+ this.imgur = this.table('imgur')
+ this.s3plist = this.table('s3plist')
+ }
+}
+
+export const fileCacheDbInstance = new FileCacheDb()
diff --git a/src/renderer/manage/store/manageStore.ts b/src/renderer/manage/store/manageStore.ts
new file mode 100644
index 0000000..86f285f
--- /dev/null
+++ b/src/renderer/manage/store/manageStore.ts
@@ -0,0 +1,47 @@
+import { defineStore } from 'pinia'
+import { getConfig } from '../utils/dataSender'
+
+export const useManageStore = defineStore('manageConfig', {
+ state: () => {
+ return {
+ config: {} as IStringKeyMap
+ }
+ },
+ actions: {
+ async refreshConfig () {
+ this.config = await getConfig() ?? {}
+ }
+ },
+ persist: true
+})
+
+export const useFileTransferStore = defineStore('fileTransfer', {
+ state: () => {
+ return {
+ fileTransferList: [] as IStringKeyMap[],
+ success: false,
+ finished: false
+ }
+ },
+ actions: {
+ refreshFileTransferList (newData: IStringKeyMap) {
+ this.fileTransferList = newData.fullList ?? []
+ this.success = newData.success
+ this.finished = newData.finished
+ },
+ resetFileTransferList () {
+ this.fileTransferList = []
+ this.success = false
+ this.finished = false
+ },
+ getFileTransferList () {
+ return this.fileTransferList
+ },
+ isFinished () {
+ return this.finished
+ },
+ isSuccess () {
+ return this.success
+ }
+ }
+})
diff --git a/src/renderer/manage/utils/common.ts b/src/renderer/manage/utils/common.ts
new file mode 100644
index 0000000..376f1ff
--- /dev/null
+++ b/src/renderer/manage/utils/common.ts
@@ -0,0 +1,146 @@
+import { v4 as uuidv4 } from 'uuid'
+import path from 'path'
+import crypto from 'crypto'
+import { availableIconList } from './icon'
+
+export function randomStringGenerator (length: number): string {
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ return Array.from({ length }).map(() => chars.charAt(Math.floor(Math.random() * chars.length))).join('')
+}
+
+export function renameFileNameWithTimestamp (oldName: string): string {
+ return `${Math.floor(Date.now() / 1000)}${randomStringGenerator(5)}${path.extname(oldName)}`
+}
+
+export function renameFileNameWithRandomString (oldName: string, length: number = 5): string {
+ return `${randomStringGenerator(length)}${path.extname(oldName)}`
+}
+
+export function renameFileNameWithCustomString (oldName: string, customFormat: string): string {
+ const conversionMap : {[key: string]: () => string} = {
+ '{Y}': () => new Date().getFullYear().toString(),
+ '{y}': () => new Date().getFullYear().toString().slice(2),
+ '{m}': () => (new Date().getMonth() + 1).toString().length === 1 ? `0${new Date().getMonth() + 1}` : (new Date().getMonth() + 1).toString(),
+ '{d}': () => new Date().getDate().toString().length === 1 ? `0${new Date().getDate()}` : new Date().getDate().toString(),
+ '{md5}': () => crypto.createHash('md5').update(path.basename(oldName, path.extname(oldName))).digest('hex'),
+ '{md5-16}': () => crypto.createHash('md5').update(path.basename(oldName, path.extname(oldName))).digest('hex').slice(0, 16),
+ '{str-10}': () => randomStringGenerator(10),
+ '{str-20}': () => randomStringGenerator(20),
+ '{filename}': () => path.basename(oldName, path.extname(oldName)),
+ '{uuid}': () => uuidv4().replace(/-/g, ''),
+ '{timestamp}': () => Math.floor(Date.now() / 1000).toString()
+ }
+ if (customFormat === undefined || !Object.keys(conversionMap).some(item => customFormat.includes(item))) {
+ return oldName
+ }
+ const ext = path.extname(oldName)
+ return Object.keys(conversionMap).reduce((acc, cur) => {
+ return acc.replace(cur, conversionMap[cur]())
+ }, customFormat) + ext
+}
+
+export function renameFile (typeMap : IStringKeyMap, oldName: string): string {
+ if (typeMap.timestampRename) {
+ return renameFileNameWithTimestamp(oldName)
+ } else if (typeMap.randomStringRename) {
+ return renameFileNameWithRandomString(oldName, 20)
+ } else {
+ return renameFileNameWithCustomString(oldName, typeMap.customRenameFormat)
+ }
+}
+
+export function formatLink (url: string, fileName: string, type: string, format?: string) : string {
+ switch (type) {
+ case 'markdown':
+ return ``
+ case 'html':
+ return `
`
+ case 'bbcode':
+ return `[img]${url}[/img]`
+ case 'url':
+ return url
+ case 'markdown-with-link':
+ return `[](${url})`
+ case 'custom':
+ if (format && (format.includes('$url') || format.includes('$fileName'))) {
+ return format.replace(/\$url/g, url).replace(/\$fileName/g, fileName)
+ }
+ return url
+ default:
+ return url
+ }
+}
+
+export function getFileIconPath (fileName: string) {
+ const ext = path.extname(fileName).slice(1)
+ return availableIconList.includes(ext) ? `${ext}.png` : 'unknown.png'
+}
+
+export function formatFileSize (size: number) {
+ if (size === 0) return ''
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
+ const index = Math.floor(Math.log2(size) / 10)
+ return `${(size / Math.pow(2, index * 10)).toFixed(2)} ${units[index]}`
+}
+
+export function formatFileName (fileName: string) {
+ const ext = path.extname(fileName)
+ const name = path.basename(fileName, ext)
+ return name.length > 20 ? `${name.slice(0, 20)}...${ext}` : fileName
+}
+
+export function getExtension (fileName: string) {
+ return path.extname(fileName).slice(1)
+}
+
+export function isImage (fileName: string) {
+ return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico'].includes(getExtension(fileName))
+}
+
+export function formObjToTableData (obj: any) {
+ const exclude = [undefined, null, '', 'transformedConfig']
+ return Object.keys(obj).filter(key => !exclude.includes(obj[key])).map(key => ({
+ key,
+ value: typeof obj[key] === 'object' ? JSON.stringify(obj[key]) : obj[key]
+ })).sort((a, b) => a.key.localeCompare(b.key))
+}
+
+export function isValidUrl (str: string) {
+ const pattern = new RegExp(
+ '^([a-zA-Z]+:\\/\\/)?' +
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
+ '((\\d{1,3}\\.){3}\\d{1,3}))' +
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
+ '(\\?[;&a-z\\d%_.~+=-]*)?' +
+ '(\\#[-a-z\\d_]*)?$',
+ 'i'
+ )
+ return pattern.test(str)
+}
+
+export interface IHTTPProxy {
+ host: string
+ port: number
+ protocol: string
+}
+
+export const formatHttpProxy = (proxy: string | undefined, type: 'object' | 'string'): IHTTPProxy | undefined | string => {
+ if (proxy === undefined || proxy === '') return undefined
+ if (proxy.startsWith('http://') || proxy.startsWith('https://')) {
+ const { protocol, hostname, port } = new URL(proxy)
+ if (type === 'string') return `${protocol}//${hostname}:${port}`
+ return {
+ host: hostname,
+ port: Number(port),
+ protocol: protocol.slice(0, -1)
+ }
+ } else {
+ const [host, port] = proxy.split(':')
+ if (type === 'string') return `http://${host}:${port}`
+ return {
+ host,
+ port: port ? Number(port) : 80,
+ protocol: 'http'
+ }
+ }
+}
diff --git a/src/renderer/manage/utils/constants.ts b/src/renderer/manage/utils/constants.ts
new file mode 100644
index 0000000..a487f51
--- /dev/null
+++ b/src/renderer/manage/utils/constants.ts
@@ -0,0 +1,501 @@
+
+const defaultBaseRule = (name: string) => {
+ return [
+ {
+ required: true,
+ message: `请输入${name}`,
+ trigger: 'blur'
+ }
+ ]
+}
+
+const itemsPerPageRule = [
+ {
+ required: true,
+ message: '请输入每页显示数量',
+ trigger: 'blur'
+ },
+ {
+ type: 'number',
+ message: '每页显示数量必须为数字',
+ trigger: 'change'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ if (value < 20 || value > 1000) {
+ callback(new Error('每页显示数量必须在20-1000之间'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+]
+
+const aliasRule = [
+ {
+ required: true,
+ message: '请输入配置别名, 该配置的唯一标识',
+ trigger: 'blur'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ const reg = /^[\u4e00-\u9fa5_a-zA-Z0-9-]{1,15}$/
+ if (!reg.test(value)) {
+ callback(new Error('配置别名只能包含中文、英文、数字和下划线,且不能超过15个字符'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+]
+
+export const supportedPicBedList: IStringKeyMap = {
+ smms: {
+ name: 'SM.MS',
+ icon: 'smms',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'smms-A'
+ },
+ token: {
+ required: true,
+ description: 'token-必需',
+ placeholder: '请输入token',
+ type: 'string',
+ rule: defaultBaseRule('token')
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: true,
+ type: 'boolean'
+ }
+ },
+ explain: '大陆地区请访问备用域名https://smms.app, 请勿大批量上传图片,否则API接口会被限制',
+ options: ['alias', 'token', 'paging'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-6',
+ referenceText: '配置教程请参考:'
+ },
+ qiniu: {
+ name: '七牛云',
+ icon: 'qiniu',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'qiniu-A'
+ },
+ accessKey: {
+ required: true,
+ description: 'accessKey-必需',
+ placeholder: '请输入accessKey',
+ type: 'string',
+ rule: defaultBaseRule('accessKey')
+ },
+ secretKey: {
+ required: true,
+ description: 'secretKey-必需',
+ placeholder: '请输入secretKey',
+ type: 'string',
+ rule: defaultBaseRule('secretKey')
+ },
+ bucketName: {
+ required: false,
+ description: '空间名-可选',
+ placeholder: '英文逗号分隔,例如:bucket1,bucket2',
+ type: 'string'
+ },
+ baseDir: {
+ required: false,
+ description: '起始目录-可选',
+ placeholder: '英文逗号分隔,例如:/test1,/test2',
+ default: '/',
+ type: 'string'
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: true,
+ type: 'boolean'
+ },
+ itemsPerPage: {
+ required: true,
+ description: '每页显示数量',
+ default: 50,
+ type: 'number',
+ rule: itemsPerPageRule
+ }
+ },
+ explain: '空间名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
+ options: ['alias', 'accessKey', 'secretKey', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-3',
+ referenceText: '配置教程请参考:'
+ },
+ github: {
+ name: 'GitHub',
+ icon: 'github',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'github-A'
+ },
+ token: {
+ required: true,
+ description: 'token-必需',
+ placeholder: '请输入token',
+ type: 'string',
+ rule: defaultBaseRule('token')
+ },
+ githubUsername: {
+ required: true,
+ description: '用户名-必需',
+ placeholder: '请输入用户名',
+ type: 'string',
+ rule: defaultBaseRule('用户名')
+ },
+ proxy: {
+ required: false,
+ description: '代理-可选',
+ placeholder: '例如:http://127.0.0.1:1080',
+ type: 'string'
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: false,
+ type: 'boolean'
+ },
+ customUrl: {
+ required: false,
+ description: 'CDN加速域名-可选;例如: https://cdn.staticaly.com/gh/{username}/{repo}@{branch}/{path}',
+ placeholder: '支持使用{username}、{repo}、{branch}和{path}作为替换占位符,用于适配不同仓库和分支',
+ type: 'string',
+ rule: [
+ {
+ validator: (_rule: any, value: any, callback: any) => {
+ if (value) {
+ const customUrlList = value.split(',')
+ const customUrlValid = customUrlList.every((customUrl: string) => {
+ const reg = /^((https|http)?:\/\/)/
+ if (customUrl === '') {
+ return true
+ } else if (!reg.test(customUrl)) {
+ return false
+ }
+ return true
+ })
+ const isBracketsValid = customUrlList.every((customUrl: string) => {
+ const bracketPaired = (str: string) => {
+ const stack = []
+ for (let i = 0; i < str.length; i++) {
+ if (str[i] === '{') {
+ stack.push(str[i])
+ } else if (str[i] === '}') {
+ if (stack.length === 0) {
+ return false
+ }
+ stack.pop()
+ }
+ }
+ return stack.length === 0
+ }
+ if (customUrl === '') {
+ return true
+ } else if (!bracketPaired(customUrl)) {
+ return false
+ }
+ return true
+ })
+ if (!customUrlValid) {
+ callback(new Error('加速域名请以http://或https://开头'))
+ } else if (!isBracketsValid) {
+ callback(new Error('加速域名中的大括号必须成对出现'))
+ } else {
+ callback()
+ }
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ }
+ },
+ explain: 'API调用有每小时上限,此外不支持上传超过100M的文件',
+ options: ['alias', 'token', 'githubUsername', 'proxy', 'customUrl'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-9',
+ referenceText: '配置教程请参考:'
+ },
+ aliyun: {
+ name: '阿里云',
+ icon: 'aliyun',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'aliyun-A'
+ },
+ accessKeyId: {
+ required: true,
+ description: 'accessKeyId-必需',
+ placeholder: '请输入accessKeyId',
+ type: 'string',
+ rule: defaultBaseRule('accessKeyId')
+ },
+ accessKeySecret: {
+ required: true,
+ description: 'accessKeySecret-必需',
+ placeholder: '请输入accessKeySecret',
+ type: 'string',
+ rule: defaultBaseRule('accessKeySecret')
+ },
+ bucketName: {
+ required: false,
+ description: '存储桶名-可选',
+ placeholder: '英文逗号分隔,例如:bucket1,bucket2',
+ type: 'string'
+ },
+ baseDir: {
+ required: false,
+ description: '起始目录-可选',
+ placeholder: '英文逗号分隔,例如:/test1,/test2',
+ type: 'string',
+ default: '/'
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: true,
+ type: 'boolean'
+ },
+ itemsPerPage: {
+ required: true,
+ description: '每页显示数量',
+ default: 50,
+ type: 'number',
+ rule: itemsPerPageRule
+ }
+ },
+ explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
+ options: ['alias', 'accessKeyId', 'accessKeySecret', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-1',
+ referenceText: '配置教程请参考:'
+ },
+ tcyun: {
+ name: '腾讯云',
+ icon: 'tcyun',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'tcyun-A'
+ },
+ secretId: {
+ required: true,
+ description: 'secretId-必需',
+ placeholder: '请输入secretId',
+ type: 'string',
+ rule: defaultBaseRule('secretId')
+ },
+ secretKey: {
+ required: true,
+ description: 'secretKey-必需',
+ placeholder: '请输入secretKey',
+ type: 'string',
+ rule: defaultBaseRule('secretKey')
+ },
+ appId: {
+ required: true,
+ description: 'appId-必需',
+ placeholder: '请输入appId',
+ type: 'string',
+ rule: defaultBaseRule('appId')
+ },
+ bucketName: {
+ required: false,
+ description: '存储桶名-可选(注意包含AppId)',
+ placeholder: '英文逗号分隔,例如:bucket1-1250000000,bucket2-1250000000',
+ type: 'string'
+ },
+ baseDir: {
+ required: false,
+ description: '起始目录-可选',
+ placeholder: '英文逗号分隔,例如:/test1,/test2',
+ type: 'string',
+ default: '/'
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: true,
+ type: 'boolean'
+ },
+ itemsPerPage: {
+ required: true,
+ description: '每页显示数量',
+ default: 50,
+ type: 'number',
+ rule: itemsPerPageRule
+ }
+ },
+ explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
+ options: ['alias', 'secretId', 'secretKey', 'appId', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-2',
+ referenceText: '配置教程请参考:'
+ },
+ upyun: {
+ name: '又拍云',
+ icon: 'upyun',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'upyun-A'
+ },
+ bucketName: {
+ required: true,
+ description: '服务名-必需',
+ placeholder: '对应其它对象存储的存储桶名',
+ type: 'string',
+ rule: defaultBaseRule('bucketName')
+ },
+ operator: {
+ required: true,
+ description: '操作员-必需',
+ placeholder: '推荐使用具有读取、写入和删除完整权限的操作员',
+ type: 'string',
+ rule: defaultBaseRule('操作员')
+ },
+ password: {
+ required: true,
+ description: '操作员密码-必需',
+ placeholder: '请输入密码',
+ type: 'string',
+ rule: defaultBaseRule('操作员密码')
+ },
+ baseDir: {
+ required: false,
+ description: '起始目录-可选',
+ placeholder: '读取文件时的初始目录',
+ type: 'string',
+ default: '/'
+ },
+ customUrl: {
+ required: true,
+ description: '加速域名-必需',
+ placeholder: '请以http://或https://开头',
+ type: 'string',
+ rule: [
+ {
+ required: true,
+ message: '加速域名不能为空',
+ trigger: 'change'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ if (value) {
+ const customUrlList = value.split(',')
+ const customUrlValid = customUrlList.every((customUrl: string) => {
+ const reg = /^((https|http)?:\/\/)/
+ if (customUrl === '') {
+ return true
+ } else if (!reg.test(customUrl)) {
+ return false
+ }
+ return true
+ })
+ if (!customUrlValid) {
+ callback(new Error('自定义域名请以http://或https://开头'))
+ } else {
+ callback()
+ }
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ },
+ paging: {
+ required: true,
+ description: '是否分页',
+ default: true,
+ type: 'boolean'
+ },
+ itemsPerPage: {
+ required: true,
+ description: '每页显示数量',
+ default: 50,
+ type: 'number',
+ rule: itemsPerPageRule
+ }
+ },
+ explain: '又拍云图床务必填写加速域名,否则无法正常使用',
+ options: ['alias', 'bucketName', 'operator', 'password', 'baseDir', 'customUrl', 'paging', 'itemsPerPage'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-4',
+ referenceText: '配置教程请参考:'
+ },
+ imgur: {
+ name: 'Imgur',
+ icon: 'imgur',
+ configOptions: {
+ alias: {
+ required: true,
+ description: '配置别名-必需',
+ placeholder: '该配置的唯一标识',
+ type: 'string',
+ rule: aliasRule,
+ default: 'imgur-A'
+ },
+ imgurUserName: {
+ required: true,
+ description: 'imgur用户名-必需',
+ placeholder: '请输入imgur用户名',
+ type: 'string',
+ rule: defaultBaseRule('imgurUserName')
+ },
+ accessToken: {
+ required: true,
+ description: 'accessToken-必需(不是clientID,请参考配置教程)',
+ placeholder: '请输入accessToken',
+ type: 'string',
+ rule: defaultBaseRule('accessToken')
+ },
+ proxy: {
+ required: false,
+ description: '代理-可选',
+ placeholder: '例如:http://127.0.0.1:1080',
+ type: 'string'
+ }
+ },
+ explain: '大陆地区请使用代理,API调用存在限制,请注意使用频率',
+ options: ['alias', 'imgurUserName', 'accessToken', 'proxy'],
+ refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=imgur%e5%9b%be%e5%ba%8a-1',
+ referenceText: '配置教程请参考:'
+ }
+}
diff --git a/src/renderer/manage/utils/dataSender.ts b/src/renderer/manage/utils/dataSender.ts
new file mode 100644
index 0000000..887138f
--- /dev/null
+++ b/src/renderer/manage/utils/dataSender.ts
@@ -0,0 +1,44 @@
+import { ipcRenderer, IpcRendererEvent } from 'electron'
+import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from '~/main/manage/events/constants'
+import { v4 as uuid } from 'uuid'
+import { getRawData } from '~/renderer/utils/common'
+
+export function getConfig (key?: string): Promise {
+ return new Promise((resolve) => {
+ const callbackId = uuid()
+ const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => {
+ if (returnCallbackId === callbackId) {
+ resolve(config)
+ ipcRenderer.removeListener(PICLIST_MANAGE_GET_CONFIG, callback)
+ }
+ }
+ ipcRenderer.on(PICLIST_MANAGE_GET_CONFIG, callback)
+ ipcRenderer.send(PICLIST_MANAGE_GET_CONFIG, key, callbackId)
+ })
+}
+
+export function saveConfig (_config: IObj | string, value?: any) {
+ let config
+ if (typeof _config === 'string') {
+ config = {
+ [_config]: value
+ }
+ } else {
+ config = getRawData(_config)
+ }
+ ipcRenderer.send(PICLIST_MANAGE_SAVE_CONFIG, config)
+}
+
+export function removeConfig (key: string, propName: string) {
+ ipcRenderer.send(PICLIST_MANAGE_REMOVE_CONFIG, key, propName)
+}
+
+export function sendToMain (channel: string, ...args: any[]) {
+ const data = getRawData(args)
+ ipcRenderer.send(channel, ...data)
+}
+
+export function invokeToMain (channel: string, ...args: any[]) {
+ const data = getRawData(args)
+ return ipcRenderer.invoke(channel, ...data)
+}
diff --git a/src/renderer/manage/utils/icon.ts b/src/renderer/manage/utils/icon.ts
new file mode 100644
index 0000000..cd47e91
--- /dev/null
+++ b/src/renderer/manage/utils/icon.ts
@@ -0,0 +1,224 @@
+export const availableIconList = [
+ '_blank',
+ '_page',
+ '3g2',
+ '3gp',
+ '7z',
+ 'aac',
+ 'accdb',
+ 'adt',
+ 'ai',
+ 'aiff',
+ 'aly',
+ 'amiga',
+ 'amr',
+ 'ape',
+ 'apk',
+ 'arj',
+ 'asf',
+ 'asm',
+ 'asx',
+ 'au',
+ 'avc',
+ 'avi',
+ 'avs',
+ 'bak',
+ 'bas',
+ 'bat',
+ 'bmp',
+ 'bom',
+ 'c',
+ 'cda',
+ 'cdr',
+ 'chm',
+ 'class',
+ 'cmd',
+ 'com',
+ 'cpp',
+ 'css',
+ 'csv',
+ 'dart',
+ 'dat',
+ 'ddb',
+ 'dif',
+ 'divx',
+ 'dll',
+ 'dmg',
+ 'doc',
+ 'docm',
+ 'docx',
+ 'dot',
+ 'dotm',
+ 'dotx',
+ 'dsl',
+ 'dv',
+ 'dvd',
+ 'dvdaudio',
+ 'dwg',
+ 'dxf',
+ 'emf',
+ 'env',
+ 'eot',
+ 'eps',
+ 'exe',
+ 'exif',
+ 'fakesmms',
+ 'flc',
+ 'fli',
+ 'flv',
+ 'folder',
+ 'fon',
+ 'font',
+ 'for',
+ 'fpx',
+ 'fv',
+ 'gif',
+ 'gitingore',
+ 'gitkeep',
+ 'gz',
+ 'h',
+ 'hdri',
+ 'hlp',
+ 'hpp',
+ 'htm',
+ 'html',
+ 'ico',
+ 'ics',
+ 'int',
+ 'ipynb',
+ 'iso',
+ 'java',
+ 'jpeg',
+ 'jpg',
+ 'js',
+ 'json',
+ 'key',
+ 'ksp',
+ 'less',
+ 'lib',
+ 'lic',
+ 'license',
+ 'log',
+ 'lst',
+ 'lua',
+ 'mac',
+ 'map',
+ 'markdown',
+ 'md',
+ 'mdf',
+ 'mht',
+ 'mhtml',
+ 'mid',
+ 'midi',
+ 'mkv',
+ 'mmf',
+ 'mod',
+ 'mov',
+ 'mp2',
+ 'mp3',
+ 'mp4',
+ 'mpa',
+ 'mpe',
+ 'mpeg',
+ 'mpeg1',
+ 'mpeg2',
+ 'mpg',
+ 'mppro',
+ 'msg',
+ 'mts',
+ 'mux',
+ 'mv',
+ 'navi',
+ 'obj',
+ 'odf',
+ 'ods',
+ 'odt',
+ 'ogg',
+ 'one',
+ 'otf',
+ 'otp',
+ 'ots',
+ 'ott',
+ 'pas',
+ 'pcd',
+ 'pcx',
+ 'pdf',
+ 'php',
+ 'pic',
+ 'png',
+ 'ppt',
+ 'pptx',
+ 'proe',
+ 'prt',
+ 'psd',
+ 'py',
+ 'pyc',
+ 'qsv',
+ 'qt',
+ 'quicktime',
+ 'ra',
+ 'ram',
+ 'rar',
+ 'raw',
+ 'rb',
+ 'realaudio',
+ 'rm',
+ 'rmvb',
+ 'rp',
+ 'rtf',
+ 's48',
+ 'sacd',
+ 'sass',
+ 'sch',
+ 'scss',
+ 'sh',
+ 'sql',
+ 'stp',
+ 'svcd',
+ 'svg',
+ 'swf',
+ 'sys',
+ 'tga',
+ 'tgz',
+ 'tiff',
+ 'tmp',
+ 'ts',
+ 'ttc',
+ 'ttf',
+ 'txt',
+ 'ufo',
+ 'unknown',
+ 'vcd',
+ 'vob',
+ 'voc',
+ 'vqf',
+ 'vue',
+ 'wav',
+ 'wdl',
+ 'webm',
+ 'webp',
+ 'wki',
+ 'wma',
+ 'wmf',
+ 'wmv',
+ 'wmvhd',
+ 'woff',
+ 'woff2',
+ 'wps',
+ 'wpt',
+ 'x_t',
+ 'xls',
+ 'xlsm',
+ 'xlsx',
+ 'xlt',
+ 'xltm',
+ 'xltx',
+ 'xmind',
+ 'xml',
+ 'xv',
+ 'xvid',
+ 'yaml',
+ 'yml',
+ 'z',
+ 'zip'
+]
diff --git a/src/renderer/manage/utils/newBucketConfig.ts b/src/renderer/manage/utils/newBucketConfig.ts
new file mode 100644
index 0000000..3ab6320
--- /dev/null
+++ b/src/renderer/manage/utils/newBucketConfig.ts
@@ -0,0 +1,227 @@
+import { AliyunAreaCodeName, QiniuAreaCodeName, TencentAreaCodeName } from '~/main/manage/utils/constants'
+
+export const newBucketConfig:IStringKeyMap = {
+ tcyun: {
+ name: '腾讯云',
+ icon: 'tcyun',
+ configOptions: {
+ BucketName: {
+ required: true,
+ description: 'Bucket名称',
+ placeholder: '请输入Bucket名称',
+ paraType: 'string',
+ component: 'input',
+ default: 'piclist',
+ rule: [
+ {
+ required: true,
+ message: 'Bucket名称不能为空',
+ trigger: 'blur'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ const reg = /^[a-z0-9][a-z0-9-]{1,21}[a-z0-9]$/
+ if (value.length > 23) {
+ callback(new Error('Bucket名称长度不能超过23个字符'))
+ } else if (!reg.test(value)) {
+ callback(new Error('Bucket名称只能包含小写字母、数字和中划线,且不能以中划线开头和结尾'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ },
+ region: {
+ required: true,
+ description: '地域',
+ paraType: 'string',
+ component: 'select',
+ default: 'ap-nanjing',
+ options: TencentAreaCodeName
+ },
+ acl: {
+ required: true,
+ description: '访问权限',
+ paraType: 'string',
+ component: 'select',
+ default: 'private',
+ options: {
+ private: '私有',
+ publicRead: '公共读',
+ publicReadWrite: '公共读写'
+ }
+ }
+ },
+ options: ['BucketName', 'region', 'acl']
+ },
+ aliyun: {
+ name: '阿里云',
+ icon: 'aliyun',
+ configOptions: {
+ BucketName: {
+ required: true,
+ description: 'Bucket名称',
+ placeholder: '请输入Bucket名称',
+ paraType: 'string',
+ component: 'input',
+ default: 'piclist',
+ rule: [
+ {
+ required: true,
+ message: 'Bucket名称不能为空',
+ trigger: 'blur'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ const reg = /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/
+ if (value.length > 63) {
+ callback(new Error('Bucket名称长度不能超过63个字符'))
+ } else if (!reg.test(value)) {
+ callback(new Error('Bucket名称只能包含小写字母、数字和中划线,且不能以中划线开头和结尾'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ },
+ region: {
+ required: true,
+ description: '地域',
+ paraType: 'string',
+ component: 'select',
+ default: 'oss-cn-hangzhou',
+ options: AliyunAreaCodeName
+ },
+ acl: {
+ required: true,
+ description: '访问权限',
+ paraType: 'string',
+ component: 'select',
+ default: 'private',
+ options: {
+ private: '私有',
+ publicRead: '公共读',
+ publicReadWrite: '公共读写'
+ }
+ }
+ },
+ options: ['BucketName', 'region', 'acl']
+ },
+ qiniu: {
+ name: '七牛云',
+ icon: 'qiniu',
+ configOptions: {
+ BucketName: {
+ required: true,
+ description: 'Bucket名称',
+ placeholder: '请输入Bucket名称',
+ paraType: 'string',
+ component: 'input',
+ default: 'piclist',
+ rule: [
+ {
+ required: true,
+ message: 'Bucket名称不能为空',
+ trigger: 'blur'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ const reg = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/
+ if (value.length > 63) {
+ callback(new Error('Bucket名称长度不能超过63个字符'))
+ } else if (!reg.test(value)) {
+ callback(new Error('Bucket名称只能包含小写字母、数字和中划线,且不能以中划线开头和结尾'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ },
+ region: {
+ required: true,
+ description: '地域',
+ paraType: 'string',
+ component: 'select',
+ default: 'z0',
+ options: QiniuAreaCodeName
+ },
+ acl: {
+ required: true,
+ description: '公开访问',
+ paraType: 'boolean',
+ component: 'switch',
+ default: false
+ }
+ },
+ options: ['BucketName', 'region', 'acl']
+ },
+ upyun: {
+ name: '又拍云',
+ icon: 'upyun',
+ configOptions: {
+ BucketName: {
+ required: true,
+ description: 'Bucket名称',
+ placeholder: '请输入Bucket名称',
+ paraType: 'string',
+ component: 'input',
+ default: 'piclist',
+ rule: [
+ {
+ required: true,
+ message: 'Bucket名称不能为空',
+ trigger: 'blur'
+ },
+ {
+ validator: (rule: any, value: any, callback: any) => {
+ const reg = /^[a-z][a-z0-9-]{4,19}$/
+ if (value.length > 23 || value.length < 5) {
+ callback(new Error('Bucket名称长度为5-20个字符'))
+ } else if (!reg.test(value)) {
+ callback(new Error('Bucket名称只能包含小写字母、数字和中划线,且不能以中划线开头和结尾'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'change'
+ }
+ ]
+ },
+ operator: {
+ required: true,
+ description: '操作员',
+ placeholder: '请输入操作员',
+ paraType: 'string',
+ component: 'input',
+ rule: [
+ {
+ required: true,
+ message: '操作员不能为空',
+ trigger: 'blur'
+ }
+ ]
+ },
+ password: {
+ required: true,
+ description: '密码',
+ placeholder: '请输入密码',
+ paraType: 'string',
+ component: 'input',
+ rule: [
+ {
+ required: true,
+ message: '密码不能为空',
+ trigger: 'blur'
+ }
+ ]
+ }
+ },
+ options: ['BucketName', 'operator', 'password']
+ }
+}
diff --git a/src/renderer/pages/Gallery.vue b/src/renderer/pages/Gallery.vue
index 10323e7..491764c 100644
--- a/src/renderer/pages/Gallery.vue
+++ b/src/renderer/pages/Gallery.vue
@@ -10,6 +10,15 @@
+ 同步删除云端:
+
@@ -79,7 +88,7 @@
{{ $T('COPY') }}
@@ -88,7 +97,7 @@
{{ $T('DELETE') }}
@@ -97,10 +106,10 @@
- {{ isAllSelected ? $T('CANCEL') : $T('SELECT_ALL') }}
+ {{ isAllSelected? $T('CANCEL'): $T('SELECT_ALL') }}
@@ -128,7 +137,11 @@
@@ -187,9 +200,7 @@
:modal-append-to-body="false"
>
-
+
{{ $T('CANCEL') }}
@@ -206,7 +217,7 @@
@@ -623,7 +697,7 @@ export default {
.pull-right
float right
.gallery-list
- height 360px
+ height 100%
box-sizing border-box
padding 8px 0
overflow-y auto
@@ -633,7 +707,7 @@ export default {
transition all .2s ease-in-out .1s
width 100%
&.small
- height: 287px
+ height: 100%
top: 113px
&__img
// height 150px
diff --git a/src/renderer/pages/MiniPage.vue b/src/renderer/pages/MiniPage.vue
index 3189350..7a2403e 100644
--- a/src/renderer/pages/MiniPage.vue
+++ b/src/renderer/pages/MiniPage.vue
@@ -4,7 +4,6 @@
:style="{ backgroundImage: 'url(' + logo + ')' }"
:class="{ linux: os === 'linux' }"
>
-