Update On Mon Apr 1 20:27:02 CEST 2024

This commit is contained in:
github-action[bot]
2024-04-01 20:27:03 +02:00
parent 26fcc9cf4f
commit 7e9c836f6c
1162 changed files with 74819 additions and 72622 deletions

1
.github/update.log vendored
View File

@@ -603,3 +603,4 @@ Update On Wed Mar 27 19:27:19 CET 2024
Update On Thu Mar 28 19:29:20 CET 2024
Update On Sat Mar 30 19:27:32 CET 2024
Update On Sun Mar 31 20:25:39 CEST 2024
Update On Mon Apr 1 20:26:51 CEST 2024

View File

@@ -1,86 +0,0 @@
name: Bug report
description: Problems with the software
title: '[Bug] {{请输入标题,不要留空 - Please enter a title, do not leave it blank.}}'
labels: ['bug']
body:
- type: markdown
attributes:
value: |
**非常感谢您的反馈Thank you very much for your feedback!**
有关讨论、建议或者咨询的内容请去往[讨论区](https://github.com/gaozhangmin/aliyunpan/discussions)。
For suggestions or help, please consider using [Github Discussion](https://github.com/gaozhangmin/aliyunpan/discussions) instead.
- type: checkboxes
attributes:
label: Please search before asking
description: |
辛苦提 Bug 前,检索一下 [问题](https://github.com/gaozhangmin/aliyunpan/issues?q=) 列表是否已经存在类似问题。麻烦提供系统版本、录屏或者截图、复现步骤,有助于更好的解决问题。
非 Bug 相关,烦请移步 [讨论区](https://github.com/gaozhangmin/aliyunpan/discussions) 找寻有关讨论。
Please search [issues](https://github.com/gaozhangmin/aliyunpan/issues) to check if your issue has already been reported.
Not related to bugs, please go to the [discussion area](https://github.com/gaozhangmin/aliyunpan/discussions) for relevant discussions.
options:
- label: >
I searched in the [issues](https://github.com/gaozhangmin/aliyunpan/issues) and found nothing similar.
required: true
- type: checkboxes
attributes:
label: Please read README
description: |
辛苦提 Bug 前,请仔细阅读一下 README 中的 [Troubleshooting](https://github.com/gaozhangmin/aliyunpan/tree/main#troubleshooting) 是否已经给出相关解决方案
Before reporting bugs (especially for issues such as missing icons in the desktop application, permission pop-ups, and damaged report files), please carefully read the [Troubleshooting](https://github.com/gaozhangmin/aliyunpan/tree/main#troubleshooting) section in README to see if relevant solutions have already been provided.
options:
- label: I have read the troubleshooting section in the README in detail.
required: true
- type: input
attributes:
label: 使用的版本
description: >
请提供您正在使用的 小白羊 的版本。Please provide the version of 小白羊 you are using. For example, `3.11.7`.
validations:
required: true
- type: input
attributes:
label: 系统 System
description: >
请提供您正在使用的系统。Please provide the version of the System you are using. For example, `macOS 11.2.3`.
validations:
required: true
- type: textarea
attributes:
label: 复现步骤 Reproduce step
description: >
请提供完整且简明的复现步骤以方便及时定位并解决问题。Please provide complete and concise reproduction steps to facilitate timely identification and resolution of the issue.
validations:
required: true
- type: textarea
attributes:
label: 你看到了什么错误What errors do you see?
validations:
required: true
- type: textarea
attributes:
label: 你期望看到什么What did you expect to see?
validations:
required: true
- type: textarea
attributes:
label: 还有其他的内容吗Anything else?
- type: checkboxes
attributes:
label: 你是否愿意提交一份 PR 来修改这个错误Are you willing to submit a PR?
description: >
我们期待开发人员和用户的帮助,以解决在 小白羊 中发现的任何问题。 如果您愿意通过提交 PR 来解决此问题请勾选。We eagerly anticipate developers' and users' support and collaboration in resolving any issues found in 小白羊. If you are willing to offer a solution by submitting a PR to fix this matter, kindly mark the checkbox provided.
options:
- label: 我愿意提供 PR! I'm willing to submit a PR!
- type: markdown
attributes:
value: '非常感谢您的反馈Thank you very much for your feedback!'

View File

@@ -0,0 +1,86 @@
name: Bug report
description: Problems with the software
title: '[Bug] {{请输入标题,不要留空 - Please enter a title, do not leave it blank.}}'
labels: ['bug']
body:
- type: markdown
attributes:
value: |
**非常感谢您的反馈Thank you very much for your feedback!**
有关讨论、建议或者咨询的内容请去往[讨论区](https://github.com/gaozhangmin/aliyunpan/discussions)。
For suggestions or help, please consider using [Github Discussion](https://github.com/gaozhangmin/aliyunpan/discussions) instead.
- type: checkboxes
attributes:
label: Please search before asking
description: |
辛苦提 Bug 前,检索一下 [问题](https://github.com/gaozhangmin/aliyunpan/issues?q=) 列表是否已经存在类似问题。麻烦提供系统版本、录屏或者截图、复现步骤,有助于更好的解决问题。
非 Bug 相关,烦请移步 [讨论区](https://github.com/gaozhangmin/aliyunpan/discussions) 找寻有关讨论。
Please search [issues](https://github.com/gaozhangmin/aliyunpan/issues) to check if your issue has already been reported.
Not related to bugs, please go to the [discussion area](https://github.com/gaozhangmin/aliyunpan/discussions) for relevant discussions.
options:
- label: >
I searched in the [issues](https://github.com/gaozhangmin/aliyunpan/issues) and found nothing similar.
required: true
- type: checkboxes
attributes:
label: Please read README
description: |
辛苦提 Bug 前,请仔细阅读一下 README 中的 [Troubleshooting](https://github.com/gaozhangmin/aliyunpan/tree/main#troubleshooting) 是否已经给出相关解决方案
Before reporting bugs (especially for issues such as missing icons in the desktop application, permission pop-ups, and damaged report files), please carefully read the [Troubleshooting](https://github.com/gaozhangmin/aliyunpan/tree/main#troubleshooting) section in README to see if relevant solutions have already been provided.
options:
- label: I have read the troubleshooting section in the README in detail.
required: true
- type: input
attributes:
label: 使用的版本
description: >
请提供您正在使用的 小白羊 的版本。Please provide the version of 小白羊 you are using. For example, `3.11.7`.
validations:
required: true
- type: input
attributes:
label: 系统 System
description: >
请提供您正在使用的系统。Please provide the version of the System you are using. For example, `macOS 11.2.3`.
validations:
required: true
- type: textarea
attributes:
label: 复现步骤 Reproduce step
description: >
请提供完整且简明的复现步骤以方便及时定位并解决问题。Please provide complete and concise reproduction steps to facilitate timely identification and resolution of the issue.
validations:
required: true
- type: textarea
attributes:
label: 你看到了什么错误What errors do you see?
validations:
required: true
- type: textarea
attributes:
label: 你期望看到什么What did you expect to see?
validations:
required: true
- type: textarea
attributes:
label: 还有其他的内容吗Anything else?
- type: checkboxes
attributes:
label: 你是否愿意提交一份 PR 来修改这个错误Are you willing to submit a PR?
description: >
我们期待开发人员和用户的帮助,以解决在 小白羊 中发现的任何问题。 如果您愿意通过提交 PR 来解决此问题请勾选。We eagerly anticipate developers' and users' support and collaboration in resolving any issues found in 小白羊. If you are willing to offer a solution by submitting a PR to fix this matter, kindly mark the checkbox provided.
options:
- label: 我愿意提供 PR! I'm willing to submit a PR!
- type: markdown
attributes:
value: '非常感谢您的反馈Thank you very much for your feedback!'

92
aliyunpan/.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Build
on:
push:
tags:
- '*.*.*'
jobs:
create-release:
permissions:
contents: write
runs-on: ubuntu-20.04
outputs:
release_id: ${{ steps.create-release.outputs.id }}
release_upload_url: ${{ steps.create-release.outputs.upload_url }}
steps:
- uses: actions/checkout@v3
- name: Get version
id: get_version
uses: battila7/get-version-action@v2
- name: Create Release
id: create-release
uses: ncipollo/release-action@v1
with:
draft: true
name: ${{ steps.get_version.outputs.version }}
tag: ${{ steps.get_version.outputs.version }}
body: "${{ steps.tag.outputs.message }}"
release:
needs: create-release
name: build and release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
permissions:
contents: write
steps:
- name: Checkout Git repository
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v3
id: cache-yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v3
id: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-nodemodules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.node-version }}-nodemodules-
- name: Install Dependencies
if: |
steps.cache-yarn-cache.outputs.cache-hit != 'true' ||
steps.cache-node-modules.outputs.cache-hit != 'true'
run: |
npm install -g yarn
yarn install
- name: Make Aria2c Executable
if: matrix.os != 'windows-latest'
run: |
chmod -R 777 ./static/engine/
- name: Build Electron app
uses: samuelmeuli/action-electron-builder@v1.6.0
with:
release: ${{ startsWith(github.ref, 'refs/tags') }}
github_token: ${{ secrets.github_token }}

View File

@@ -1,23 +0,0 @@
# https://github.com/actions/stale
name: autoCloseIssue
on:
schedule:
# 每5天北京时间9点
- cron: '30 1 1/5 * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: '由于该问题长期没有更新将于5天后自动关闭。如有需要可重新打开。'
days-before-stale: 30
days-before-close: 5
operations-per-run: 100

View File

@@ -1,101 +0,0 @@
name: Build Release
on:
push:
branches:
- main
paths:
- 'package.json'
jobs:
release:
name: Build Release
strategy:
fail-fast: false
matrix:
os: [ windows-latest, macos-latest, ubuntu-latest ]
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- name: Checkout Git repository
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: 18
registry-url: https://registry.npmjs.org/
- uses: pnpm/action-setup@v3
name: Install pnpm
id: pnpm-install
with:
version: 8
run_install: false
- name: Get Pnpm Store Directory
shell: bash
run: |
echo "PNPM_STORE_PATH=$(pnpm store path)" >> $GITHUB_ENV
- name: Get Electron Store Directory
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
echo "ELECTRON_STORE_PATH=$LOCALAPPDATA\\electron\\cache" >> $GITHUB_ENV
echo "ELECTRON_BUILDER_STORE_PATH=$LOCALAPPDATA\\electron-builder\\cache" >> $GITHUB_ENV
elif [[ "${{ runner.os }}" == "macOS" ]]; then
echo "ELECTRON_STORE_PATH=$HOME/Library/Caches/electron" >> $GITHUB_ENV
else
echo "ELECTRON_STORE_PATH=$HOME/.cache/electron" >> $GITHUB_ENV
echo "ELECTRON_BUILDER_STORE_PATH=$HOME/.cache/electron-builder" >> $GITHUB_ENV
fi
- name: Setup Pnpm Cache
uses: actions/cache@v4
with:
path: ${{ env.PNPM_STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Setup Cache Electron
uses: actions/cache@v4
with:
path: ${{ env.ELECTRON_STORE_PATH }}
key: ${{ runner.os }}-electron-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-cache-
- name: Setup Cache Electron Builder
uses: actions/cache@v4
if: matrix.os != 'macos-latest'
with:
path: ${{ env.ELECTRON_BUILDER_STORE_PATH }}
key: ${{ runner.os }}-electron-builder-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-electron-builder-cache-
- name: Install Dependencies
run: |
pnpm install
- name: Install Lib For Pacman Build
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get install libarchive-tools
- name: Make Aria2c Executable
if: matrix.os != 'windows-latest'
run: |
chmod -R 777 ./static/engine/
- name: Build Electron App
uses: paneron/action-electron-builder@v1.8.1
with:
package_manager: pnpm
release: true
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -9,5 +9,5 @@ tmp
release
.idea
yarn.lock
localVersion

View File

@@ -1,674 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
MIT License
Copyright (c) 2023 Zhangao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,5 @@
<p align="center">
<img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/95fb537d-411d-41aa-8f73-9dc8adab760e" alt="NebulaGraph Data Intelligence Suite(ngdi)">
<img src="https://github.com/gaozhangmin/staticResource/blob/master/images/icon.png" alt="NebulaGraph Data Intelligence Suite(ngdi)">
</p>
<p align="center">
<br> English | <a href="README-CN.md">中文</a>
@@ -44,11 +44,12 @@
</p>
[![](https://img.shields.io/badge/-%E5%8A%9F%E8%83%BD-blue)](#功能-) [![](https://img.shields.io/badge/-%E7%95%8C%E9%9D%A2-blue)](#界面-) [![](https://img.shields.io/badge/-%E5%AE%89%E8%A3%85-blue)](#安装-) [![](https://img.shields.io/badge/-%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-blue)](#小白羊公众号-) [![](https://img.shields.io/badge/-%E4%BA%A4%E6%B5%81%E7%A4%BE%E5%8C%BA-blue)](#交流社区-) [![](https://img.shields.io/badge/-%E9%B8%A3%E8%B0%A2-blue)](#鸣谢-) [![](https://img.shields.io/badge/-%E5%A3%B0%E6%98%8E-blue)](#免责声明-)
# 功能 [![](https://img.shields.io/badge/-%E5%8A%9F%E8%83%BD-blue)](#功能-)
1.基于阿里云盘Open平台api开发的网盘客户端支持win7-11macOSlinux <br>
1.根据阿里云盘Open平台api开发的网盘客户端支持win7-11macOSlinux <br>
2.支持同时登录多个账号管理。 <br>
@@ -68,21 +69,16 @@
10.支持一次性上传/下载百万级的文件/文件夹。<br>
11. 支持M3u8下载。<br>
12. 影院模式支持自动刮削视频信息。<br>
13. 支持展示视频观看进度。<br>
更多功能见小白羊官网https://xbysite.pages.dev/
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
</a>
# 界面 [![](https://img.shields.io/badge/-%E7%95%8C%E9%9D%A2-blue)](#界面-)
<img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/24d892b8-f36e-4eaf-a277-e105e8dae8f1" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/e789e557-f56f-41c5-80f9-1fad20b2ae1a" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/0b40bf83-3e59-4f15-bd01-76b9a14ec86c" width="270">
<img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/8684a964-7682-4d62-86cb-191382eb9170" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/8e10cad3-6252-4953-9e92-98175be3ddb9" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/4a3d6a68-5fab-48f1-8146-1d47e9f633fd" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/b5c3be0b-1038-4118-8a29-fa6e5d833148" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/25b5ff29-203a-451f-97fe-31a13c4c98e2" width="270"><img src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/f32c0733-23ac-4099-9de6-5e94c20af48a" width="270">
![main_window](https://github.com/gaozhangmin/aliyunpan/assets/9278488/b01d09ec-9b9b-49d8-8281-aded4dbbbc3e)
![movie_detail](https://github.com/gaozhangmin/aliyunpan/assets/9278488/f7a8d8d2-b132-4344-a193-56145199553c)
![movie_detail1](https://github.com/gaozhangmin/aliyunpan/assets/9278488/c9aa112a-c7a2-4ac0-97c9-d0c38b4b67bf)
![movie_page](https://github.com/gaozhangmin/aliyunpan/assets/9278488/b4bbb604-fdbd-4cf5-b191-e6a33c28eeaf)
![album](https://github.com/gaozhangmin/aliyunpan/assets/9278488/1fe490ad-f6ee-4193-b4b6-ccb4ccb740c1)
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
</a>
@@ -147,7 +143,7 @@
# 小白羊公众号 [![](https://img.shields.io/badge/-%E5%BE%AE%E4%BF%A1%E5%85%AC%E4%BC%97%E5%8F%B7-blue)](#小白羊公众号-)
<p align="center">
<img height="360" src="https://github.com/gaozhangmin/staticResource/blob/master/images/qrcode_search_white.png" />
<img height="360" src="https://github.com/gaozhangmin/aliyunpan/assets/9278488/135f4ff3-4817-4306-9a1b-669e90384702" />
</p>
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
@@ -169,11 +165,6 @@
[//]: # (</a>)
# 交流社区 [![](https://img.shields.io/badge/-%E4%BA%A4%E6%B5%81%E7%A4%BE%E5%8C%BA-blue)](#交流社区-)
#### Telegram
[![Telegram-group](https://img.shields.io/badge/Telegram-%E7%BE%A4%E7%BB%84-blue)](https://t.me/+wjdFeQ7ZNNE1NmM1)
# 鸣谢 [![](https://img.shields.io/badge/-%E9%B8%A3%E8%B0%A2-blue)](#鸣谢-)
本项目基于 https://github.com/liupan1890/aliyunpan 仓库继续开发。
@@ -192,7 +183,7 @@
4.在使用本程序之前你应了解并承担相应的风险包括但不限于账号被ban下载限速等与本程序无关
5.如有侵权,请通过邮件zhangao456@proton.me与我联系,会及时处理。
5.如有侵权,请通过邮件与我联系,会及时处理。
<a href="#readme">
<img src="https://img.shields.io/badge/-返回顶部-orange.svg" alt="#" align="right">
</a>

329
aliyunpan/changelog.txt Normal file
View File

@@ -0,0 +1,329 @@
#### 2023/2/24
1.修复UserDeviceIllegality问题
2.修复下载功能
3.修复打包问题
#### 2022/10/24
注:因上传下载功能尚未做完,本次仅同步代码不发布安装包
0. 恢复文件上传功能,支持单次上传百万个文件/文件夹,优化上传任务的数据库文件体积过大的问题
1. 优化sha1并发计算逻辑增加文件sha1的缓存提升上传大文件的性能
2. Add 上传前弹窗,设置重名冲突(删除/覆盖/自动重命名/不上传)
3. Add 单独的总上传速度限制设置
4. Add 上传文件时只上传可以秒传的文件的设置
5. Add 单独的上传文件时的并发执行数设置(最大50文件同时上传)
6. Add 左侧文件树文件夹对拖放上传的支持
7. Add 上传中列表里文件夹视图
8. Add 优先上传小文件(100M)的支持
9. Fix 按住Ctrl时点击CheckBox和点击空白处选中结果不一致的BUG
10. Fix 右键菜单-打开文件位置没有选中和滚动到指定文件的BUG
11. Fix 断网时重试上传任务可能导致上传任务的断点续传进度丢失的BUG
#### 2022/09/18
注:因上传下载功能尚未做完,本次仅同步代码不发布安装包
1. Fix v3.5.23alpha中的20余处小BUG
2. Fix 登录时遇到二次短信验证时不能继续登录的BUG
3. 完善批量重命名功能
4. 完善颜色标记功能
5. 优化文件列表加载显示逻辑,现在很优雅了
6. 优化文件名排序支持中文数字排序按win习惯英文在前中文在后
7. 优化文件夹树性能(全部文件夹列出速度加快3倍20万文件夹不卡顿树内存占用减少60%)
8. 恢复显示文件夹体积(可按大小排序),优化计算文件夹体积的逻辑(速度加快计算量减少)
9. 底部增加网盘空间信息和文件夹内文件总数量
10. 增加复制文件名和复制目录树的功能
11. 适配更新vite3.1.2更新全部package到最新版
#### 2022/05/23
v3.5.23alpha 开发人员测试版本
1. Fix 偶发的登录后显示空白窗口的 BUG
2. Fix 登录太多账号时切换账号弹窗不能正确显示的 BUG
3. 优化长时间不用后需要重新登录的问题
4. Fix 偶发的下载中显示空白列表的 BUG
5. Fix 多次弹出升级提示窗口的 BUG
6. Fix 目录下超过 200 个文件夹不能显示的 BUG
7. 重制 APP 设置页面,分组设置,配置保存到 setting.config
8. 优化快捷键(现在很多操作都支持键盘快捷键)
--
v3.05.23.alpha 新增加的功能
文件恢复,放映室,网页版播放器,彻底删除菜单,文件属性菜单,所有列表支持右键菜单,
整盘高级搜索,文件快速筛选,右侧文件拖放移动,文件标记,文件夹快捷方式,我订阅的公众号,
重复文件清理,扫描大文件,扫描重复文件,扫描违规文件,扫描空文件夹,网盘相册间复制,
批量重命名
#### 2022/04/14
1. 修正因阿里云盘官网升级导致的无法使用
2. 优化访问频次问题
3. 修正违禁视频详情BUG
#### 2021/12/05
1. 修正网盘内文件路径过长时下载失败 BUG
2. 优化快捷键功能(帮助文档里有完整的快捷键说明)
3. 增加收藏夹的搜索功能
4. 优化后退按钮(以前是返回父文件夹,现在是后退,最多后退 20 步)
5. 增加优先下载小文件选项
6. 增加雪碧图里视频信息的显示和保存雪碧图按钮
7. 网盘页顶部路径默认隐藏需要在设置里勾选显示
8. 修正m3u8播放链接15分钟后失效的 BUG (现在4小时)
9. 修正之前部分版本代理设置被覆盖的 BUG
10. 增加自定义缓存位置功能
11. 增加帮助文档
#### 2021/11/29
1. 修正 2.11.28 下载显示出错的 BUG
1. 修正登录时遇到二次验证导致点击登录按钮无反应的 BUG
2. 修正自 v2.11.07 开始不能上传体积为 0 的文件的 BUG
3. 修正 v2.11.16 上传文件时占用内存过多的问题
4. 11.16 里 60 文件同时上传会占用大量内存11.28 里优化为只占用 400MB 以内的内存
5. 修正传输完自动关机触发时机不准确的 BUG
6. 修正上传时遇到没有访问权限的文件/文件夹时上传中队列卡住的 BUG
7. 增加视频文件洗码功能
8. 增加右侧文件拖动到左侧文件夹树(移动文件)功能
9. 优化远程 Aria2 下载功能并修复断线后自动重连
10. 优化本地 Aria2无法连接时会尝试自动重启一次 Aria2 进程
11. 增加任务栏的下载中上传中进度提示(win/mac)
12. 底部状态栏显示总传输的预估剩余时间
13. 增加上传时跳过同名文件的设置项
14. 优化盘内文件搜索支持选择分类
15. 一些细节优化
#### 2021/11/14
1. 增加创建分享、编辑分享、管理分享功能
2. 完善导入分享功能,支持部分导入和完整导入
3. 增加网盘内文件搜索功能
4. 增加下载时自动过滤违规文件的设置
5. 增加对禁止分享的文件的图标
6. 增加文件/文件夹置顶功能
7. 增加主题跟随系统
8. 增加下载上传完成后自动关机设置
9. 完善 mac 和 linux 下自定义播放软件功能
10. 恢复视频文件雪碧图,增加复制 M3U8 链接和原始视频链接功能
11. 修正 linux 下上传时会自动过滤软链接文件
#### 2021/11/07
1. 优化一次性上传或下载大量文件时的界面卡顿(参阅 挑战一百万.md
2. 创建日期文件夹支持编号了
3. 新增定时清理回收站功能
4. 新增我创建的分享列表功能(没做完)
5. 增加文件列表显示限制(减少加载中)
6. 恢复导入阿里云盘分享链接功能(仅全部导入)
7. 恢复新建文件功能
8. 恢复版本升级提示功能
9. 修正 windows 下载位置不能选择根目录的 BUG
10. 修正不能上传大于 97.6GB 的文件的问题
11. 修正上传速度显示不准确的 BUG
12. 修正不能同时下载同一个文件(下载到不同的位置)的 BUG
13. 优化部分文件格式图标
#### 2021/10/31
1. 增加文件列表的缩略图模式
2. 修正v2.10.17和v2.10.19版本里一个严重的BUG删除文件时可能会错误选中父文件夹一起删除
3. 去除彻底删除按钮
4. 回收站增加清理回收站按钮(一键删除回收站内全部文件)
5. 增加上传和下载的文件过滤功能(自动跳过特定格式的文件)
6. 修正左侧文件夹树和右侧文件列表的互动关联
7. 恢复重命名功能和移动复制功能
#### 2021/10/19
1. 优化文件列出逻辑,节省一半的等待加载中时间
2. 优化v2.9一个文件夹直接包含大量子文件夹时的卡顿和内存剧增问题
一个文件夹里直接包含 17000 个子文件夹时,打开文件夹 v2.9 需要占用 700MB 内存, v2.10 需要占用 190MB 内存
v2.9 当一个文件夹里包含 3000 个以上的子文件夹时,小白羊文件夹树会卡顿
v2.10 无所谓多少个子文件夹,不会卡顿
3. 优化v2.9网盘包含有巨量文件夹时,启动后前几秒会卡顿的问题
4. 优化v2.9上下传记录的本地数据库体积
长期大量上传下载会产生较大的本地记录数据,现在会自动清理
5. 修正v2.9统计文件夹体积功能的本地缓存和运行时CPU内存占用
开启统计文件夹体积功能后,我网盘里有 2 万个文件夹和 31 万个文件v2.9 会产生 167MB 的缓存v2.10 会产生 8MB 的缓存
修正了一个会导致 CPU 和内存占用高的 BUG用户账号 token 失效时,会因为定时统计功能导致 CPU 和内存占用很高)
6. 优化在线预览视频现在支持很多种播放器了
当前只适配了 windows (测试了 MPVPotplayerVLC media playerKMPlayer恒星播放器SMPlayer
macos 和 linux 稍后会适配,现在仍旧只能用 mpv 播放器
7. 增加在线预览 word/excel/ppt/pdf 文件的功能
当前大部分音视频格式大部分图片格式word/excel/ppt/pdf/txt200 余种常见文本格式 都可以在线预览了
8. 修正在文件夹里搜索后,拖动搜索结果里的文件上传,上传文件名错误的 BUG
9. 优化上传前的 sha1 校验速度,提升上传速度
10. 美化了一下界面
11. 增加是否按照完整网盘路径保存的设置
12. 增加关闭窗口立即退出的设置
13. 增加双击才打开文件、文件夹的设置
14. 增加清理缓存的设置
15. 增加运行日志的设置
16. 文件夹树的宽度可以拖动调整了
17. 图片、Office、文本预览现在是单独窗口了
18. 修正一些文件格式识别不准确的 BUG
#### 2021/09/24
1. 修正上传 20GB 以上的文件时,断点续传时进度不准确的 BUG
2. 取消文件列表的加载中状态提示,快速展现文件列表
3. 更新文件列表缓存方式,数据库文件体积减少 73% (14 万个文件从 240MB 降低为 60MB)
4. 增加是否统计文件夹体积的设置开关,减少网盘内文件过多时的白屏问题
5. 同步 v2.9.24 源码到 github
#### 2021/09/19
1. 删除秒传相关功能
2. 修正 v2.8.30 里 aria2 远程模式连接失败的 BUG
3. 修正偶发文件列表只显示占位符不显示文件名的 BUG
4. 修正移动文件后选中文件数显示错误的 BUG
5. 修正批量重命名取消勾选文件夹时子文件名计算错误的 BUG
6. 修正批量重命名点击刷新后因一直加载,不能关闭的 BUG
7. 增加对文件名结尾的点和空格的清理,修正这些文件下载失败的 BUG
8. 修正闲置长时间后上传文件可能出现获取上传地址失败的 BUG
9. 减少因并发数太高容易出现的操作失败 BUG
10. 修正等宽图片预览时,切换下一张后滚动条没有自动回到顶部的 BUG
11. 增加文件列表(F5 键刷新文件Back 键返回上级文件夹),等宽图片预览(← 上一张,→ 下一张)的快捷键
12. 增加点击头像图片时自动刷新网盘空间用量
13. 增加文件夹独立排序选项
14. 增加直接彻底删除文件的右键菜单
15. 升级数据库架构,提升了加载文件列表的性能,本周重点就是此项,性能提升涉及方方面面的细节,大部分以前加载慢的功能都有了极明显的提升,例如一次性上传包含 10 万个文件的文件夹,不会出现任何卡顿了
16. 修正 v2.9.15 里长时间后上传文件时出现获取上传地址失败的 BUG
17. 增加上传/下载任务出错后等 1 分钟自动重试功能,可以放心挂机下载、挂机上传了
#### 2021/08/30
1. Fix 修正 v1.6.29 大量上传下载后会生成大体积的 数据库 的 BUG
2. Fix 修正 v1.6.29 导入阿里云盘分享链接失败的 BUG
3. Fix 修正 v1.6.29 上传途中重启程序后,重新上传不会断点续传的 BUG
4. Fix 修正部分违规视频不能播放的 BUG现在可以使用"优先播放转码视频"模式播放了
#
1. Add 增加阿里云盘官方登录接口手机短信、账号密码、APP 扫码登录)
2. Add 增加多个账号同时登录、切换功能
3. Add 增加 Aria 远程连接设置,可以把文件直接下载到远程电脑/VPS/NAS/Docker
4. Add 增加文件名颜色标记,批量标记功能,观看视频自动标记功能
5. Add 增加文件、文件夹详情功能(文件夹大小,包含文件数),视频文件的雪碧图
6. Add 增加新的图片预览模式,可以放大/缩小/旋转/幻灯片播放
7. Add 增加代码高亮/ json 格式化显示 / txt 在线预览功能
8. Add 增加快速创建日期格式的文件夹
9. Add 增加可选择文件夹是否和文件一起排序了
10. Add 增加所有文件夹体积的显示,可以按照体积排序文件夹了
11. Add windows 上支持 Potplayer 播放器了
12. Add 顶部快捷路径跳转和区间选择功能
#
1. Pro 优化文件复制功能,可极速复制 TB 级/上万文件 到网盘的其他位置
2. Pro 优化导入分享功能,在导入时可以选择网盘里的保存位置,可以勾选要保存的 文件/文件夹
3. Pro 优化上传功能,现在部分不能秒传的大文件,上传前不再需要计算 sha1 了(减少上传时间)
4. Pro 优化 sha1 计算逻辑和性能,同时最多 3 个文件计算 sha1机械硬盘不会掉速CPU 不会爆满
5. Pro 现在 windows/macos/linux 都支持拖拽文件、文件夹上传了
6. Pro 优化批量重命名功能,支持勾选文件,支持重命名多级子文件夹,支持 替换/删除/增加/序号/随机字符 等方式
7. Pro 优化在线解压功能,支持全部解压和勾选文件解压,支持有密码的压缩包
#### 2021/06/29
Fix 优化重命名、搜索输入框大小
Fix 修正下载中、上传中页面因进度条动画导致的GPU占用过高的BUG
Fix 中文名导致偶有macos启动失败的BUG
Add 导入阿里云盘分享链接的功能
Add 导入115网盘分享链接的功能
#### 2021/06/21
Fix 显示用户昵称和头像
Fix 完善对字体的支持(可以随意更换自己喜欢的字体了),完善文字大小设置功能
Fix 完善在线预览图片功能(支持旋转,文件夹内全部图片上一张下一张查看)
Fix 文件名是.(点)时导致的创建下载任务失败的BUG
Fix 创建文件夹太快偶发点击文件夹名不能进入的BUG
Add Windows上支持批量拖拽文件/文件夹上传
Add 选择文件/文件夹计算秒传信息保存到网盘内txt文件的功能
Add 导入txt文件类型的秒传链接(支持文件夹嵌套)
Add 新增相册功能(文件可以在相册和网盘之间移动复制)
#### 2021/06/13
Fix 在下载大文件时Aria在某些系统上强制分配硬盘BUG导致下载进度卡死
Fix 优化Aria的连接性减少出错崩溃
Fix Mac版输出大量无用日志的BUG
Fix 一堆UI细节上的完善
Add 复制文件到...的功能(官方只有移动到...)
Add 批量重命名功能(替换/删除字符,增加前缀,正则表达式替换)
Add 聚合搜索功能(当前搜不到什么,要等以后大家主动分享)
Add 初步支持在线解压缩(zip,rar)
Ver 更新到Flutter2.2.1
#### 2021/06/10
Fix 因阿里云盘API升级导致的无法加载文件列表的BUG
Fix 选择上传文件夹时 可能 需要长时间等待的BUG
Fix 批量下载大量子文件夹时 可能 需要长时间等待的BUG
Fix 修正回收站内文件无法在线播放的BUG
Fix 增加一些在线播放视频格式的支持(m2ts/hevc....)
Add 增加对违规文件的标识
Add 增加深色模式
Add 下载失败时的一些错误提示
Del 去除创建秒传链接的功能,仅支持导入秒传链接(115:// 、aliyun://)
#### 2021/06/06
1. Fix 批量下载时只解析了第一个选中的文件夹的严重BUG
2. Fix 大量操作更新为异步操作,极大的减少了操作等待时间
3. Fix 因阿里云盘升级导致扫码登陆失败的BUG
4. Add 支持导入李恒道版本秒传链接
#### 2021/06/05
1. Add 秒传短链接功能(创建秒传链接、导入别人分享的短链接、短链接本地历史记录)
2. Add 增加115链接批量导入功能(靠运气)
3. Add 增加在线预览文本文件功能
4. Fix 因阿里云盘升级导致扫码登陆失败的BUG
5. Fix 阿里云盘对单次批量操作最多限制100条的限制
6. Fix 在线预览图片时图片大小缩放BUG
7. Fix 按文件名排序时不准确的BUG
#### 2021/05/31
1. Add 上传文件、文件夹功能
2. Add 在线预览图片
3. Add 移动文件、文件夹功能
4. Fix 优化启动时启动后台提示
5. Fix 延长下载链接时效(15分钟->4小时)
6. Fix 文件夹内包含大量文件时多次操作可能回重复拉取的BUG
#### 2021/05/25
1. 上传第一个开发中版本仅供测试
2. 支持 扫码登录/Cookie登录
3. 支持 阿里云盘基本功能
4. 支持 在线预览全格式原画视频(非转码)
5. 支持 批量下载文件/文件夹,只要阿里云不限速,就是满速下载

View File

@@ -10,9 +10,9 @@
{ "from": "./static/engine/aria2.conf", "to": "./engine/aria2.conf"},
{ "from": "./static/crx", "to": "./crx"},
{ "from": "./public/images/qrcode_1280.jpg", "to": "./images/qrcode_1280.jpg"},
{ "from": "./static/images/icon_24x24.png", "to": "./images/icon_24x24.png"},
{ "from": "./static/images/icon_64x64.png", "to": "./images/icon_64x64.png"},
{ "from": "./static/images/icon_256x256.png", "to": "./images/icon_256x256.png"}
{ "from": "./static/images/icon_24.png", "to": "./images/icon_24.png"},
{ "from": "./static/images/icon_64.png", "to": "./images/icon_64.png"},
{ "from": "./static/images/icon_256.png", "to": "./images/icon_256.png"}
],
"mac": {
"icon": "./static/images/icon.icns",
@@ -22,41 +22,35 @@
"hardenedRuntime": true,
"category": "public.app-category.utilities",
"extraResources": [
{ "from": "./static/images/icon.icns", "to": "./images/icon.icns"},
{ "from": "./static/images/icon_24x24.png", "to": "./images/icon_24x24.png"},
{ "from": "./static/engine/darwin/${arch}", "to": "./engine"}
{ "from": "./static/engine/darwin/${arch}", "to": "./engine" },
{ "from": "./static/images/icon.icns", "to": "./images/icon.icns"}
],
"target": [
{ "target": "dmg", "arch": [ "x64", "arm64" ] }
]
},
"linux": {
"icon": "./static/images/icon_256x256.png",
"icon": "./static/images/icon_256.png",
"category": "Network",
"artifactName": "XBYDriver-${version}-linux-${arch}.${ext}",
"extraResources": [
{ "from": "./static/images/icon_24x24.png", "to": "./images/icon_24x24.png"},
{ "from": "./static/images/icon_64x64.png", "to": "./images/icon_64x64.png"},
{ "from": "./static/images/icon_256x256.png", "to": "./images/icon_256x256.png"},
{ "from": "./static/engine/linux/${arch}", "to": "./engine"}
],
"target": [
{ "target": "AppImage", "arch": [ "x64", "arm64", "armv7l" ] },
{ "target": "deb", "arch": [ "x64", "arm64", "armv7l" ] },
{ "target": "pacman", "arch": [ "x64", "arm64", "armv7l" ] }
{ "target": "deb", "arch": [ "x64", "arm64", "armv7l" ] }
]
},
"win": {
"icon": "./static/images/icon_256x256.ico",
"icon": "./static/images/icon_256.ico",
"artifactName": "XBYDriver-${version}-win-${arch}.${ext}",
"requestedExecutionLevel": "asInvoker",
"extraResources": [
{ "from": "./static/images/icon_64x64.png", "to": "./images/icon_64x64.png"},
{ "from": "./static/images/icon_256x256.ico", "to": "./images/icon_256x256.ico"},
{ "from": "./static/engine/win32/${arch}", "to": "./engine"}
{ "from": "./static/engine/win32/${arch}", "to": "./engine"},
{ "from": "./static/images/icon_256.ico", "to": "./images/icon_256.ico"}
],
"target": [
{ "target": "nsis", "arch": [ "x64", "ia32", "arm64" ] }
{ "target": "nsis", "arch": [ "x64", "ia32" ] }
]
},
"dmg": {
@@ -75,10 +69,7 @@
},
"publish": [
{
"owner": "gaozhangmin",
"repo": "aliyunpan",
"provider": "github",
"publishAutoUpdate": false,
"releaseType": "draft"
}
]

View File

@@ -1,50 +0,0 @@
import { app, dialog } from 'electron'
export function ShowErrorAndRelaunch(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title + ',小白羊将自动退出',
message: '错误信息:' + errmsg
})
.then((_) => {
setTimeout(() => {
app.relaunch()
try {
app.exit()
} catch {
}
}, 100)
})
}
export function ShowErrorAndExit(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title + ',小白羊将自动退出',
message: '错误信息:' + errmsg
})
.then((_) => {
setTimeout(() => {
try {
app.exit()
} catch {
}
}, 100)
})
}
export function ShowError(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title,
message: '错误信息:' + errmsg
})
.then((_) => {
})
}

View File

@@ -1,24 +0,0 @@
import is from 'electron-is'
import { ShowErrorAndExit } from './dialog'
import { app } from 'electron'
export default class exception {
private constructor() {
}
static handler() {
if (is.dev()) {
return
}
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason)
})
process.on('uncaughtException', (err) => {
let { message, stack = '' } = err
if (app.isReady()) {
ShowErrorAndExit('发生未定义的异常', err.message + '\n' + stack)
}
})
}
}

View File

@@ -1,577 +0,0 @@
import { AppWindow, createElectronWindow, Referer, ua } from './window'
import path from 'path'
import is from 'electron-is'
import { app, BrowserWindow, dialog, ipcMain, Menu, powerSaveBlocker, session, shell } from 'electron'
import { existsSync, writeFileSync } from 'fs'
import { exec, execFile, spawn, SpawnOptions } from 'child_process'
import { ShowError } from './dialog'
import { getStaticPath, getUserDataPath } from '../utils/mainfile'
import { portIsOccupied } from '../utils'
let psbId: any
export default class ipcEvent {
private constructor() {
}
static handleEvents() {
this.handleWebToElectron()
this.handleWebToElectronCB()
this.handleShowContextMenu()
this.handleWebShowOpenDialogSync()
this.handleWebShowSaveDialogSync()
this.handleWebShowItemInFolder()
this.handleWebPlatformSync()
this.handleWebSpawnSync()
this.handleWebExecSync()
this.handleWebSaveTheme()
this.handleWebClearCookies()
this.handleWebGetCookies()
this.handleWebSetCookies()
this.handleWebClearCache()
this.handleWebReload()
this.handleWebRelaunch()
this.handleWebRelaunchAria()
this.handleWebRelaunchAlist()
this.handleWebResetAlistPwd()
this.handleWebSetProgressBar()
this.handleWebShutDown()
this.handleWebSetProxy()
this.handleWebOpenWindow()
this.handleWebOpenUrl()
}
private static handleWebToElectron() {
ipcMain.on('WebToElectron', async (event, data) => {
let mainWindow = AppWindow.mainWindow
if (data.cmd && data.cmd === 'close') {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide()
} else if (data.cmd && data.cmd === 'relaunch') {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.destroy()
mainWindow = undefined
}
try {
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)
} catch {
}
} else if (data.cmd && data.cmd === 'exit') {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.destroy()
mainWindow = undefined
}
try {
app.exit(0)
} catch {
}
} else if (data.cmd && data.cmd === 'minsize') {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.minimize()
} else if (data.cmd && data.cmd === 'maxsize') {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
}
} else if (data.cmd && data.cmd === 'minsizeAudio') {
if (AppWindow.auidoWindow && !AppWindow.auidoWindow.isDestroyed()) AppWindow.auidoWindow.minimize()
} else if (data.cmd && data.cmd === 'maxsizeAudio') {
if (AppWindow.auidoWindow && !AppWindow.auidoWindow.isDestroyed()) {
if (AppWindow.auidoWindow.isMaximized()) {
AppWindow.auidoWindow.unmaximize()
} else {
AppWindow.auidoWindow.maximize()
}
}
} else if (data.cmd && data.cmd === 'minsizeVideo') {
if (AppWindow.videoWindow && !AppWindow.videoWindow.isDestroyed()) AppWindow.videoWindow.minimize()
} else if (data.cmd && data.cmd === 'maxsizeVideo') {
if (AppWindow.videoWindow && !AppWindow.videoWindow.isDestroyed()) {
if (AppWindow.videoWindow.isMaximized()) {
AppWindow.videoWindow.unmaximize()
} else {
AppWindow.videoWindow.maximize()
}
}
} else if (data.cmd && (Object.hasOwn(data.cmd, 'launchStart')
|| Object.hasOwn(data.cmd, 'launchStartShow'))) {
const launchStart = data.cmd.launchStart
const launchStartShow = data.cmd.launchStartShow
const appName = path.basename(process.execPath)
// 设置开机自启
const settings: Electron.Settings = {
openAtLogin: launchStart,
path: process.execPath
}
// 显示主窗口
if (is.macOS()) {
settings.openAsHidden = !launchStartShow
} else {
settings.args = [
'--processStart', `${appName}`,
'--process-start-args', `"--hidden"`
]
!launchStartShow && settings.args.push('--openAsHidden')
}
app.setLoginItemSettings(settings)
} else if (data.cmd && data.cmd === 'preventSleep') {
if (data.flag) {
if (psbId && powerSaveBlocker.isStarted(psbId)) {
return
}
psbId = powerSaveBlocker.start('prevent-app-suspension')
} else {
if (typeof psbId === 'undefined' || !powerSaveBlocker.isStarted(psbId)) {
return
}
powerSaveBlocker.stop(psbId)
psbId = undefined
}
} else {
event.sender.send('ElectronToWeb', 'mainsenddata')
}
})
}
private static handleWebToElectronCB() {
ipcMain.on('WebToElectronCB', (event, data) => {
const mainWindow = AppWindow.mainWindow
if (data.cmd && data.cmd === 'maxsize') {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
event.returnValue = 'unmaximize'
} else {
mainWindow.maximize()
event.returnValue = 'maximize'
}
}
} else {
event.returnValue = 'backdata'
}
})
}
private static handleShowContextMenu() {
ipcMain.on('show-context-menu', (event, params) => {
const { showCut, showCopy, showPaste } = params
const window = BrowserWindow.fromWebContents(event.sender)
// 制作右键菜单
let template: Array<Electron.MenuItemConstructorOptions> = [
// 设置选项是否可见
{ role: 'selectAll', label: '全选' },
{ role: 'copy', label: '复制', visible: showCopy },
{ role: 'cut', label: '剪切', visible: showCut },
{ role: 'paste', label: '粘贴', visible: showPaste },
{ role: 'undo', label: '撤销' }
]
// 显示菜单
const contextMenu = Menu.buildFromTemplate(template)
contextMenu.popup({ window })
})
}
private static handleWebShowOpenDialogSync() {
ipcMain.on('WebShowOpenDialogSync', (event, config) => {
dialog.showOpenDialog(AppWindow.mainWindow!, config).then((result) => {
event.returnValue = result.filePaths
})
})
}
private static handleWebShowSaveDialogSync() {
ipcMain.on('WebShowSaveDialogSync', (event, config) => {
dialog.showSaveDialog(AppWindow.mainWindow!, config).then((result) => {
event.returnValue = result.filePath || ''
})
})
}
private static handleWebShowItemInFolder() {
ipcMain.on('WebShowItemInFolder', (event, fullPath) => {
for (let i = 0; i < 5; i++) {
if (existsSync(fullPath)) break
if (fullPath.lastIndexOf(path.sep) > 0) {
fullPath = fullPath.substring(0, fullPath.lastIndexOf(path.sep))
} else return
}
if (fullPath.length > 2) shell.showItemInFolder(fullPath)
})
}
private static handleWebPlatformSync() {
ipcMain.on('WebPlatformSync', (event) => {
const asarPath = app.getAppPath()
const appPath = app.getPath('userData')
event.returnValue = {
platform: process.platform,
arch: process.arch,
version: process.version,
execPath: process.execPath,
appPath: appPath,
asarPath: asarPath,
argv0: process.argv0
}
})
}
private static handleWebSpawnSync() {
ipcMain.on('WebSpawnSync', (event, data) => {
try {
const options: SpawnOptions = {
shell: true,
stdio: 'ignore',
windowsVerbatimArguments: true,
...data.options
}
const argsToStr = (args: string) => is.windows() ? `"${args}"` : `'${args}'`
if ((is.windows() || is.macOS()) && !existsSync(data.command)) {
event.returnValue = { error: '找不到文件' + data.command }
ShowError('找不到文件', data.command)
} else {
let command
if (is.macOS()) {
command = `open -a ${argsToStr(data.command)} ${data.command.includes('mpv.app') ? '--args ' : ''}`
} else {
command = `${argsToStr(data.command)}`
}
const subProcess = spawn(command, data.args, options)
subProcess.unref()
event.returnValue = {
pid: subProcess.pid,
subProcess: subProcess,
execCmd: data,
options: options,
exitCode: subProcess.exitCode
}
}
} catch (err: any) {
event.returnValue = { error: err }
}
})
}
private static handleWebExecSync() {
ipcMain.on('WebExecSync', (event, data) => {
try {
const cmdArguments = []
cmdArguments.push(data.command)
if (data.args) cmdArguments.push(...data.args)
const finalCmd = cmdArguments.join(' ')
exec(finalCmd, (err: any) => {
event.returnValue = err
})
event.returnValue = ''
} catch (err: any) {
event.returnValue = { error: err }
}
})
}
private static handleWebSaveTheme() {
ipcMain.on('WebSaveTheme', (event, data) => {
try {
const themeJson = getUserDataPath('theme.json')
writeFileSync(themeJson, `{"theme":"${data.theme || ''}"}`, 'utf-8')
} catch {
}
})
}
private static handleWebClearCookies() {
ipcMain.on('WebClearCookies', (event, data) => {
session.defaultSession.clearStorageData(data)
})
}
private static handleWebGetCookies() {
ipcMain.handle('WebGetCookies', async (event, data) => {
return await session.defaultSession.cookies.get(data)
})
}
private static handleWebSetCookies() {
ipcMain.on('WebSetCookies', (event, data) => {
for (let i = 0, maxi = data.length; i < maxi; i++) {
const cookie = {
url: data[i].url,
name: data[i].name,
value: data[i].value,
domain: '.' + data[i].url.substring(data[i].url.lastIndexOf('/') + 1),
secure: data[i].url.indexOf('https://') == 0,
expirationDate: data[i].expirationDate
}
session.defaultSession.cookies.set(cookie).catch((err: any) => console.error(err))
}
})
}
private static handleWebClearCache() {
ipcMain.on('WebClearCache', (event, data) => {
if (data.cache) {
session.defaultSession.clearCache()
session.defaultSession.clearAuthCache()
} else {
session.defaultSession.clearStorageData(data)
}
})
}
private static handleWebReload() {
ipcMain.on('WebReload', (event, data) => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) AppWindow.mainWindow.reload()
})
}
private static handleWebRelaunch() {
ipcMain.on('WebRelaunch', (event, data) => {
app.relaunch()
try {
app.exit()
} catch {
}
})
}
private static handleWebRelaunchAria() {
ipcMain.handle('WebRelaunchAria', async (event, data) => {
try {
const enginePath: string = getStaticPath('engine')
const confPath: string = path.join(enginePath, 'aria2.conf')
const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c'
const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '')
const ariaFilePath: string = path.join(basePath, ariaPath)
if (!existsSync(ariaFilePath)) {
ShowError('找不到Aria程序文件', ariaFilePath)
return 0
}
const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'`
const listenPort = await portIsOccupied(16800)
const options: SpawnOptions = {
shell: true,
stdio: is.dev() ? 'pipe' : 'ignore',
windowsHide: false,
windowsVerbatimArguments: true
}
const fileAllocation = is.macOS() ? 'none' : (is.windows() ? 'falloc' : 'trunc')
const args = [
`--stop-with-process=${argsToStr(process.pid)}`,
`--conf-path=${argsToStr(confPath)}`,
`--file-allocation=${argsToStr(fileAllocation)}`,
`--rpc-listen-port=${argsToStr(listenPort)}`,
'-D'
]
spawn(`${argsToStr(ariaFilePath)}`, args, options)
return listenPort
} catch (e: any) {
console.log(e)
}
return 0
})
}
private static handleWebRelaunchAlist() {
ipcMain.handle('WebRelaunchAlist', async (event, data) => {
try {
const enginePath: string = getStaticPath('engine')
const alistPath = is.windows() ? 'alist.exe' : 'alist'
const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '')
const alistFilePath: string = path.join(basePath, alistPath)
const alistDataPath = getUserDataPath('alist-data')
if (!existsSync(alistFilePath)) {
ShowError('找不到alist程序文件', alistFilePath)
return 0
}
const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'`
const alistArgs = [
'server',
'--data ' + `${argsToStr(alistDataPath)}`
]
const options = {
shell: true,
windowsVerbatimArguments: true
}
console.log(`${argsToStr(alistFilePath + ' server')}`)
execFile(`${argsToStr(alistFilePath)}`, alistArgs, options,
async (error, stdout, stderr) => {
if (error) {
console.log(`启动AList失败 : ${error}`)
return 0
}
})
return 0
} catch (e: any) {
console.log(e)
}
return 0
})
}
private static handleWebResetAlistPwd() {
ipcMain.handle('WebResetAlistPwd', async (event, data) => {
try {
const enginePath: string = getStaticPath('engine')
const alistPath = is.windows() ? 'alist.exe' : 'alist'
const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '')
const alistFilePath: string = path.join(basePath, alistPath)
const alistDataPath = getUserDataPath('alist-data')
if (!existsSync(alistFilePath)) {
ShowError('找不到alist程序文件', alistFilePath)
return 0
}
const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'`
const password = data.cmd
const alistArgs = [
'admin set ' + `${argsToStr(password)}`,
'--data ' + `${argsToStr(alistDataPath)}`
]
console.log(`修改AList密码 : `, password)
const options = {
shell: true,
windowsVerbatimArguments: true
}
execFile(`${argsToStr(alistFilePath)}`, alistArgs, options,
async (error, stdout, stderr) => {
if (error) {
console.log(`修改AList密码失败 : ${error}`)
} else {
console.log(`修改AList密码成功 : ${stdout}`)
}
})
} catch (e: any) {
console.log(e)
}
})
}
private static handleWebSetProgressBar() {
ipcMain.on('WebSetProgressBar', (event, data) => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) {
if (data.pro) {
AppWindow.mainWindow.setProgressBar(data.pro, { mode: data.mode || 'normal' })
} else AppWindow.mainWindow.setProgressBar(-1)
}
})
}
private static handleWebShutDown() {
ipcMain.on('WebShutDown', (event, data) => {
if (is.macOS()) {
const shutdownCmd = 'osascript -e \'tell application "System Events" to shut down\''
exec(shutdownCmd, (err: any) => {
if (data.quitApp) {
try {
app.exit()
} catch {
}
}
if (err) {
// donothing
}
})
} else {
const cmdArguments = ['shutdown']
if (is.linux()) {
if (data.sudo) {
cmdArguments.unshift('sudo')
}
cmdArguments.push('-h')
cmdArguments.push('now')
}
if (is.windows()) {
cmdArguments.push('-s')
cmdArguments.push('-f')
cmdArguments.push('-t 0')
}
const finalcmd = cmdArguments.join(' ')
exec(finalcmd, (err: any) => {
if (data.quitApp) {
try {
app.exit()
} catch {
}
}
if (err) {
// donothing
}
})
}
})
}
private static handleWebSetProxy() {
ipcMain.on('WebSetProxy', (event, data) => {
// if (data.proxyUrl) app.commandLine.appendSwitch('proxy-server', data.proxyUrl)
// else app.commandLine.removeSwitch('proxy-server')
console.log(JSON.stringify(data))
if (data.proxyUrl) {
session.defaultSession.setProxy({ proxyRules: data.proxyUrl })
} else {
session.defaultSession.setProxy({})
}
})
}
private static handleWebOpenWindow() {
// let winWidth = AppWindow.winWidth
// if (winWidth < 1080) winWidth = 1080
ipcMain.on('WebOpenWindow', (event, data) => {
const win = createElectronWindow(data.width || AppWindow.winWidth, data.height || AppWindow.winHeight, true, 'main2', data.theme)
win.on('ready-to-show', function() {
win.webContents.send('setPage', data)
win.setTitle('预览窗口')
win.show()
})
if (data.page === 'PageAudio') {
AppWindow.auidoWindow = win
} else if (data.page === 'PageVideo') {
AppWindow.videoWindow = win
}
})
}
private static handleWebOpenUrl() {
ipcMain.on('WebOpenUrl', (event, data) => {
const win = new BrowserWindow({
show: false,
width: AppWindow.winWidth,
height: AppWindow.winHeight,
center: true,
minWidth: 680,
minHeight: 500,
icon: getStaticPath('icon_256x256.ico'),
useContentSize: true,
frame: true,
hasShadow: true,
autoHideMenuBar: true,
backgroundColor: data.theme && data.theme == 'dark' ? '#23232e' : '#ffffff',
webPreferences: {
spellcheck: false,
devTools: is.dev(),
sandbox: false,
webSecurity: false,
allowRunningInsecureContent: true,
backgroundThrottling: false,
enableWebSQL: false,
disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure'
}
})
win.on('ready-to-show', function() {
win.setTitle('预览窗口')
win.show()
})
win.loadURL(data.PageUrl, {
userAgent: ua,
httpReferrer: Referer
})
})
}
}

View File

@@ -1,13 +1,621 @@
import { app } from 'electron'
import { getStaticPath } from './utils/mainfile'
import launch from './launch'
import {getResourcesPath, getStaticPath, getUserDataPath} from './mainfile'
import { release } from 'os'
import { AppWindow, creatElectronWindow, createMainWindow, createTray, Referer, ShowError, ShowErrorAndExit, ua } from './window'
import Electron from 'electron'
import is from 'electron-is'
import { execFile, SpawnOptions } from 'child_process'
import { portIsOccupied } from './utils'
import { app, BrowserWindow, dialog, Menu, MenuItem, ipcMain, shell, session } from 'electron'
import { exec, spawn } from 'child_process'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import path from 'path'
import fixPath from 'fix-path'
fixPath()
if (release().startsWith('6.1')) {
app.disableHardwareAcceleration()
}
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason)
})
process.on('uncaughtException', (err) => {
const stack = err.stack || ''
if (app.isReady()) ShowErrorAndExit('发生未定义的异常', err.message + '\n' + stack)
})
// app.commandLine.appendSwitch('proxy-server', '192.168.31.74:8888')
app.commandLine.appendSwitch('no-sandbox')
app.commandLine.appendSwitch('disable-web-security')
app.commandLine.appendSwitch('disable-renderer-backgrounding')
app.commandLine.appendSwitch('disable-site-isolation-trials')
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure')
app.commandLine.appendSwitch('ignore-connections-limit', 'bj29.cn-beijing.data.alicloudccp.com,alicloudccp.com,api.aliyundrive.com,aliyundrive.com')
app.commandLine.appendSwitch('ignore-certificate-errors')
app.commandLine.appendSwitch('proxy-bypass-list', '<local>')
app.commandLine.appendSwitch('wm-window-animations-disabled')
app.setAppUserModelId('gaozhangmin')
app.name = 'alixby3'
const DEBUGGING = !app.isPackaged
const userData = getResourcesPath('userdir.config')
try {
if (existsSync(userData)) {
const configData = readFileSync(userData, 'utf-8')
if (configData) app.setPath('userData', configData)
}
} catch {
}
const gotTheLock = app.requestSingleInstanceLock()
if (DEBUGGING == false) {
if (!gotTheLock) {
app.exit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (commandLine && commandLine.join(' ').indexOf('exit') >= 0) {
app.exit()
} else if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
}
})
}
}
if (process.argv && process.argv.join(' ').indexOf('exit') >= 0) {
app.exit()
}
app.on('window-all-closed', () => {
if (is.macOS()) {
AppWindow.appTray?.destroy()
} else {
app.quit() // 未测试应该使用哪一个
}
})
app.on('activate', () => {
if (!AppWindow.mainWindow || AppWindow.mainWindow.isDestroyed()) createMainWindow()
else {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
}
})
app.on('will-quit', () => {
try {
if (AppWindow.appTray) {
AppWindow.appTray.destroy()
AppWindow.appTray = undefined
}
} catch {
}
})
app.setAboutPanelOptions({
applicationName: '小白羊云盘',
copyright: 'Zhangmin Gao',
website: 'https://github.com/gaozhangmin/aliyunpan',
iconPath: getStaticPath('icon_64x64.png'),
iconPath: getStaticPath('icon_64.png'),
applicationVersion: '30'
})
const appLaunch = new launch()
let userToken: { access_token: string; access_token_v2:string, user_id: string; refresh: boolean } = {
access_token: '',
access_token_v2: '',
user_id: '',
refresh: false
}
ipcMain.on('WebUserToken', (event, data) => {
if (data.login) {
userToken = data
} else if (userToken.user_id == data.user_id) {
userToken = data
// ShowError('WebUserToken', 'update' + data.name)
} else {
// ShowError('WebUserToken', 'nothing' + data.name)
}
})
// ipcMain.on('CheckUpdate', () => {
// checkForUpdates()
// });
//
// autoUpdater.setFeedURL({
// provider: 'github',
// owner: 'gaozhangmin',
// repo: 'aliyunpan',
// });
//
// function checkForUpdates() {
// autoUpdater.checkForUpdates().then((updateCheckResult) => {
// if (updateCheckResult && updateCheckResult.updateInfo.version !== autoUpdater.currentVersion.version) {
// //有新版本可用,显示提示框
// dialog.showMessageBox({
// type: 'question',
// buttons: ['Yes', 'No'],
// title: '应用有新版本可用',
// message: `检测到新版本: ${updateCheckResult.updateInfo.version} , 扫码关注小白羊网盘下载更新`,
// detail: '是否立即安装新版本?',
// icon: qrCodeImage
// }).then((result) => {
// // 根据用户选择的按钮执行相应操作
// if (result.response === 0) {
// shell.openExternal(downloadPageUrl);
// }
// });
// }
// }).catch((error) => {
// // 更新检查失败
// });
// }
app
.whenReady()
.then(() => {
try {
const localVersion = getResourcesPath('localVersion')
if (localVersion && existsSync(localVersion)) {
const version = readFileSync(localVersion, 'utf-8')
if (app.getVersion() !== version) {
writeFileSync(localVersion, app.getVersion(), 'utf-8')
}
} else {
writeFileSync(localVersion, app.getVersion(), 'utf-8')
}
} catch (err) {}
session.defaultSession.webRequest.onBeforeSendHeaders((details, cb) => {
const should115Referer = details.url.indexOf('.115.com') > 0
const shouldGieeReferer = details.url.indexOf('gitee.com') > 0
const shouldAliOrigin = details.url.indexOf('.aliyundrive.com') > 0
const shouldAliReferer = !should115Referer && !shouldGieeReferer && (!details.referrer || details.referrer.trim() === '' || /(\/localhost:)|(^file:\/\/)|(\/127.0.0.1:)/.exec(details.referrer) !== null)
const shouldToken = details.url.includes('aliyundrive') && details.url.includes('download')
const shouldOpenApiToken = details.url.includes('adrive/v1.0')
cb({
cancel: false,
requestHeaders: {
...details.requestHeaders,
...(should115Referer && {
Referer: 'http://115.com/s/swn4bs33z88',
Origin: 'http://115.com'
}),
...(shouldGieeReferer && {
Referer: 'https://gitee.com/'
}),
...(shouldAliOrigin && {
Origin: 'https://www.aliyundrive.com'
}),
...(shouldAliReferer && {
Referer: 'https://www.aliyundrive.com/'
}),
...(shouldToken && {
Authorization: userToken.access_token
}),
// ...(shouldOpenApiToken && {
// Authorization: userToken.access_token_v2
// }),
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.1.0 Chrome/108.0.5359.215 Electron/22.3.1 Safari/537.36',
'X-Canary': 'client=windows,app=adrive,version=v4.1.0',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
})
})
session.defaultSession.loadExtension(getStaticPath('crx'), { allowFileAccess: true })
.then((le) => {
createMenu()
createTray()
createMainWindow()
})
})
.catch((err: any) => {
console.log(err)
})
let menuEdit: Electron.Menu | undefined
let menuCopy: Electron.Menu | undefined
export function createMenu() {
menuEdit = new Menu()
menuEdit.append(new MenuItem({ label: '剪切', role: 'cut' }))
menuEdit.append(new MenuItem({ label: '复制', role: 'copy' }))
menuEdit.append(new MenuItem({ label: '粘贴', role: 'paste' }))
menuEdit.append(new MenuItem({ label: '删除', role: 'delete' }))
menuEdit.append(new MenuItem({ label: '全选', role: 'selectAll' }))
menuCopy = new Menu()
menuCopy.append(new MenuItem({ label: '复制', role: 'copy' }))
menuCopy.append(new MenuItem({ label: '全选', role: 'selectAll' }))
}
async function creatAria() {
try {
const enginePath: string = getStaticPath('engine')
const confPath: string = path.join(enginePath, 'aria2.conf')
const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c'
const basePath: string = path.join(enginePath, DEBUGGING ? path.join(process.platform, process.arch) : '')
let ariaFilePath: string = path.join(basePath, ariaPath)
if (!existsSync(ariaFilePath)) {
ShowError('找不到Aria程序文件', ariaFilePath)
return 0
}
const listenPort = await portIsOccupied(16800)
const options: SpawnOptions = {
stdio: is.dev() ? 'pipe' : 'ignore',
windowsHide: false
}
const args = [
`--stop-with-process=${process.pid}`,
`--conf-path=${confPath}`,
`--rpc-listen-port=${listenPort}`,
'-D'
]
execFile(`${ariaFilePath}`, args, options)
return listenPort
} catch (e: any) {
console.log(e)
}
return 0
}
ipcMain.on('WebToElectron', async (event, data) => {
let mainWindow = AppWindow.mainWindow
if (data.cmd && data.cmd === 'close') {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide()
} else if (data.cmd && data.cmd === 'exit') {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.destroy()
mainWindow = undefined
}
try {
app.exit()
} catch {
}
} else if (data.cmd && data.cmd === 'minsize') {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.minimize()
} else if (data.cmd && data.cmd === 'maxsize') {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
}
} else if (data.cmd && data.cmd === 'menuedit') {
if (menuEdit) menuEdit.popup()
} else if (data.cmd && data.cmd === 'menucopy') {
if (menuCopy) menuCopy.popup()
} else if (data.cmd && (Object.hasOwn(data.cmd, 'launchStartUp')
|| Object.hasOwn(data.cmd, 'launchStartUpShow'))) {
console.log("data.cmd", data.cmd)
const launchStart = data.cmd.launchStartUp
const launchStartShow = data.cmd.launchStartUpShow
const appName = path.basename(process.execPath)
// 设置开机自启
const settings: Electron.Settings = {
openAtLogin: launchStart,
path: process.execPath
}
// 显示主窗口
if (is.macOS()) {
settings.openAsHidden = !launchStartShow
} else {
settings.args = [
'--processStart', `${appName}`,
'--process-start-args', `"--hidden"`
]
!launchStartShow && settings.args.push('--openAsHidden')
}
app.setLoginItemSettings(settings)
} else if (data.cmd && Object.hasOwn(data.cmd, 'appUserDataPath')) {
const userDataPath = data.cmd.appUserDataPath
const localVersion = getResourcesPath('userdir.config')
writeFileSync(localVersion, userDataPath, 'utf-8')
} else {
event.sender.send('ElectronToWeb', 'mainsenddata')
}
})
ipcMain.on('WebToElectronCB', (event, data) => {
const mainWindow = AppWindow.mainWindow
if (data.cmd && data.cmd === 'maxsize') {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
event.returnValue = 'unmaximize'
} else {
mainWindow.maximize()
event.returnValue = 'maximize'
}
}
} else {
event.returnValue = 'backdata'
}
})
ipcMain.on('WebShowOpenDialogSync', (event, config) => {
dialog.showOpenDialog(AppWindow.mainWindow!, config).then((result) => {
event.returnValue = result.filePaths
})
})
ipcMain.on('WebShowSaveDialogSync', (event, config) => {
dialog.showSaveDialog(AppWindow.mainWindow!, config).then((result) => {
event.returnValue = result.filePath || ''
})
})
ipcMain.on('WebShowItemInFolder', (event, fullPath) => {
for (let i = 0; i < 5; i++) {
if (existsSync(fullPath)) break
if (fullPath.lastIndexOf(path.sep) > 0) {
fullPath = fullPath.substring(0, fullPath.lastIndexOf(path.sep))
} else return
}
if (fullPath.length > 2) shell.showItemInFolder(fullPath)
})
ipcMain.on('WebPlatformSync', (event) => {
const asarPath = app.getAppPath()
const appPath = app.getPath('userData')
event.returnValue = {
platform: process.platform,
arch: process.arch,
version: process.version,
execPath: process.execPath,
appPath: appPath,
asarPath: asarPath,
argv0: process.argv0
}
})
ipcMain.on('WebSpawnSync', (event, data) => {
try {
const options: SpawnOptions = {
stdio: 'ignore',
shell: true,
...data.options
}
const argsToStr = (args: string) => is.windows() ? `"${args}"` : `'${args}'`
if ((is.windows() || is.macOS()) && !existsSync(data.command)) {
event.returnValue = { error: '找不到文件' + data.command }
ShowError('找不到文件', data.command)
} else {
let command
if (is.macOS()) {
command = `open -a ${argsToStr(data.command)} ${data.command.includes('mpv.app') ? '--args ' : ''}`
} else {
command = `${argsToStr(data.command)}`
}
const subProcess = spawn(command, data.args, options)
console.log(command, data.args)
const isRunning = process.kill(subProcess.pid, 0)
subProcess.unref()
event.returnValue = {
pid: subProcess.pid,
isRunning: isRunning,
execCmd: data,
options: options,
exitCode: subProcess.exitCode
}
}
} catch (err: any) {
event.returnValue = { error: err }
}
})
ipcMain.on('WebExecSync', (event, data) => {
try {
const cmdArguments = []
cmdArguments.push(data.command)
if (data.args) cmdArguments.push(...data.args)
const finalCmd = cmdArguments.join(' ')
exec(finalCmd, (err: any) => {
event.returnValue = err
})
event.returnValue = ''
} catch (err: any) {
event.returnValue = { error: err }
}
})
ipcMain.on('WebSaveTheme', (event, data) => {
try {
const themeJson = getUserDataPath('theme.json')
writeFileSync(themeJson, `{"theme":"${data.theme || ''}"}`, 'utf-8')
} catch {
}
})
ipcMain.on('WebClearCookies', (event, data) => {
session.defaultSession.clearStorageData(data)
})
ipcMain.on('WebSetCookies', (event, data) => {
for (let i = 0, maxi = data.length; i < maxi; i++) {
const cookie = {
url: data[i].url,
name: data[i].name,
value: data[i].value,
domain: '.' + data[i].url.substring(data[i].url.lastIndexOf('/') + 1),
secure: data[i].url.indexOf('https://') == 0,
expirationDate: data[i].expirationDate
}
session.defaultSession.cookies.set(cookie).catch((err: any) => console.error(err))
}
})
ipcMain.on('WebClearCache', (event, data) => {
if (data.cache) {
session.defaultSession.clearCache()
session.defaultSession.clearAuthCache()
} else {
session.defaultSession.clearStorageData(data)
}
})
ipcMain.on('WebReload', (event, data) => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) AppWindow.mainWindow.reload()
})
ipcMain.on('WebRelaunch', (event, data) => {
app.relaunch()
try {
app.exit()
} catch {
}
})
ipcMain.handle('WebRelaunchAria', async (event, data) => {
return await startAria2c()
})
ipcMain.on('WebSetProgressBar', (event, data) => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) {
if (data.pro) {
AppWindow.mainWindow.setProgressBar(data.pro, { mode: data.mode || 'normal' })
} else AppWindow.mainWindow.setProgressBar(-1)
}
})
ipcMain.on('WebShutDown', (event, data) => {
if (is.macOS()) {
const shutdownCmd = 'osascript -e \'tell application "System Events" to shut down\''
exec(shutdownCmd, (err: any) => {
if (data.quitApp) {
try {
app.exit()
} catch {
}
}
if (err) {
// donothing
}
})
} else {
const cmdArguments = ['shutdown']
if (is.linux()) {
if (data.sudo) {
cmdArguments.unshift('sudo')
}
cmdArguments.push('-h')
cmdArguments.push('now')
}
if (is.windows()) {
cmdArguments.push('-s')
cmdArguments.push('-f')
cmdArguments.push('-t 0')
}
const finalcmd = cmdArguments.join(' ')
exec(finalcmd, (err: any) => {
if (data.quitApp) {
try {
app.exit()
} catch {
}
}
if (err) {
// donothing
}
})
}
})
ipcMain.on('WebSetProxy', (event, data) => {
// if (data.proxyUrl) app.commandLine.appendSwitch('proxy-server', data.proxyUrl)
// else app.commandLine.removeSwitch('proxy-server')
console.log(JSON.stringify(data))
if (data.proxyUrl) {
session.defaultSession.setProxy({ proxyRules: data.proxyUrl })
} else {
session.defaultSession.setProxy({})
}
})
ipcMain.on('WebOpenWindow', (event, data) => {
const win = creatElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main2', data.theme)
win.on('ready-to-show', function() {
win.webContents.send('setPage', data)
win.setTitle('预览窗口')
win.show()
})
})
ipcMain.on('WebOpenUrl', (event, data) => {
const win = new BrowserWindow({
show: false,
width: AppWindow.winWidth,
height: AppWindow.winHeight,
center: true,
minWidth: 680,
minHeight: 500,
icon: getStaticPath('icon_256.ico'),
useContentSize: true,
frame: true,
hasShadow: true,
autoHideMenuBar: true,
backgroundColor: data.theme && data.theme == 'dark' ? '#23232e' : '#ffffff',
webPreferences: {
spellcheck: false,
devTools: DEBUGGING,
sandbox: false,
webSecurity: false,
allowRunningInsecureContent: true,
backgroundThrottling: false,
enableWebSQL: false,
disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure'
}
})
win.on('ready-to-show', function() {
win.setTitle('预览窗口')
win.show()
})
win.loadURL(data.PageUrl, {
userAgent: ua,
httpReferrer: Referer
})
})
async function startAria2c() {
try {
const enginePath: string = getStaticPath('engine')
const confPath: string = path.join(enginePath, 'aria2.conf')
const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c'
const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '')
const ariaFilePath: string = path.join(basePath, ariaPath)
if (!existsSync(ariaFilePath)) {
ShowError('找不到Aria程序文件', ariaFilePath)
return 0
}
const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'`
const listenPort = await portIsOccupied(16800)
const options: SpawnOptions = {
shell: true,
stdio: is.dev() ? 'pipe' : 'ignore',
windowsHide: false,
windowsVerbatimArguments: true
}
const args = [
`--stop-with-process=${argsToStr(process.pid)}`,
`--conf-path=${argsToStr(confPath)}`,
`--rpc-listen-port=${argsToStr(listenPort)}`,
'-D'
]
spawn(`${argsToStr(ariaFilePath)}`, args, options)
return listenPort
} catch (e: any) {
console.log(e)
}
return 0
}

View File

@@ -1,234 +0,0 @@
import { AppWindow, createMainWindow, createTray } from './core/window'
import { app, ipcMain, session } from 'electron'
import is from 'electron-is'
import fixPath from 'fix-path'
import { release } from 'os'
import { getResourcesPath, getStaticPath } from './utils/mainfile'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import { EventEmitter } from 'node:events'
import exception from './core/exception'
import ipcEvent from './core/ipcEvent'
import path from 'path'
type UserToken = {
access_token: string;
open_api_access_token: string;
user_id: string;
refresh: boolean
}
export default class launch extends EventEmitter {
private userToken: UserToken = {
access_token: '',
open_api_access_token: '',
user_id: '',
refresh: false
}
constructor() {
super()
this.init()
}
init() {
this.start()
if (is.mas()) return
const gotSingleLock = app.requestSingleInstanceLock()
if (!gotSingleLock) {
app.exit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (commandLine && commandLine.join(' ').indexOf('exit') >= 0) {
this.hasExitArgv(commandLine)
} else if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) {
AppWindow.mainWindow.restore()
}
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
}
})
}
}
start() {
exception.handler()
this.setInitArgv()
this.loadUserData()
this.handleEvents()
this.handleAppReady()
}
setInitArgv() {
fixPath()
if (release().startsWith('6.1')) {
app.disableHardwareAcceleration()
}
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
process.env.DIST = path.join(__dirname, '../dist')
process.env.VITE_PUBLIC = app.isPackaged
? process.env.DIST
: path.join(process.env.DIST, '../public')
app.commandLine.appendSwitch('no-sandbox')
app.commandLine.appendSwitch('disable-web-security')
app.commandLine.appendSwitch('disable-renderer-backgrounding')
app.commandLine.appendSwitch('disable-site-isolation-trials')
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure,BlockInsecurePrivateNetworkRequests')
app.commandLine.appendSwitch('ignore-connections-limit', 'bj29-enet.cn-beijing.data.alicloudccp.com,bj29-hz.cn-hangzhou.data.alicloudccp.com,bj29.cn-beijing.data.alicloudccp.com,alicloudccp.com,api.aliyundrive.com,aliyundrive.com,api.alipan.com,alipan.com')
app.commandLine.appendSwitch('ignore-certificate-errors')
app.commandLine.appendSwitch('proxy-bypass-list', '*')
app.commandLine.appendSwitch('wm-window-animations-disabled')
app.commandLine.appendSwitch('enable-features', 'PlatformHEVCDecoderSupport')
app.commandLine.appendSwitch('force_high_performance_gpu')
app.name = 'alixby3'
if (is.windows()) {
app.setAppUserModelId('gaozhangmin')
}
this.hasExitArgv(process.argv)
}
hasExitArgv(args) {
if (args && args.join(' ').indexOf('exit') >= 0) {
app.exit()
}
}
loadUserData() {
const userData = getResourcesPath('userdir.config')
try {
if (existsSync(userData)) {
const configData = readFileSync(userData, 'utf-8')
if (configData) app.setPath('userData', configData)
}
} catch {
}
}
handleEvents() {
ipcEvent.handleEvents()
this.handleUserToken()
this.handleAppActivate()
this.handleAppWillQuit()
this.handleAppWindowAllClosed()
}
handleAppReady() {
app
.whenReady()
.then(() => {
try {
const localVersion = getResourcesPath('localVersion')
if (localVersion && existsSync(localVersion)) {
const version = readFileSync(localVersion, 'utf-8')
if (app.getVersion() > version) {
writeFileSync(localVersion, app.getVersion(), 'utf-8')
}
} else {
writeFileSync(localVersion, app.getVersion(), 'utf-8')
}
} catch (err) {
}
session.defaultSession.webRequest.onBeforeSendHeaders((details, cb) => {
const shouldGieeReferer = details.url.indexOf('gitee.com') > 0
const shouldBiliBili = details.url.indexOf('bilibili.com') > 0
const shouldQQTv = details.url.indexOf('v.qq.com') > 0 || details.url.indexOf('video.qq.com') > 0
const shouldAliPanOrigin = details.url.indexOf('.aliyundrive.com') > 0 || details.url.indexOf('.alipan.com') > 0
const shouldAliReferer = !shouldQQTv && !shouldBiliBili && !shouldGieeReferer && (!details.referrer || details.referrer.trim() === '' || /(\/localhost:)|(^file:\/\/)|(\/127.0.0.1:)/.exec(details.referrer) !== null)
const shouldToken = details.url.includes('alipan') && details.url.includes('download')
const shouldOpenApiToken = details.url.includes('adrive/v1.0') || details.url.includes('adrive/v1.1')
cb({
cancel: false,
requestHeaders: {
...details.requestHeaders,
...(shouldGieeReferer && {
Referer: 'https://gitee.com/'
}),
...(shouldAliPanOrigin && {
Origin: 'https://www.alipan.com'
}),
...(shouldAliReferer && {
Referer: 'https://www.alipan.com/'
}),
...(shouldBiliBili && {
Referer: 'https://www.bilibili.com/',
Cookie: 'buvid_fp=4e5ab1b80f684b94efbf0d2f4721913e;buvid3=0679D9AB-1548-ED1E-B283-E0114517315E63379infoc;buvid4=990C4544-0943-1FBF-F13C-4C42A4EA97AA63379-024020214-83%2BAINcbQP917Ye0PjtrCg%3D%3D;',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0'
}),
...(shouldQQTv && {
Referer: 'https://m.v.qq.com/',
Origin: 'https://m.v.qq.com',
'user-agent': 'Mozilla/5.0 (Linux; Android 13; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0'
}),
...(shouldToken && {
Authorization: this.userToken.access_token
}),
...(shouldOpenApiToken && {
Authorization: this.userToken.open_api_access_token
}),
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.12.0 Chrome/108.0.5359.215 Electron/22.3.24 Safari/537.36',
'X-Canary': 'client=windows,app=adrive,version=v4.12.0',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
})
})
session.defaultSession.loadExtension(getStaticPath('crx'), { allowFileAccess: true }).then(() => {
createMainWindow()
createTray()
})
})
.catch((err: any) => {
console.log(err)
})
}
handleUserToken() {
ipcMain.on('WebUserToken', (event, data) => {
if (data.login) {
this.userToken = data
} else if (this.userToken.user_id == data.user_id) {
this.userToken = data
// ShowError('WebUserToken', 'update' + data.name)
} else {
// ShowError('WebUserToken', 'nothing' + data.name)
}
})
}
handleAppActivate() {
app.on('activate', () => {
if (!AppWindow.mainWindow || AppWindow.mainWindow.isDestroyed()) createMainWindow()
else {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
}
})
}
handleAppWillQuit() {
app.on('will-quit', () => {
try {
if (AppWindow.appTray) {
AppWindow.appTray.destroy()
AppWindow.appTray = undefined
}
} catch {
}
})
}
handleAppWindowAllClosed() {
app.on('window-all-closed', () => {
if (is.macOS()) {
AppWindow.appTray?.destroy()
} else {
app.quit() // 未测试应该使用哪一个
}
})
}
}

View File

@@ -3,11 +3,13 @@ import path from 'path'
import { copyFileSync, existsSync, rmSync } from 'fs'
import is from 'electron-is'
const DEBUGGING = !app.isPackaged
let NewCopyed = false
let NewSaved = false
export function getAsarPath(fileName: string) {
if (is.dev()) {
if (DEBUGGING) {
const basePath = path.resolve(app.getAppPath())
return path.join(basePath, fileName)
} else {
@@ -35,16 +37,16 @@ export function getAsarPath(fileName: string) {
export function getResourcesPath(fileName: string) {
let basePath = path.resolve(app.getAppPath(), '..')
if (is.dev()) basePath = path.resolve(app.getAppPath(), '.')
if (DEBUGGING) basePath = path.resolve(app.getAppPath(), '.')
return path.join(basePath, fileName)
}
export function getStaticPath(fileName: string) {
let basePath = path.resolve(app.getAppPath(), '..')
if (is.dev()) basePath = path.resolve(app.getAppPath(), './static')
if (DEBUGGING) basePath = path.resolve(app.getAppPath(), './static')
if (fileName.startsWith('icon')) {
if (fileName == 'icon_256x256.ico' && !is.windows()) {
fileName = path.join('images', 'icon_24x24.png')
if (fileName == 'icon_256.ico' && !is.windows()) {
fileName = path.join('images', 'icon_24.png')
} else {
fileName = path.join('images', fileName)
}

View File

@@ -1,13 +1,17 @@
import net from 'net'
export function portIsOccupied(port: number) {
const server = net.createServer().listen(port, '0.0.0.0')
return new Promise<number>((resolve, reject) => {
server.on('listening', () => {
console.log(`the server is runnint on port ${port}`)
server.close()
resolve(port) // 返回可用端口
})
server.on('error', (err: any) => {
if (err.code === 'EADDRINUSE') {
resolve(portIsOccupied(port + 1)) // 如传入端口号被占用则 +1
@@ -18,5 +22,7 @@ export function portIsOccupied(port: number) {
resolve(port)
}
})
})
}

View File

@@ -1,18 +1,17 @@
import { app, BrowserWindow, ipcMain, Menu, MessageChannelMain, nativeTheme, screen, shell, Tray } from 'electron'
import { getAsarPath, getStaticPath, getUserDataPath } from '../utils/mainfile'
import { app, BrowserWindow, dialog, Menu, MessageChannelMain, nativeTheme, Tray, screen } from 'electron'
import { getAsarPath, getStaticPath, getUserDataPath } from './mainfile'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import path from "path";
import is from 'electron-is'
import { ShowErrorAndRelaunch } from './dialog'
const DEBUGGING = !app.isPackaged
export const ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.12.0 Chrome/108.0.5359.215 Electron/22.3.24 Safari/537.36'
export const Referer = 'https://www.alipan.com/'
export const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33'
export const Referer = 'https://www.aliyundrive.com/'
export const AppWindow: {
mainWindow: BrowserWindow | undefined
uploadWindow: BrowserWindow | undefined
downloadWindow: BrowserWindow | undefined
videoWindow: BrowserWindow | undefined
auidoWindow: BrowserWindow | undefined
appTray: Tray | undefined
winWidth: number
winHeight: number
@@ -21,14 +20,340 @@ export const AppWindow: {
mainWindow: undefined,
uploadWindow: undefined,
downloadWindow: undefined,
videoWindow: undefined,
auidoWindow: undefined,
appTray: undefined,
winWidth: 0,
winHeight: 0,
winTheme: ''
}
export function createMainWindow() {
Menu.setApplicationMenu(null)
try {
const configJson = getUserDataPath('config.json')
if (existsSync(configJson)) {
const configData = JSON.parse(readFileSync(configJson, 'utf-8'))
AppWindow.winWidth = configData.width
AppWindow.winHeight = configData.height
}
} catch {}
try {
const themeJson = getUserDataPath('theme.json')
if (existsSync(themeJson)) {
const themeData = JSON.parse(readFileSync(themeJson, 'utf-8'))
AppWindow.winTheme = themeData.theme
}
} catch {}
if (AppWindow.winWidth <= 0) {
try {
const size = screen.getPrimaryDisplay().workAreaSize
let width = size.width * 0.677
const height = size.height * 0.866
if (width > AppWindow.winWidth) AppWindow.winWidth = width
if (size.width >= 970 && width < 970) width = 970
if (AppWindow.winWidth > 1080) AppWindow.winWidth = 1080
if (height > AppWindow.winHeight) AppWindow.winHeight = height
if (AppWindow.winHeight > 720) AppWindow.winHeight = 720
} catch {
AppWindow.winWidth = 970
AppWindow.winHeight = 600
}
}
AppWindow.mainWindow = creatElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main', AppWindow.winTheme)
AppWindow.mainWindow.on('resize', () => {
debounceResize(function () {
try {
if (AppWindow.mainWindow && AppWindow.mainWindow.isMaximized() == false && AppWindow.mainWindow.isMinimized() == false && AppWindow.mainWindow.isFullScreen() == false) {
const s = AppWindow.mainWindow!.getSize()
const configJson = getUserDataPath('config.json')
writeFileSync(configJson, `{"width":${s[0].toString()},"height": ${s[1].toString()}}`, 'utf-8')
}
} catch {}
}, 3000)
})
AppWindow.mainWindow.on('close', (event) => {
if (is.macOS()) {
// donothing
} else {
event.preventDefault()
AppWindow.mainWindow?.hide()
}
})
AppWindow.mainWindow.on('closed', (event: any) => {
app.quit()
})
AppWindow.mainWindow.on('ready-to-show', function () {
AppWindow.mainWindow!.webContents.send('setPage', { page: 'PageMain' })
AppWindow.mainWindow!.webContents.send('setTheme', { dark: nativeTheme.shouldUseDarkColors })
AppWindow.mainWindow!.setTitle('小白羊云盘')
if (is.windows() && process.argv && process.argv.join(' ').indexOf('--openAsHidden') < 0) {
AppWindow.mainWindow!.show()
} else if (is.macOS() && !app.getLoginItemSettings().wasOpenedAsHidden){
AppWindow.mainWindow!.show()
}
if (is.linux()){
AppWindow.mainWindow!.show()
}
creatUploadPort()
creatDownloadPort()
})
AppWindow.mainWindow.webContents.on('render-process-gone', function (event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed') {
ShowErrorAndRelanch('(⊙o⊙)?小白羊遇到错误崩溃了', details.reason)
}
})
creatUpload()
creatDownload()
}
nativeTheme.on('updated', () => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed())
AppWindow.mainWindow.webContents.send('setTheme', {
dark: nativeTheme.shouldUseDarkColors
})
})
function ShowErrorAndRelanch(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title + ',小白羊将自动退出',
message: '错误信息:' + errmsg
})
.then((_) => {
setTimeout(() => {
app.relaunch()
try {
app.exit()
} catch {}
}, 100)
})
}
export function ShowErrorAndExit(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title + ',小白羊将自动退出',
message: '错误信息:' + errmsg
})
.then((_) => {
setTimeout(() => {
try {
app.exit()
} catch {}
}, 100)
})
}
export function ShowError(title: string, errmsg: string) {
dialog
.showMessageBox({
type: 'error',
buttons: ['ok'],
title: title,
message: '错误信息:' + errmsg
})
.then((_) => {})
}
let timerResize: NodeJS.Timeout | undefined
const debounceResize = (fn: any, wait: number) => {
if (timerResize) clearTimeout(timerResize)
timerResize = setTimeout(() => {
fn()
timerResize = undefined
}, wait)
}
export function createTray() {
const trayMenuTemplate = [
{
label: '显示主界面',
click: function () {
if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
} else {
createMainWindow()
}
}
},
{
label: '彻底退出并停止下载',
click: function () {
if (AppWindow.mainWindow) {
AppWindow.mainWindow.destroy()
AppWindow.mainWindow = undefined
}
app.quit()
}
}
]
const icon = getStaticPath('icon_256.ico')
AppWindow.appTray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
AppWindow.appTray.setToolTip('小白羊云盘')
AppWindow.appTray.setContextMenu(contextMenu)
AppWindow.appTray.on('click', () => {
if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
} else {
createMainWindow()
}
})
}
export function creatUpload() {
if (AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) return
AppWindow.uploadWindow = creatElectronWindow(10, 10, false, 'main', 'dark', false)
AppWindow.uploadWindow.on('ready-to-show', function () {
creatUploadPort()
AppWindow.uploadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'upload' } })
AppWindow.uploadWindow!.setTitle('小白羊云盘上传进程')
})
AppWindow.uploadWindow.webContents.on('render-process-gone', function (event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') {
try {
AppWindow.uploadWindow?.destroy()
} catch {}
AppWindow.uploadWindow = undefined
creatUpload()
}
})
AppWindow.uploadWindow.hide()
}
export function creatDownload() {
if (AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) return
AppWindow.downloadWindow = creatElectronWindow(10, 10, false, 'main', 'dark', false)
AppWindow.downloadWindow.on('ready-to-show', function () {
creatDownloadPort()
AppWindow.downloadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'download' } })
AppWindow.downloadWindow!.setTitle('小白羊云盘下载进程')
})
AppWindow.downloadWindow.webContents.on('render-process-gone', function (event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') {
try {
AppWindow.downloadWindow?.destroy()
} catch {}
AppWindow.downloadWindow = undefined
creatDownload()
}
})
AppWindow.downloadWindow.webContents.closeDevTools()
AppWindow.downloadWindow.hide()
}
export function creatElectronWindow(width: number, height: number, center: boolean, page: string, theme: string, devTools: boolean = true) {
const win = new BrowserWindow({
show: false,
width: width,
height: height,
minWidth: width > 680 ? 680 : width,
minHeight: height > 500 ? 500 : height,
center: center,
icon: getStaticPath('icon_256.ico'),
useContentSize: true,
frame: false,
transparent: false,
hasShadow: width > 680,
autoHideMenuBar: true,
backgroundColor: theme && theme == 'dark' ? '#23232e' : '#ffffff',
webPreferences: {
spellcheck: false,
devTools: DEBUGGING,
webviewTag: true,
nodeIntegration: true,
nodeIntegrationInWorker: true,
sandbox: false,
webSecurity: false,
allowRunningInsecureContent: true,
contextIsolation: false,
backgroundThrottling: false,
enableWebSQL: true,
disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure',
preload: getAsarPath('dist/electron/preload/index.js')
}
})
win.removeMenu()
if (DEBUGGING) {
const url = `http://localhost:${process.env.VITE_DEV_SERVER_PORT}`
win.loadURL(url, { userAgent: ua, httpReferrer: Referer })
} else {
win.loadURL('file://' + getAsarPath('dist/' + page + '.html'), {
userAgent: ua,
httpReferrer: Referer
})
}
if (DEBUGGING && devTools) {
if (width < 100) win.setSize(800, 600)
win.show()
win.webContents.openDevTools()
} else {
win.webContents.on('devtools-opened', () => {
if (win && win.webContents.getType() === 'webview') {
win.webContents.closeDevTools()
}
})
}
win.webContents.on('before-input-event', (_, input: Electron.Input) => {
if (input.type === 'keyDown' && input.control && input.shift && input.key === 'F12') {
win.webContents.isDevToolsOpened()
? win.webContents.closeDevTools()
: win.webContents.openDevTools({ mode: 'undocked' })
}
})
win.webContents.on('did-create-window', (childWindow) => {
if (is.windows()) {
childWindow.setMenu(null)
}
})
return win
}
function creatUploadPort() {
debounceUpload(function () {
if (AppWindow.mainWindow && AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) {
const { port1, port2 } = new MessageChannelMain()
AppWindow.mainWindow.webContents.postMessage('setUploadPort', undefined, [port1])
AppWindow.uploadWindow.webContents.postMessage('setPort', undefined, [port2])
}
}, 1000)
}
function creatDownloadPort() {
debounceDownload(function () {
if (AppWindow.mainWindow && AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) {
const { port1, port2 } = new MessageChannelMain()
AppWindow.mainWindow.webContents.postMessage('setDownloadPort', undefined, [port1])
AppWindow.downloadWindow.webContents.postMessage('setPort', undefined, [port2])
}
}, 1000)
}
let timerUpload: NodeJS.Timeout | undefined
const debounceUpload = (fn: any, wait: number) => {
if (timerUpload) {
@@ -49,368 +374,3 @@ const debounceDownload = (fn: any, wait: number) => {
timerDownload = undefined
}, wait)
}
let timerResize: NodeJS.Timeout | undefined
const debounceResize = (fn: any, wait: number) => {
if (timerResize) clearTimeout(timerResize)
timerResize = setTimeout(() => {
fn()
timerResize = undefined
}, wait)
}
nativeTheme.on('updated', () => {
if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed())
AppWindow.mainWindow.webContents.send('setTheme', {
dark: nativeTheme.shouldUseDarkColors
})
})
export function createMainWindow() {
Menu.setApplicationMenu(null)
try {
const configJson = getUserDataPath('config.json')
if (existsSync(configJson)) {
const configData = JSON.parse(readFileSync(configJson, 'utf-8'))
AppWindow.winWidth = configData.width
AppWindow.winHeight = configData.height
}
} catch {
}
try {
const themeJson = getUserDataPath('theme.json')
if (existsSync(themeJson)) {
const themeData = JSON.parse(readFileSync(themeJson, 'utf-8'))
AppWindow.winTheme = themeData.theme
}
} catch {
}
if (AppWindow.winWidth <= 0) {
try {
const size = screen.getPrimaryDisplay().workAreaSize
let width = size.width * 0.677
const height = size.height * 0.866
if (width > AppWindow.winWidth) AppWindow.winWidth = width
if (size.width >= 970 && width < 970) width = 970
if (AppWindow.winWidth > 1080) AppWindow.winWidth = 1080
if (height > AppWindow.winHeight) AppWindow.winHeight = height
if (AppWindow.winHeight > 720) AppWindow.winHeight = 720
} catch {
AppWindow.winWidth = 970
AppWindow.winHeight = 600
}
}
AppWindow.mainWindow = createElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main', AppWindow.winTheme)
AppWindow.mainWindow.on('resize', () => {
debounceResize(function() {
try {
if (AppWindow.mainWindow
&& !AppWindow.mainWindow.isMaximized()
&& !AppWindow.mainWindow.isMinimized()
&& !AppWindow.mainWindow.isFullScreen()) {
const s = AppWindow.mainWindow!.getSize()
const configJson = getUserDataPath('config.json')
writeFileSync(configJson, `{"width":${s[0].toString()},"height": ${s[1].toString()}}`, 'utf-8')
}
} catch {
}
}, 3000)
})
AppWindow.mainWindow.on('close', (event) => {
if (is.macOS()) {
// donothing
} else {
event.preventDefault()
AppWindow.mainWindow?.hide()
}
})
AppWindow.mainWindow.on('closed', (event: any) => {
app.quit()
})
AppWindow.mainWindow.on('ready-to-show', function() {
AppWindow.mainWindow!.webContents.send('setPage', { page: 'PageMain' })
AppWindow.mainWindow!.webContents.send('setTheme', { dark: nativeTheme.shouldUseDarkColors })
AppWindow.mainWindow.maximize()
AppWindow.mainWindow!.setTitle('阿里云盘小白羊')
if (is.windows() && process.argv && process.argv.join(' ').indexOf('--openAsHidden') < 0) {
AppWindow.mainWindow!.show()
} else if (is.macOS() && !app.getLoginItemSettings().wasOpenedAsHidden) {
AppWindow.mainWindow!.show()
}
if (is.linux()) {
AppWindow.mainWindow!.show()
}
creatUploadPort()
creatDownloadPort()
})
AppWindow.mainWindow.webContents.on('render-process-gone', function(event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed') {
ShowErrorAndRelaunch('(⊙o⊙)?小白羊遇到错误崩溃了', details.reason)
}
})
createUpload()
createDownload()
}
export function createTray() {
const trayMenuTemplate = [
{
label: '显示主界面',
click: function() {
if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
} else {
createMainWindow()
}
}
},
{
label: '彻底退出并停止下载',
click: function() {
if (AppWindow.mainWindow) {
AppWindow.mainWindow.destroy()
AppWindow.mainWindow = undefined
}
app.quit()
}
}
]
const icon = getStaticPath('icon_256x256.ico')
AppWindow.appTray = new Tray(icon)
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
AppWindow.appTray.setToolTip('阿里云盘小白羊')
AppWindow.appTray.setContextMenu(contextMenu)
AppWindow.appTray.on('click', () => {
if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) {
if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore()
AppWindow.mainWindow.show()
AppWindow.mainWindow.focus()
} else {
createMainWindow()
}
})
}
export function createElectronWindow(width: number, height: number, center: boolean, page: string, theme: string, devTools: boolean = true) {
const win = new BrowserWindow({
show: false,
width: width,
height: height,
minWidth: width > 680 ? 680 : width,
minHeight: height > 500 ? 500 : height,
center: center,
icon: getStaticPath('icon_256x256.ico'),
useContentSize: true,
frame: false,
transparent: false,
hasShadow: width > 680,
autoHideMenuBar: true,
backgroundColor: theme && theme == 'dark' ? '#23232e' : '#ffffff',
webPreferences: {
spellcheck: false,
devTools: true,
webviewTag: true,
nodeIntegration: true,
nodeIntegrationInWorker: true,
sandbox: false,
webSecurity: false,
allowRunningInsecureContent: true,
contextIsolation: false,
backgroundThrottling: false,
enableWebSQL: true,
disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure',
preload: getAsarPath('dist/electron/preload/index.js')
}
})
win.removeMenu()
if (DEBUGGING) {
win.loadURL(process.env.VITE_DEV_SERVER_URL, { userAgent: ua, httpReferrer: Referer })
} else {
win.loadURL('file://' + getAsarPath('dist/' + page + '.html'), {
userAgent: ua,
httpReferrer: Referer
})
}
if (DEBUGGING && devTools) {
if (width < 100) {
win.setSize(800, 680)
}
win.show()
win.webContents.openDevTools({ mode: 'bottom' })
} else {
win.webContents.on('devtools-opened', () => {
if (win && win.webContents.getType() === 'webview') {
win.webContents.closeDevTools()
}
})
}
if (page == 'main2') {
handleWinCmd(win)
}
handleWebView(win)
win.webContents.on('will-navigate', (e, url) => {
e.preventDefault()
if (!url.includes(process.env.VITE_DEV_SERVER_URL)) {
shell.openExternal(url)
}
})
win.webContents.on('did-create-window', (childWindow) => {
if (is.windows()) {
childWindow.setMenu(null)
}
})
return win
}
function handleWebView(win: BrowserWindow) {
// 处理DevTools
win.webContents.on('before-input-event', (_, input: Electron.Input) => {
if (input.type === 'keyDown' && input.control && input.shift && input.key === 'F12') {
win.webContents.isDevToolsOpened()
? win.webContents.closeDevTools()
: win.webContents.openDevTools({ mode: 'undocked' })
}
})
// 处理webview跳转
win.webContents.addListener('did-attach-webview', (event, webContent) => {
webContent.on('before-input-event', (_, input: Electron.Input) => {
if (input.type === 'keyDown' && input.control && input.shift && input.key === 'F12') {
webContent.isDevToolsOpened()
? webContent.closeDevTools()
: webContent.openDevTools({ mode: 'undocked' })
}
})
// 不允许的网址则阻止页面跳转并拉取浏览器展示页面
webContent.setWindowOpenHandler((details) => {
let url = details.url
if (!/(aliyundrive|alipan).com\/s\/[0-9a-zA-Z_]{11,}/.test(url)) {
webContent.loadURL(url)
} else {
win.webContents.send('webview-new-window', webContent.id, details)
}
return { action: 'deny' }
})
webContent.on('will-redirect', (e, url) => {
if (!/(aliyundrive|alipan).com\/s\/[0-9a-zA-Z_]{11,}/.test(url)) {
webContent.loadURL(url)
} else {
win.webContents.send('webview-redirect', webContent.id, url)
}
e.preventDefault()
})
// 拦截链接跳转
webContent.on('will-navigate', (e, url) => {
if (/(aliyundrive|alipan).com\/s\/[0-9a-zA-Z_]{11,}/.test(url)) {
e.preventDefault()
}
})
})
}
function handleWinCmd(win: BrowserWindow) {
ipcMain.on('WebToWindow', (event, data) => {
if (data.cmd && data.cmd === 'close') {
if (win && !win.isDestroyed()) win.close()
} else if (data.cmd && data.cmd === 'minsize') {
if (win && !win.isDestroyed()) win.minimize()
} else if (data.cmd && data.cmd === 'top') {
if (win && !win.isDestroyed()) {
if (win.isAlwaysOnTop()) {
event.returnValue = 'untop'
win.setAlwaysOnTop(false)
} else {
event.returnValue = 'top'
win.setAlwaysOnTop(true, 'status')
}
}
} else if (data.cmd && data.cmd === 'maxsize') {
if (win && !win.isDestroyed()) {
if (win.isMaximized()) {
event.returnValue = 'unmaximize'
win.unmaximize()
} else {
event.returnValue = 'maximize'
win.maximize()
}
}
}
})
}
function creatUploadPort() {
debounceUpload(function() {
if (AppWindow.mainWindow && AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) {
const { port1, port2 } = new MessageChannelMain()
AppWindow.mainWindow.webContents.postMessage('setUploadPort', undefined, [port1])
AppWindow.uploadWindow.webContents.postMessage('setPort', undefined, [port2])
}
}, 1000)
}
function creatDownloadPort() {
debounceDownload(function() {
if (AppWindow.mainWindow && AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) {
const { port1, port2 } = new MessageChannelMain()
AppWindow.mainWindow.webContents.postMessage('setDownloadPort', undefined, [port1])
AppWindow.downloadWindow.webContents.postMessage('setPort', undefined, [port2])
}
}, 1000)
}
function createUpload() {
if (AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) return
AppWindow.uploadWindow = createElectronWindow(10, 10, false, 'main', 'dark', false)
AppWindow.uploadWindow.on('ready-to-show', function() {
creatUploadPort()
AppWindow.uploadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'upload' } })
AppWindow.uploadWindow!.setTitle('阿里云盘小白羊上传进程')
})
AppWindow.uploadWindow.webContents.on('render-process-gone', function(event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') {
try {
AppWindow.uploadWindow?.destroy()
} catch {
}
AppWindow.uploadWindow = undefined
createUpload()
}
})
// AppWindow.uploadWindow.webContents.openDevTools({ mode: 'undocked' })
AppWindow.uploadWindow.hide()
}
function createDownload() {
if (AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) return
AppWindow.downloadWindow = createElectronWindow(10, 10, false, 'main', 'dark', false)
AppWindow.downloadWindow.on('ready-to-show', function() {
creatDownloadPort()
AppWindow.downloadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'download' } })
AppWindow.downloadWindow!.setTitle('阿里云盘小白羊下载进程')
})
AppWindow.downloadWindow.webContents.on('render-process-gone', function(event, details) {
if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') {
try {
AppWindow.downloadWindow?.destroy()
} catch {
}
AppWindow.downloadWindow = undefined
createDownload()
}
})
AppWindow.downloadWindow.webContents.closeDevTools()
AppWindow.downloadWindow.hide()
}

View File

@@ -4,116 +4,96 @@ window.Electron = Electron
process.noAsar = true
window.platform = process.platform
window.WebToElectron = function(data: any) {
window.WebToElectron = function (data: any) {
try {
ipcRenderer.send('WebToElectron', data)
} catch {
}
} catch {}
}
window.WebToWindow = function(data: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebToWindow', data)
callback && callback(backData)
} catch {
}
}
window.WebToElectronCB = function(data: any, callback: any) {
window.WebToElectronCB = function (data: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebToElectronCB', data)
callback(backData)
} catch {
}
} catch {}
}
ipcRenderer.on('MainSendToken', function(event, arg) {
ipcRenderer.on('ElectronToWeb', function (event, arg) {
})
ipcRenderer.on('MainSendToken', function (event, arg) {
try {
window.postMessage(arg)
} catch {
}
} catch {}
})
window.WebSpawnSync = function(data: any, callback: any) {
window.WebSpawnSync = function (data: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebSpawnSync', data)
callback(backData)
} catch {
}
} catch {}
}
window.WebExecSync = function(data: any, callback: any) {
window.WebExecSync = function (data: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebExecSync', data)
callback(backData)
} catch {
}
} catch {}
}
window.WebShowOpenDialogSync = function(config: any, callback: any) {
window.WebShowOpenDialogSync = function (config: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebShowOpenDialogSync', config)
callback(backData)
} catch {
}
} catch {}
}
window.WebShowSaveDialogSync = function(config: any, callback: any) {
window.WebShowSaveDialogSync = function (config: any, callback: any) {
try {
const backData = ipcRenderer.sendSync('WebShowSaveDialogSync', config)
callback(backData)
} catch {
}
} catch {}
}
window.WebShowItemInFolder = function(fullPath: string) {
window.WebShowItemInFolder = function (fullPath: string) {
try {
ipcRenderer.send('WebShowItemInFolder', fullPath)
} catch {
}
} catch {}
}
window.WebPlatformSync = function(callback: any) {
window.WebPlatformSync = function (callback: any) {
try {
const backData = ipcRenderer.sendSync('WebPlatformSync')
callback(backData)
} catch {
}
} catch {}
}
window.WebClearCookies = function(data: any) {
window.WebClearCookies = function (data: any) {
try {
ipcRenderer.send('WebClearCookies', data)
} catch {
}
} catch {}
}
window.WebClearCache = function(data: any) {
window.WebClearCache = function (data: any) {
try {
ipcRenderer.send('WebClearCache', data)
} catch {
}
} catch {}
}
window.WebUserToken = function(data: any) {
window.WebUserToken = function (data: any) {
try {
ipcRenderer.send('WebUserToken', data)
} catch {
}
} catch {}
}
window.WebSaveTheme = function(data: any) {
window.WebSaveTheme = function (data: any) {
try {
ipcRenderer.send('WebSaveTheme', data)
} catch {
}
} catch {}
}
window.WebReload = function(data: any) {
window.WebReload = function (data: any) {
try {
ipcRenderer.send('WebReload', data)
} catch {
}
} catch {}
}
window.WebRelaunch = function(data: any) {
window.WebRelaunch = function (data: any) {
try {
ipcRenderer.send('WebRelaunch', data)
} catch {
}
} catch {}
}
window.WebRelaunchAria = async function() {
try {
@@ -122,109 +102,69 @@ window.WebRelaunchAria = async function() {
return 0
}
}
window.WebRelaunchAlist = async function() {
try {
return await ipcRenderer.invoke('WebRelaunchAlist')
} catch {
return 0
}
}
window.WebResetAlistPwd = async function(data: any) {
try {
return await ipcRenderer.invoke('WebResetAlistPwd', data)
} catch {
return 0
}
}
window.WebSetProgressBar = function(data: any) {
window.WebSetProgressBar = function (data: any) {
try {
ipcRenderer.send('WebSetProgressBar', data)
} catch {
}
} catch {}
}
window.WebGetCookies = async function(data: any) {
try {
return await ipcRenderer.invoke('WebGetCookies', data)
} catch {
}
}
window.WebSetCookies = function(cookies: any) {
window.WebSetCookies = function (cookies: any) {
try {
ipcRenderer.send('WebSetCookies', cookies)
} catch {
}
} catch {}
}
window.WebOpenWindow = function(data: any) {
window.WebOpenWindow = function (data: any) {
try {
ipcRenderer.send('WebOpenWindow', data)
} catch {
}
} catch {}
}
window.WebOpenUrl = function(data: any) {
window.WebOpenUrl = function (data: any) {
try {
ipcRenderer.send('WebOpenUrl', data)
} catch {
}
} catch {}
}
window.WebShutDown = function(data: any) {
window.WebShutDown = function (data: any) {
try {
ipcRenderer.send('WebShutDown', data)
} catch {
}
} catch {}
}
window.WebSetProxy = function(data: { proxyUrl: string }) {
window.WebSetProxy = function (data: { proxyUrl: string }) {
try {
ipcRenderer.send('WebSetProxy', data)
} catch {
}
} catch {}
}
function createRightMenu() {
window.addEventListener('contextmenu', (e) => {
window.addEventListener(
'contextmenu',
(e) => {
try {
if (e) e.preventDefault()
const target = e.target as HTMLElement
// 检查页面是否是有选择的文本 这里显示复制和剪切选项是否可见
const selectText = !!window.getSelection().toString()
if (selectText || isEleEditable(target)) {
// 读取剪切板是否有文本 这里传递粘贴选项是否可见
const showPaste = !!navigator.clipboard.readText()
// 判断ReadOnly
const isReadOnly = target.hasAttribute('readonly')
// 发送给主进程让它显示菜单
ipcRenderer.send('show-context-menu', {
showPaste: !isReadOnly && showPaste,
showCopy: selectText,
showCut: !isReadOnly && selectText
})
}
if (isEleEditable(e.target)) {
ipcRenderer.send('WebToElectron', { cmd: 'menuedit' })
} else {
const selectText = window.getSelection()?.toString()
if (selectText) ipcRenderer.send('WebToElectron', { cmd: 'menucopy' })
}
} catch {}
},
false
)
}
function isEleEditable(e: any): boolean {
if (!e) return false
if (e.tagName === 'TEXTAREA'
|| (e.tagName === 'INPUT' && e.type !== 'checkbox')
|| e.contentEditable == 'true') {
if (!e) {
return false
}
if ((e.tagName === 'INPUT' && e.type !== 'checkbox') || e.tagName === 'TEXTAREA' || e.contentEditable == 'true') {
return true
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return isEleEditable(e.parentNode)
}
}
createRightMenu()
// fix: new-windows event
ipcRenderer.on('webview-new-window', (e, webContentsId, details) => {
const webview = document.getElementById('webview') as any
const evt = new Event('new-window', { bubbles: true, cancelable: false })
webview.dispatchEvent(Object.assign(evt, details))
})
ipcRenderer.on('webview-redirect', (e, webContentsId, url) => {
const webview = document.getElementById('webview') as any
const evt = new Event('will-redirect', { bubbles: true, cancelable: false })
webview.dispatchEvent(Object.assign(evt, { url }))
})

View File

@@ -2,8 +2,6 @@
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production'
DIST: string
VITE_PUBLIC: string
readonly VITE_DEV_SERVER_HOST: string
readonly VITE_DEV_SERVER_PORT: string
}
@@ -13,7 +11,6 @@ declare interface Window {
platform: any
WinMsg: any
WebToElectron: any
WebToWindow: any
WebToElectronCB: any
WebSpawnSync: any
WebExecSync: any
@@ -28,10 +25,7 @@ declare interface Window {
WebReload: any
WebRelaunch: any
WebRelaunchAria: () => Promise<number>
WebRelaunchAlist: () => Promise<number>
WebSetProgressBar: any
WebResetAlistPwd:any
WebGetCookies: any
WebSetCookies: any
WebOpenWindow: any
WebOpenUrl: any

View File

@@ -4,5 +4,5 @@ export default {
'*.{vue}': ['stylelint --fix', 'prettier --write', 'eslint --cache --fix'],
'*.{less,css}': ['stylelint --fix', 'prettier --write'],
// typecheck
'src/**/{*.ts,*.tsx,*.vue,tsconfig.json}': ({ filenames }) => 'npm run typecheck'
'packages/renderer/**/{*.ts,*.tsx,*.vue,tsconfig.json}': ({ filenames }) => 'npm run typecheck'
}

View File

@@ -1,103 +1,74 @@
{
"name": "xbyyunpan",
"description": "阿里云盘小白羊",
"version": "3.13.1",
"description": "小白羊云盘",
"version": "3.11.15",
"license": "MIT",
"main": "dist/electron/main/index.js",
"author": {
"name": "gavingaozhangmin",
"name": "gaozhangmin",
"email": "gaozhangmin@gmail.com"
},
"homepage": "https://github.com/gaozhangmin/aliyunpan",
"license": "MIT",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build && electron-builder -wml",
"build:version": "node version.mjs",
"build:electron": "pnpm run build && electron-builder",
"build:test": "pnpm run build && electron-builder --dir"
"build": "vue-tsc --noEmit && vite build && electron-builder -wml"
},
"engines": {
"node": ">=18.0.0"
"node": ">=16.0.0"
},
"dependencies": {},
"devDependencies": {
"@arco-design/web-vue": "2.55.0",
"@arco-themes/vue-gi-demo": "^0.0.48",
"@types/crypto-js": "^4.2.2",
"@types/fast-levenshtein": "^0.0.4",
"@types/howler": "^2.2.11",
"@types/lodash": "^4.17.0",
"@types/markdown-it": "^13.0.7",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.11.30",
"@types/node-ssdp": "^4.0.4",
"@types/secp256k1": "^4.0.6",
"@types/thunky": "^1.1.2",
"@types/uuid": "^9.0.8",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/runtime-core": "3.4.21",
"@vue/runtime-dom": "3.4.21",
"ass-html5": "^0.3.5",
"autoprefixer": "^10.4.16",
"ant-design-vue": "4.1.2",
"@arco-design/web-vue": "^2.45.3",
"@electron/remote": "^2.0.9",
"@types/crypto-js": "^4.1.1",
"@types/fast-levenshtein": "^0.0.2",
"@types/howler": "^2.2.7",
"@types/lodash": "^4.14.184",
"@types/node": "^17.0.45",
"@types/secp256k1": "^4.0.3",
"@types/uuid": "^9.0.1",
"@vitejs/plugin-vue": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"ant-design-vue": "^3.2.20",
"aria2-lib": "1.0.1",
"artplayer": "^5.1.1",
"axios": "^1.6.8",
"chinese-simple2traditional": "^1.2.0",
"consola": "^3.2.3",
"cookie": "^0.6.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dexie": "^3.2.7",
"artplayer": "^5.0.9",
"axios": "^1.4.0",
"consola": "^3.1.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.7",
"dexie": "^3.2.3",
"dom-to-image": "^2.6.0",
"electron": "21.4.4",
"electron-builder": "^24.13.3",
"electron": "^21.4.4",
"electron-builder": "^23.6.0",
"electron-is": "^3.0.0",
"electron-log": "^4.4.8",
"fast-levenshtein": "^3.0.0",
"fast-xml-parser": "^4.3.6",
"fix-path": "^4.0.0",
"fuzzysort": "^2.0.4",
"hls.js": "^1.5.7",
"howler": "^2.2.4",
"fuzzysort": "^2.0.1",
"hls.js": "^1.4.3",
"howler": "^2.2.3",
"isomorphic-fetch": "^3.0.0",
"jassub": "^1.7.15",
"jschardet": "^3.0.0",
"less": "^4.2.0",
"lodash": "^4.17.21",
"markdown-it": "^14.1.0",
"mime-types": "^2.1.35",
"node-ssdp": "^4.0.1",
"pako": "^2.1.0",
"path-to-regexp": "^6.2.1",
"perf_hooks": "^0.0.1",
"pinia": "^2.1.7",
"pinia": "^2.0.35",
"secp256k1": "^5.0.0",
"semver": "^7.6.0",
"socks-proxy-agent": "^8.0.2",
"terser": "^5.29.2",
"thunky": "^1.1.0",
"tree-kill": "^1.2.2",
"tailwind-scrollbar": "^2.1.0",
"tailwindcss": "^3.3.5",
"typescript": "^5.4.2",
"upnp-client-ts": "^1.1.1",
"uuid": "^9.0.1",
"socks-proxy-agent": "^7.0.0",
"sudo-prompt": "^9.2.1",
"terser": "^5.15.0",
"typescript": "^4.8.2",
"uuid": "^9.0.0",
"uuid-by-string": "^4.0.0",
"viewerjs": "^1.11.6",
"vite": "5.2.2",
"vite-plugin-electron": "0.15.6",
"vite-plugin-electron-renderer": "0.14.5",
"vue": "3.4.21",
"vue-tsc": "^2.0.7",
"webdav-server": "^2.6.2",
"whacko": "^0.19.1",
"digest-fetch": "^3.0.4"
"viewerjs": "^1.10.5",
"vite": "^3.1.0",
"vite-plugin-electron": "^0.9.2",
"vite-plugin-resolve": "^2.1.2",
"vue": "^3.2.47",
"vue-tsc": "^1.6.4"
},
"debug": {
"env": {
"VITE_DEV_SERVER_HOSTNAME": "127.0.0.1",
"VITE_DEV_SERVER_PORT": 5173,
"VITE_DEV_SERVER_URL": "http://127.0.0.1:5173"
"VITE_DEV_SERVER_PORT": 3344,
"VITE_DEV_SERVER_URL": "http://127.0.0.1:3344"
}
},
"keywords": [

4359
aliyunpan/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

319
aliyunpan/public/comlink.js Normal file
View File

@@ -0,0 +1,319 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.Comlink = {}));
}(this, (function (exports) { 'use strict';
/**
* Copyright 2019 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const proxyMarker = Symbol("Comlink.proxy");
const createEndpoint = Symbol("Comlink.endpoint");
const releaseProxy = Symbol("Comlink.releaseProxy");
const throwMarker = Symbol("Comlink.thrown");
const isObject = (val) => (typeof val === "object" && val !== null) || typeof val === "function";
/**
* Internal transfer handle to handle objects marked to proxy.
*/
const proxyTransferHandler = {
canHandle: (val) => isObject(val) && val[proxyMarker],
serialize(obj) {
const { port1, port2 } = new MessageChannel();
expose(obj, port1);
return [port2, [port2]];
},
deserialize(port) {
port.start();
return wrap(port);
},
};
/**
* Internal transfer handler to handle thrown exceptions.
*/
const throwTransferHandler = {
canHandle: (value) => isObject(value) && throwMarker in value,
serialize({ value }) {
let serialized;
if (value instanceof Error) {
serialized = {
isError: true,
value: {
message: value.message,
name: value.name,
stack: value.stack,
},
};
}
else {
serialized = { isError: false, value };
}
return [serialized, []];
},
deserialize(serialized) {
if (serialized.isError) {
throw Object.assign(new Error(serialized.value.message), serialized.value);
}
throw serialized.value;
},
};
/**
* Allows customizing the serialization of certain values.
*/
const transferHandlers = new Map([
["proxy", proxyTransferHandler],
["throw", throwTransferHandler],
]);
function expose(obj, ep = self) {
ep.addEventListener("message", function callback(ev) {
if (!ev || !ev.data) {
return;
}
const { id, type, path } = Object.assign({ path: [] }, ev.data);
const argumentList = (ev.data.argumentList || []).map(fromWireValue);
let returnValue;
try {
const parent = path.slice(0, -1).reduce((obj, prop) => obj[prop], obj);
const rawValue = path.reduce((obj, prop) => obj[prop], obj);
switch (type) {
case "GET" /* GET */:
{
returnValue = rawValue;
}
break;
case "SET" /* SET */:
{
parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
returnValue = true;
}
break;
case "APPLY" /* APPLY */:
{
returnValue = rawValue.apply(parent, argumentList);
}
break;
case "CONSTRUCT" /* CONSTRUCT */:
{
const value = new rawValue(...argumentList);
returnValue = proxy(value);
}
break;
case "ENDPOINT" /* ENDPOINT */:
{
const { port1, port2 } = new MessageChannel();
expose(obj, port2);
returnValue = transfer(port1, [port1]);
}
break;
case "RELEASE" /* RELEASE */:
{
returnValue = undefined;
}
break;
default:
return;
}
}
catch (value) {
returnValue = { value, [throwMarker]: 0 };
}
Promise.resolve(returnValue)
.catch((value) => {
return { value, [throwMarker]: 0 };
})
.then((returnValue) => {
const [wireValue, transferables] = toWireValue(returnValue);
ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
if (type === "RELEASE" /* RELEASE */) {
// detach and deactive after sending release response above.
ep.removeEventListener("message", callback);
closeEndPoint(ep);
}
});
});
if (ep.start) {
ep.start();
}
}
function isMessagePort(endpoint) {
return endpoint.constructor.name === "MessagePort";
}
function closeEndPoint(endpoint) {
if (isMessagePort(endpoint))
endpoint.close();
}
function wrap(ep, target) {
return createProxy(ep, [], target);
}
function throwIfProxyReleased(isReleased) {
if (isReleased) {
throw new Error("Proxy has been released and is not useable");
}
}
function createProxy(ep, path = [], target = function () { }) {
let isProxyReleased = false;
const proxy = new Proxy(target, {
get(_target, prop) {
throwIfProxyReleased(isProxyReleased);
if (prop === releaseProxy) {
return () => {
return requestResponseMessage(ep, {
type: "RELEASE" /* RELEASE */,
path: path.map((p) => p.toString()),
}).then(() => {
closeEndPoint(ep);
isProxyReleased = true;
});
};
}
if (prop === "then") {
if (path.length === 0) {
return { then: () => proxy };
}
const r = requestResponseMessage(ep, {
type: "GET" /* GET */,
path: path.map((p) => p.toString()),
}).then(fromWireValue);
return r.then.bind(r);
}
return createProxy(ep, [...path, prop]);
},
set(_target, prop, rawValue) {
throwIfProxyReleased(isProxyReleased);
// FIXME: ES6 Proxy Handler `set` methods are supposed to return a
// boolean. To show good will, we return true asynchronously ¯\_(ツ)_/¯
const [value, transferables] = toWireValue(rawValue);
return requestResponseMessage(ep, {
type: "SET" /* SET */,
path: [...path, prop].map((p) => p.toString()),
value,
}, transferables).then(fromWireValue);
},
apply(_target, _thisArg, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const last = path[path.length - 1];
if (last === createEndpoint) {
return requestResponseMessage(ep, {
type: "ENDPOINT" /* ENDPOINT */,
}).then(fromWireValue);
}
// We just pretend that `bind()` didnt happen.
if (last === "bind") {
return createProxy(ep, path.slice(0, -1));
}
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, {
type: "APPLY" /* APPLY */,
path: path.map((p) => p.toString()),
argumentList,
}, transferables).then(fromWireValue);
},
construct(_target, rawArgumentList) {
throwIfProxyReleased(isProxyReleased);
const [argumentList, transferables] = processArguments(rawArgumentList);
return requestResponseMessage(ep, {
type: "CONSTRUCT" /* CONSTRUCT */,
path: path.map((p) => p.toString()),
argumentList,
}, transferables).then(fromWireValue);
},
});
return proxy;
}
function myFlat(arr) {
return Array.prototype.concat.apply([], arr);
}
function processArguments(argumentList) {
const processed = argumentList.map(toWireValue);
return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
}
const transferCache = new WeakMap();
function transfer(obj, transfers) {
transferCache.set(obj, transfers);
return obj;
}
function proxy(obj) {
return Object.assign(obj, { [proxyMarker]: true });
}
function windowEndpoint(w, context = self, targetOrigin = "*") {
return {
postMessage: (msg, transferables) => w.postMessage(msg, targetOrigin, transferables),
addEventListener: context.addEventListener.bind(context),
removeEventListener: context.removeEventListener.bind(context),
};
}
function toWireValue(value) {
for (const [name, handler] of transferHandlers) {
if (handler.canHandle(value)) {
const [serializedValue, transferables] = handler.serialize(value);
return [
{
type: "HANDLER" /* HANDLER */,
name,
value: serializedValue,
},
transferables,
];
}
}
return [
{
type: "RAW" /* RAW */,
value,
},
transferCache.get(value) || [],
];
}
function fromWireValue(value) {
switch (value.type) {
case "HANDLER" /* HANDLER */:
return transferHandlers.get(value.name).deserialize(value.value);
case "RAW" /* RAW */:
return value.value;
}
}
function requestResponseMessage(ep, msg, transfers) {
return new Promise((resolve) => {
const id = generateUUID();
ep.addEventListener("message", function l(ev) {
if (!ev.data || !ev.data.id || ev.data.id !== id) {
return;
}
ep.removeEventListener("message", l);
resolve(ev.data);
});
if (ep.start) {
ep.start();
}
ep.postMessage(Object.assign({ id }, msg), transfers);
});
}
function generateUUID() {
return new Array(4)
.fill(0)
.map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16))
.join("-");
}
exports.createEndpoint = createEndpoint;
exports.expose = expose;
exports.proxy = proxy;
exports.proxyMarker = proxyMarker;
exports.releaseProxy = releaseProxy;
exports.transfer = transfer;
exports.transferHandlers = transferHandlers;
exports.windowEndpoint = windowEndpoint;
exports.wrap = wrap;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=comlink.js.map

Binary file not shown.

View File

@@ -4,8 +4,9 @@
}
.iconfont {
font-family: "iconfont", serif !important;
font-family: "iconfont" !important;
font-size: 16px;
color: #0078D7;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -804,6 +805,10 @@
content: "\e842";
}
.iconhistory:before {
content: "\e6bb";
}
.iconset:before {
content: "\e623";
}

View File

@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
<path d="M16.118 3.667h.382a3.667 3.667 0 013.667 3.667v7.333a3.667 3.667 0 01-3.667 3.667h-11a3.667 3.667 0 01-3.667-3.667V7.333A3.667 3.667 0 015.5 3.666h.382L4.95 2.053a1.1 1.1 0 011.906-1.1l1.567 2.714h5.156L15.146.953a1.101 1.101 0 011.906 1.1l-.934 1.614z" fill="#333"></path>
<path d="M5.561 5.194h10.878a2.2 2.2 0 012.2 2.2v7.211a2.2 2.2 0 01-2.2 2.2H5.561a2.2 2.2 0 01-2.2-2.2V7.394a2.2 2.2 0 012.2-2.2z" fill="#fff"></path>
<path d="M6.967 8.556a1.1 1.1 0 011.1 1.1v2.689a1.1 1.1 0 11-2.2 0V9.656a1.1 1.1 0 011.1-1.1zM15.033 8.556a1.1 1.1 0 011.1 1.1v2.689a1.1 1.1 0 11-2.2 0V9.656a1.1 1.1 0 011.1-1.1z" fill="#333"></path>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

View File

@@ -1,52 +0,0 @@
<svg width="30px" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#e1e1e1">
<rect y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite"/>
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite"/>
</rect>
<rect x="30" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite"/>
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite"/>
</rect>
<rect x="60" width="15" height="140" rx="6">
<animate attributeName="height"
begin="0s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite"/>
<animate attributeName="y"
begin="0s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite"/>
</rect>
<rect x="90" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite"/>
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite"/>
</rect>
<rect x="120" y="10" width="15" height="120" rx="6">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite"/>
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite"/>
</rect>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 KiB

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="pid-64-svgo-a" d="M0 0h80v80H0z"></path><path d="M52.546 8.014a3.998 3.998 0 014.222 3.077c.104.446.093.808.039 1.138a2.74 2.74 0 01-.312.881c-.073.132-.16.254-.246.376l-.257.366-.521.73c-.7.969-1.415 1.926-2.154 2.866l-.015.02a240.945 240.945 0 015.986.341l1.643.123.822.066.41.034.206.018.103.008.115.012c1.266.116 2.516.45 3.677.975a11.663 11.663 0 013.166 2.114c.931.87 1.719 1.895 2.321 3.022a11.595 11.595 0 011.224 3.613c.03.157.046.316.068.474l.015.119.013.112.022.206.085.822.159 1.646c.1 1.098.19 2.198.27 3.298.315 4.4.463 8.829.36 13.255a166.489 166.489 0 01-.843 13.213c-.012.127-.034.297-.053.454a7.589 7.589 0 01-.072.475l-.04.237-.05.236a11.762 11.762 0 01-.74 2.287 11.755 11.755 0 01-5.118 5.57 11.705 11.705 0 01-3.623 1.263c-.158.024-.316.052-.475.072l-.477.053-.821.071-1.644.134c-1.096.086-2.192.16-3.288.23a260.08 260.08 0 01-6.578.325c-8.772.324-17.546.22-26.313-.302a242.458 242.458 0 01-3.287-.22l-1.643-.129-.822-.069-.41-.035-.206-.018c-.068-.006-.133-.01-.218-.02a11.566 11.566 0 01-3.7-.992 11.732 11.732 0 01-5.497-5.178 11.73 11.73 0 01-1.215-3.627c-.024-.158-.051-.316-.067-.475l-.026-.238-.013-.119-.01-.103-.07-.823-.132-1.648a190.637 190.637 0 01-.22-3.298c-.256-4.399-.358-8.817-.258-13.233.099-4.412.372-8.811.788-13.197a11.65 11.65 0 013.039-6.835 11.585 11.585 0 016.572-3.563c.157-.023.312-.051.47-.07l.47-.05.82-.07 1.643-.13a228.493 228.493 0 016.647-.405l-.041-.05a88.145 88.145 0 01-2.154-2.867l-.52-.73-.258-.366c-.086-.122-.173-.244-.246-.376a2.74 2.74 0 01-.312-.881 2.808 2.808 0 01.04-1.138 3.998 3.998 0 014.22-3.077 2.8 2.8 0 011.093.313c.294.155.538.347.742.568.102.11.19.23.28.35l.27.359.532.72a88.059 88.059 0 012.06 2.936 73.036 73.036 0 011.929 3.03c.187.313.373.628.556.945 2.724-.047 5.447-.056 8.17-.038.748.006 1.496.015 2.244.026.18-.313.364-.624.549-.934a73.281 73.281 0 011.93-3.03 88.737 88.737 0 012.059-2.935l.533-.72.268-.359c.09-.12.179-.24.281-.35a2.8 2.8 0 011.834-.881zM30.13 34.631a4 4 0 00-.418 1.42 91.157 91.157 0 00-.446 9.128c0 2.828.121 5.656.364 8.483l.11 1.212a4 4 0 005.858 3.143c2.82-1.498 5.55-3.033 8.193-4.606a177.41 177.41 0 005.896-3.666l1.434-.942a4 4 0 00.047-6.632 137.703 137.703 0 00-7.377-4.708 146.88 146.88 0 00-6.879-3.849l-1.4-.725a4 4 0 00-5.382 1.742z" id="pid-64-svgo-d"></path><filter x="-15.4%" y="-16.3%" width="130.9%" height="132.5%" filterUnits="objectBoundingBox" id="pid-64-svgo-c"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset><feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix><feOffset in="SourceAlpha" result="shadowOffsetOuter2"></feOffset><feGaussianBlur stdDeviation="3.5" in="shadowOffsetOuter2" result="shadowBlurOuter2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0" in="shadowBlurOuter2" result="shadowMatrixOuter2"></feColorMatrix><feMerge><feMergeNode in="shadowMatrixOuter1"></feMergeNode><feMergeNode in="shadowMatrixOuter2"></feMergeNode></feMerge></filter></defs><g fill="none" fill-rule="evenodd" opacity=".8"><mask id="pid-64-svgo-b" fill="#fff"><use xlink:href="#pid-64-svgo-a"></use></mask><g mask="url(#pid-64-svgo-b)"><use fill="#000" filter="url(#pid-64-svgo-c)" xlink:href="#pid-64-svgo-d"></use><use fill="#FFF" xlink:href="#pid-64-svgo-d"></use></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 B

View File

@@ -2,13 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>阿里云盘小白羊</title>
<title>小白羊云盘</title>
<meta name="data-spm" content="aliyundrive" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel='stylesheet' href='./iconfont.css' />
<link rel="stylesheet" href="iconfont.css" />
<script charset='UTF-8' src='./pinyinlite_full.min.js'></script>
<script charset="UTF-8" src="pinyinlite_full.min.js"></script>
<script src="wasm_exec.js" data-manual></script>
<script>
const go = new window.Go()
@@ -17,7 +17,7 @@
})
</script>
<script type="module" crossorigin src="./index.js"></script>
<link rel='stylesheet' href='./index.css' />
<link rel="stylesheet" href="./style.css" />
</head>
<body class="usertheme">
<div id="app"></div>

View File

@@ -2,16 +2,16 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>阿里云盘小白羊</title>
<title>小白羊云盘</title>
<meta name="data-spm" content="aliyundrive" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel='stylesheet' href='./iconfont.css' />
<script charset='UTF-8' src='./pinyinlite_full.min.js'></script>
<link rel='stylesheet' href='./prism-vsc-dark-plus.css' />
<script src='/prism.js' data-manual></script>
<link rel="stylesheet" href="iconfont.css" />
<script charset="UTF-8" src="pinyinlite_full.min.js"></script>
<link rel="stylesheet" href="prism-vsc-dark-plus.css" />
<script src="prism.js" data-manual></script>
<script type="module" crossorigin src="./index.js"></script>
<link rel='stylesheet' href='./index.css' />
<link rel="stylesheet" href="./style.css" />
</head>
<body class="usertheme">
<div id="app"></div>

BIN
aliyunpan/public/notify.wav Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -10,7 +10,7 @@ import PageVideoXBTVue from './layout/PageVideoXBT.vue'
import PageCode from './layout/PageCode.vue'
import PageOffice from './layout/PageOffice.vue'
import PageImage from './layout/PageImage.vue'
import PageAudio from './layout/PageAudio.vue'
import PageHelp from './layout/PageHelp.vue'
import PageVideo from './layout/PageVideo.vue'
import PageWorker from './layout/PageWorker.vue'
@@ -20,12 +20,12 @@ export default {
return () => {
if (appStore.appPage == 'PageMain') return h(PageMain)
if (appStore.appPage == 'PageHelp') return h(PageHelp)
if (appStore.appPage == 'PageOffice') return h(PageOffice)
if (appStore.appPage == 'PageVideoXBT') return h(PageVideoXBTVue)
if (appStore.appPage == 'PageCode') return h(PageCode)
if (appStore.appPage == 'PageImage') return h(PageImage)
if (appStore.appPage == 'PageVideo') return h(PageVideo)
if (appStore.appPage == 'PageAudio') return h(PageAudio)
if (appStore.appPage == 'PageWorker') return h(PageWorker)
return h(PageLoading)
}

View File

@@ -1,16 +1,8 @@
import {
AliAlbumFileInfo,
IAliAlbumInfo,
IAliAlbumsList,
IAliAlubmCreateInfo,
IAliAlubmListInfo,
IAliGetDirModel
} from './alimodels'
import AliHttp, { IUrlRespData } from './alihttp'
import DebugLog from '../utils/debuglog'
import { HanToPin } from '../utils/utils'
import { GetDriveID } from './utils'
import { useSettingStore, useUserStore } from '../store'
import {AliAlbumFileInfo, IAliAlbumsList, IAliAlubmCreateInfo, IAliAlubmListInfo} from './alimodels'
import AliHttp, {IUrlRespData} from "./alihttp";
import DebugLog from "../utils/debuglog";
import {useSettingStore, useUserStore} from "../store";
import {GetDriveID} from "./utils";
export default class AliAlbum {
@@ -80,7 +72,7 @@ export default class AliAlbum {
static async ApiTotalPhotosNum(): Promise<Number> {
const driver_id = GetDriveID(useUserStore().user_id, 'pic')
const userId = useUserStore().user_id
const url = 'adrive/v2/file/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
if (!driver_id) {
return 0
@@ -103,7 +95,7 @@ export default class AliAlbum {
static async ApiLimitedPhotos(marker="", limited=100): Promise<AliAlbumFileInfo[]> {
const driver_id = GetDriveID(useUserStore().user_id, 'pic')
const userId = useUserStore().user_id
const url = 'adrive/v2/file/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
let max: number = useSettingStore().debugFileListMax
const results:AliAlbumFileInfo[] = []
@@ -140,7 +132,7 @@ export default class AliAlbum {
static async ApiAllPhotos(): Promise<AliAlbumFileInfo[]> {
const driver_id = GetDriveID(useUserStore().user_id, 'pic')
const userId = useUserStore().user_id
const url = 'adrive/v1.0/openFile/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
let marker = '';
let max: number = useSettingStore().debugFileListMax
@@ -211,7 +203,7 @@ export default class AliAlbum {
return results
}
static async ApiAlbumCreate_1(name: string, description: string): Promise<{ album_id: string; error: string }> {
static async ApiAlbumCreate(name: string, description: string): Promise<{ album_id: string; error: string }> {
const userId = useUserStore().user_id
const url = 'adrive/v1/album/create'
const resp = await AliHttp.Post(url, {name, description}, userId, '')
@@ -224,7 +216,7 @@ export default class AliAlbum {
return {album_id: '', error: resp.body?.code || '创建相册出错'}
}
static async ApiAlbumUpdat_1(album_id:string, name: string, description: string): Promise<IUrlRespData> {
static async ApiAlbumUpdate(album_id:string, name: string, description: string): Promise<IUrlRespData> {
const userId = useUserStore().user_id
// { "album_id": "cfe400000000478599575b69356c5a4962383669", "description": "ff", "name": "未命名" }
const url = 'adrive/v1/album/update'
@@ -245,7 +237,7 @@ export default class AliAlbum {
}
static async ApiAlbumDelete_1(album_id:string): Promise<IUrlRespData> {
static async ApiAlbumDelete(album_id:string): Promise<IUrlRespData> {
const userId = useUserStore().user_id
const url = 'adrive/v1/album/delete'
return await AliHttp.Post(url, {album_id}, userId, '')
@@ -294,162 +286,4 @@ export default class AliAlbum {
return false
}
}
/**
* 创建相册
*/
static async ApiAlbumCreate(user_id: string, album_name: string, album_description: string): Promise<string> {
if (!user_id || !album_name) return '创建相册出错'
const url = 'adrive/v1/album/create'
const postData = { name: album_name, description: album_description }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const result = resp.body as IAliAlbumInfo
if (result) return 'success'
else return '创建相册出错'
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumCreate err=' + (resp.code || ''))
}
return '创建相册出错'
}
/**
* 获取相册路径
*/
static async ApiAlbumGetPath(user_id: string, drive_id: string, album_id: string): Promise<IAliGetDirModel[]> {
if (!user_id || !drive_id || !album_id) return []
const url = 'adrive/v1/album/get'
const postData = { album_id: album_id }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const list: IAliGetDirModel[] = []
list.push({
__v_skip: true,
drive_id: drive_id,
file_id: 'mypic',
parent_file_id: '',
name: '我的相册',
namesearch: '',
size: 0,
time: 0,
description: ''
} as IAliGetDirModel)
if (resp.body.name.length > 0) {
list.push({
__v_skip: true,
drive_id: drive_id,
file_id: resp.body.album_id,
album_id: resp.body.album_id,
parent_file_id: 'mypic',
name: resp.body.name,
namesearch: HanToPin(resp.body.name),
size: 0,
time: new Date(resp.body.updated_at).getTime(),
description: resp.body.description || ''
} as IAliGetDirModel)
}
return list
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumGetPath err=' + album_id + ' ' + (resp.code || ''), resp.body)
}
return []
}
/**
* 列出相册
*/
static async ApiAlbumList(user_id: string, limit?: string, order_by?: string, order_direction?: string) {
if (!user_id) return []
const url = 'adrive/v1/album/list'
const postData = {
limit: limit || 20,
order_by: order_by || 'updated_at',
order_direction: order_direction || 'DESC'
}
const data: IAliAlbumInfo[] = []
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const items = resp.body.items
if (items && items.length > 0) {
for (let item of items) {
let coverUrl = ''
if (item.cover && item.cover.list.length > 0) {
coverUrl = item.cover.list[0].download_url || ''
}
data.push({ ...item, coverUrl })
}
}
return data
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumList err=' + (resp.code || ''))
}
return []
}
/**
* 更新相册
*/
static async ApiAlbumUpdate(user_id: string, album_id: string, name: string, description: string): Promise<IUrlRespData | undefined> {
if (!user_id || !album_id) return undefined
const url = 'adrive/v1/album/update'
const postData = { name, album_id, description }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IUrlRespData
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumUpdate err=' + (resp.code || ''))
}
return undefined
}
/**
* 删除相册(不会删除文件)
*/
static async ApiAlbumDelete(user_id: string, album_id: string): Promise<IUrlRespData | undefined> {
if (!user_id || !album_id) return undefined
const url = 'adrive/v1/album/delete'
const resp = await AliHttp.Post(url, { album_id }, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IUrlRespData
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumDelete err=' + (resp.code || ''))
}
return undefined
}
/**
* 添加文件到相册
*/
static async ApiAlbumAddFiles(user_id: string, album_id: string, drive_file_list: {
drive_id: string,
file_id: string
}[]): Promise<IUrlRespData | undefined> {
if (!user_id || !album_id || !drive_file_list) return undefined
const url = 'adrive/v1/album/add_files'
const resp = await AliHttp.Post(url, { album_id, drive_file_list }, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IUrlRespData
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumAddFiles err=' + (resp.code || ''))
}
return undefined
}
/**
* 文件移出相册(不会删除文件)
*/
static async ApiAlbumDeleteFiles(user_id: string, album_id: string, drive_file_list: {
drive_id: string,
file_id: string
}[]): Promise<IUrlRespData | undefined> {
if (!user_id || !album_id || !drive_file_list) return undefined
const url = 'adrive/v1/album/delete_files'
const resp = await AliHttp.Post(url, { album_id, drive_file_list }, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IUrlRespData
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAlbumDeleteFiles err=' + (resp.code || ''))
}
return undefined
}
}

View File

@@ -1,13 +1,11 @@
import { ITokenInfo } from '../user/userstore'
import UserDAL from '../user/userdal'
import { AxiosResponse } from 'axios'
import axios from '../axios'
import axios, { AxiosResponse } from 'axios'
import jschardet from 'jschardet'
import AliUser from './user'
import message from '../utils/message'
import DebugLog from '../utils/debuglog'
import { v4 } from 'uuid'
import DigestClient from "digest-fetch"
export interface IUrlRespData {
code: number
@@ -19,7 +17,7 @@ function BlobToString(body: Blob, encoding: string): Promise<string> {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsText(body, encoding)
reader.onload = function() {
reader.onload = function () {
resolve((reader.result as string) || '')
}
})
@@ -29,7 +27,7 @@ function BlobToBuff(body: Blob): Promise<ArrayBuffer | undefined> {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsArrayBuffer(body)
reader.onload = function() {
reader.onload = function () {
resolve(reader.result as ArrayBuffer)
}
})
@@ -50,8 +48,9 @@ function Sleep(msTime: number): Promise<{ success: true; time: number }> {
const IsDebugHttp = false
export default class AliHttp {
static baseApi = 'https://api.alipan.com/'
static baseOpenApi = 'https://openapi.alipan.com/'
static LimitMax = 100
static baseapi = 'https://api.aliyundrive.com/'
static baseOpenApi = 'https://open.aliyundrive.com/'
static IsSuccess(code: number): Boolean {
return code >= 200 && code <= 300
@@ -61,7 +60,8 @@ export default class AliHttp {
if (code >= 200 && code <= 300) return true
if (code == 400) return true
// if (code == 401) return true
if (code > 402 && code <= 428) return true
if (code >= 402 && code <= 429) return true
if (code == 404) return true
if (code == 409) return true
return false
}
@@ -71,16 +71,15 @@ export default class AliHttp {
if (IsDebugHttp) console.log('CALLURLError ', error)
const errorMessage = error.display_message || error.message || ''
if (error.response) {
let { code, status, config, data = undefined, headers = undefined } = error.response
let { code, status, config, data = undefined, headers = undefined} = error.response
if (code == 'ERR_NETWORK' || (status == 0 && !headers)) {
DebugLog.mSaveWarning('HttpError0 message=' + errorMessage)
return { code: 600, header: '', body: 'NetError 网络无法连接' } as IUrlRespData
}
let isNeedLog = true
if (status == 429 || status == 504 || status == 500) isNeedLog = false
if (status == 429) isNeedLog = false
if (data && data.code) {
let errCode = [
'NotFound.File',
'InvalidParameter.Limit',
'ForbiddenFileInTheRecycleBin',
'PreHashMatched',
@@ -93,28 +92,19 @@ export default class AliHttp {
'UserDeviceIllegality',
'UserDeviceOffline',
'DeviceSessionSignatureInvalid',
'AccessTokenInvalid',
'AccessTokenExpired'
]
if (errCode.includes(data.code)) isNeedLog = false
// 自动刷新Token
if (data.code == 'AccessTokenInvalid' || data.code == 'AccessTokenExpired') {
if (data.code == 'AccessTokenInvalid'
|| data.code == 'AccessTokenExpired') {
if (token) {
const isOpenApi = config.url.includes('adrive/v1.0') || config.url.includes('adrive/v1.1')
if (!isOpenApi) {
return await AliUser.ApiTokenRefreshAccount(token, true, true).then((isLogin: boolean) => {
if (window.IsMainPage) {
return await AliUser.ApiTokenRefreshAccount(token, true).then((isLogin: boolean) => {
if (isLogin) {
return { code: 401, header: '', body: '' } as IUrlRespData
}
return { code: 403, header: '', body: 'NetError 账号需要重新登录' } as IUrlRespData
})
} else {
return await AliUser.OpenApiTokenRefreshAccount(token, true, true).then((flag: boolean) => {
if (flag) {
return { code: 401, header: '', body: '' } as IUrlRespData
}
return { code: 403, header: '', body: 'NetError 账号需要重新登录' } as IUrlRespData
})
}
} else {
return { code: 402, header: '', body: 'NetError 账号需要重新登录' } as IUrlRespData
@@ -126,7 +116,7 @@ export default class AliHttp {
|| data.code == 'UserDeviceOffline'
|| data.code == 'DeviceSessionSignatureInvalid') {
if (token) {
return await AliUser.ApiSessionRefreshAccount(token, true, true).then((flag: boolean) => {
return await AliUser.ApiSessionRefreshAccount(token, true).then((flag: boolean) => {
if (flag) {
return { code: 401, header: '', body: '' } as IUrlRespData
}
@@ -173,16 +163,10 @@ export default class AliHttp {
}
}
static async Get(url: string, user_id: string, params?: any): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) {
if (url.includes('adrive/v1.0') || url.includes('adrive/v1.1')) {
url = AliHttp.baseOpenApi + url
} else {
url = AliHttp.baseApi + url
}
}
static async Get(url: string, user_id: string): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._Get(url, user_id, params)
const resp = await AliHttp._Get(url, user_id)
if (AliHttp.HttpCodeBreak(resp.code)) return resp
else if (i == 5) return resp
else await Sleep(2000)
@@ -190,26 +174,17 @@ export default class AliHttp {
return { code: 607, header: '', body: 'NetError GetLost' }
}
static _Get(url: string, user_id: string, params?: any): Promise<IUrlRespData> {
static _Get(url: string, user_id: string): Promise<IUrlRespData> {
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
const headers: any = {}
if (token) {
let token_type = token.token_type
let access_token = token.access_token
let need_open_api = url.includes('openapi')
if (need_open_api && token.open_api_access_token) {
token_type = token.open_api_token_type || 'Bearer'
access_token = token.open_api_access_token
} else {
headers['Authorization'] = token.token_type + ' ' + token.access_token
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
headers['x-request-id'] = v4().toString()
}
headers['Authorization'] = token_type + ' ' + access_token
}
return axios
.get(url, {
params: params,
withCredentials: false,
responseType: 'json',
timeout: 30000,
@@ -222,7 +197,7 @@ export default class AliHttp {
body: response.data
} as IUrlRespData
})
.catch(function(err: any) {
.catch(function (err: any) {
return AliHttp.CatchError(err, token)
})
})
@@ -230,13 +205,7 @@ export default class AliHttp {
static async GetString(url: string, user_id: string, fileSize: number, maxSize: number): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) {
if (url.includes('adrive/v1.0') || url.includes('adrive/v1.1')) {
url = AliHttp.baseOpenApi + url
} else {
url = AliHttp.baseApi + url
}
}
if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._GetString(url, user_id, fileSize, maxSize)
if (AliHttp.HttpCodeBreak(resp.code)) return resp
@@ -250,21 +219,14 @@ export default class AliHttp {
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
const headers: any = {}
if (token) {
let token_type = token.token_type
let access_token = token.access_token
let need_open_api = url.includes('openapi')
if (need_open_api && token.open_api_access_token) {
token_type = token.open_api_token_type || 'Bearer'
access_token = token.open_api_access_token
} else {
headers['Authorization'] = token.token_type + ' ' + token.access_token
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
headers['x-request-id'] = v4().toString()
}
headers['Authorization'] = token_type + ' ' + access_token
}
if (maxSize > 0) {
headers.Range = 'bytes=0-' + (Math.min(fileSize, maxSize) - 1).toString()
}
return axios
.get(url, {
withCredentials: false,
@@ -318,13 +280,12 @@ export default class AliHttp {
try {
resp.body = JSON.stringify(JSON.parse(resp.body), undefined, 2)
} catch {
}
} catch {}
}
}
return resp
})
.catch(function(err: any) {
.catch(function (err: any) {
return AliHttp.CatchError(err, token)
})
})
@@ -332,13 +293,7 @@ export default class AliHttp {
static async GetBlob(url: string, user_id: string): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) {
if (url.includes('adrive/v1.0') || url.includes('adrive/v1.1')) {
url = AliHttp.baseOpenApi + url
} else {
url = AliHttp.baseApi + url
}
}
if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._GetBlob(url, user_id)
if (AliHttp.HttpCodeBreak(resp.code)) return resp
@@ -352,18 +307,10 @@ export default class AliHttp {
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
const headers: any = {}
if (token) {
let token_type = token.token_type
let access_token = token.access_token
let need_open_api = url.includes('openapi')
if (need_open_api && token.open_api_access_token) {
token_type = token.open_api_token_type || 'Bearer'
access_token = token.open_api_access_token
} else {
headers['Authorization'] = token.token_type + ' ' + token.access_token
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
headers['x-request-id'] = v4().toString()
}
headers['Authorization'] = token_type + ' ' + access_token
}
return axios
.get(url, {
@@ -379,7 +326,7 @@ export default class AliHttp {
body: response.data
} as IUrlRespData
})
.catch(function(err: any) {
.catch(function (err: any) {
return AliHttp.CatchError(err, token)
})
})
@@ -387,136 +334,24 @@ export default class AliHttp {
static async Post(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) {
if (url.includes('adrive/v1.0') || url.includes('adrive/v1.1')) {
url = AliHttp.baseOpenApi + url
} else {
url = AliHttp.baseApi + url
}
url = (url.includes('adrive/v1.0') ? AliHttp.baseOpenApi : AliHttp.baseapi) + url
}
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._Post(url, postData, user_id, share_token)
if (resp.code == 429
&& resp.body.display_message
&& !url.includes('getDownloadUrl')
&& !url.includes('get_download_url')) {
return resp
}
if (resp.code == 400 &&
(url.includes('/file/search')
|| url.includes('/file/list')
|| url.includes('/file/walk')
|| url.includes('/file/scan'))) {
await Sleep(2000)
} else if (AliHttp.HttpCodeBreak(resp.code)) return resp
else if (i == 5) return resp
else await Sleep(2000)
|| url.includes('/file/scan')
|| url.includes('openFile'))
&& !resp.body?.code) await Sleep(1000)
else if (AliHttp.HttpCodeBreak(resp.code)) return resp
else if (i == 3) return resp
else await Sleep(1000)
}
return { code: 608, header: '', body: 'NetError PostLost' } as IUrlRespData
}
private static _Post(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
const headers: any = {}
if (url.includes('aliyundrive') || url.includes('alipan')) {
headers['Content-Type'] = 'application/json'
}
if (token) {
let token_type = token.token_type
let access_token = token.access_token
let need_open_api = url.includes('openapi')
if (need_open_api && token.open_api_access_token) {
token_type = token.open_api_token_type || 'Bearer'
access_token = token.open_api_access_token
} else {
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
headers['x-request-id'] = v4().toString()
}
headers['Authorization'] = token_type + ' ' + access_token
}
if (share_token) {
headers['x-share-token'] = share_token
}
let timeout = 30000
if (url.includes('/batch')) timeout = 60000
return axios
.post(url, postData, {
withCredentials: false,
responseType: 'json',
timeout,
headers
})
.then((response: AxiosResponse) => {
return {
code: response.status,
header: JSON.stringify(response.headers),
body: response.data
} as IUrlRespData
})
.catch(function(err: any) {
return AliHttp.CatchError(err, token)
})
})
}
static async PostString(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) {
if (url.includes('adrive/v1.0') || url.includes('adrive/v1.1')) {
url = AliHttp.baseOpenApi + url
} else {
url = AliHttp.baseApi + url
}
}
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._PostString(url, postData, user_id, share_token)
if (AliHttp.HttpCodeBreak(resp.code)) return resp
else if (i == 5) return resp
else await Sleep(2000)
}
return { code: 610, header: '', body: 'NetError PostStringLost' } as IUrlRespData
}
private static _PostString(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
const headers: any = {}
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
if (token) {
let token_type = token.token_type
let access_token = token.access_token
let need_open_api = url.includes('openapi')
if (need_open_api && token.open_api_access_token) {
token_type = token.open_api_token_type || 'Bearer'
access_token = token.open_api_access_token
} else {
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
headers['x-request-id'] = v4().toString()
}
headers['Authorization'] = token_type + ' ' + access_token
}
if (share_token) {
headers['x-share-token'] = share_token
}
return axios
.post(url, postData, {
withCredentials: false,
responseType: 'text',
timeout: 50000,
headers
})
.then((response: AxiosResponse) => {
return {
code: response.status,
header: JSON.stringify(response.headers),
body: response.data
} as IUrlRespData
})
.catch(function(err: any) {
return AliHttp.CatchError(err, token)
})
})
}
static async GetWithOutUserId(url: string): Promise<IUrlRespData> {
return axios
.get(url)
@@ -553,4 +388,91 @@ export default class AliHttp {
return AliHttp.CatchError(err, undefined)
})
}
static _Post(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
const headers: any = {}
if (url.includes('aliyundrive')) {
headers['Content-Type'] = 'application/json'
}
if (token && (url.startsWith(this.baseOpenApi)
|| url.startsWith('https://openapi.aliyundrive.com'))) {
headers['Authorization'] = token.token_type + ' ' + token.access_token_v2
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
} else if (token) {
headers['Authorization'] = token.token_type + ' ' + token.access_token
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
}
if (share_token) {
headers['x-share-token'] = share_token
}
let timeout = 30000
if (url.includes('/batch')) timeout = 60000
return axios
.post(url, postData, {
withCredentials: false,
responseType: 'json',
timeout,
headers
})
.then((response: AxiosResponse) => {
return {
code: response.status,
header: JSON.stringify(response.headers),
body: response.data
} as IUrlRespData
})
.catch(function (err: any) {
return AliHttp.CatchError(err, token)
})
})
}
static async PostString(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url
for (let i = 0; i <= 5; i++) {
const resp = await AliHttp._PostString(url, postData, user_id, share_token)
if (AliHttp.HttpCodeBreak(resp.code)) return resp
else if (i == 5) return resp
else await Sleep(2000)
}
return { code: 610, header: '', body: 'NetError PostStringLost' } as IUrlRespData
}
private static _PostString(url: string, postData: any, user_id: string, share_token: string): Promise<IUrlRespData> {
const headers: any = {}
return UserDAL.GetUserTokenFromDB(user_id).then((token) => {
if (token) {
headers['Authorization'] = token.token_type + ' ' + token.access_token
headers['x-request-id'] = v4().toString()
headers['x-device-id'] = token.device_id
headers['x-signature'] = token.signature
}
if (share_token) {
headers['x-share-token'] = share_token
}
return axios
.post(url, postData, {
withCredentials: false,
responseType: 'text',
timeout: 50000,
headers
})
.then((response: AxiosResponse) => {
return {
code: response.status,
header: JSON.stringify(response.headers),
body: response.data
} as IUrlRespData
})
.catch(function (err: any) {
return AliHttp.CatchError(err, token)
})
})
}
}

View File

@@ -5,7 +5,6 @@ export interface IAliFileVideoMeta {
duration?: string
fps?: string
}
export interface IAliFileAudioMeta {
bit_rate?: string
channel_layout?: string
@@ -15,372 +14,6 @@ export interface IAliFileAudioMeta {
sample_rate?: string
}
export interface IAliAlbumInfo {
owner: string
name: string
description: string
coverUrl: string
album_id: string
file_count: number
image_count: number
video_count: number
created_at: number
updated_at: number,
}
export interface IAliFileItem {
drive_id: string
domain_id: string
description?: string
file_id: string
album_id?: string
compilation_id?: string
name: string
type: string
video_type?: string
content_type: string
created_at: string
updated_at: string
last_played_at?: string
gmt_cleaned?: string
gmt_deleted?: string
file_extension?: string
hidden: boolean
file_count?: number
image_count?: number
video_count?: number
size: number
starred: boolean
status: string
upload_id: string
parent_file_id: string
crc64_hash: string
content_hash: string
content_hash_name: string
download_url: string
url: string
category: string
encrypt_mode: string
punish_flag: number
from_share_id?: string
thumbnail?: string
mime_extension: string
mime_type: string
play_cursor: string
duration: string
video_media_metadata?: {
duration?: string | number
height?: number
width?: number
time?: string
video_media_video_stream?: IAliFileVideoMeta[] | IAliFileVideoMeta
video_media_audio_stream?: IAliFileAudioMeta[] | IAliFileAudioMeta
}
video_preview_metadata?: {
duration?: string | number
height?: number
width?: number
time?: string
audio_format?: string
bitrate?: string
frame_rate?: string
video_format?: string
template_list?: [{ template_id: string; status: string }]
audio_template_list?: [{ template_id: string; status: string }]
}
image_media_metadata?: {
height?: number
width?: number
time?: string
exif?: string
}
user_meta?: string
}
export interface IAliOtherFollowingModel {
avatar: string
description: string
is_following: boolean
nick_name: string
phone: string
user_id: string
follower_count: number
}
interface IAliMyFollowingMessageModel {
action: string
content: {
file_id_list: string[]
share: { popularity: number; popularity_emoji: string; popularity_str: string; share_id: string; share_pwd: string }
}
created: number
createdstr: string
creator: IAliOtherFollowingModel
creator_id: string
display_action: string
sequence_id: number
}
export interface IAliMyFollowingModel {
avatar: string
description: string
has_unread_message: boolean
is_following: boolean
latest_messages: IAliMyFollowingMessageModel[]
nick_name: string
phone: string
user_id: string
SearchName: string
}
export interface IAliShareBottleFish {
bottleId: string;
bottleName: string;
shareId: string;
}
export interface IAliShareItem {
created_at: string
creator: string
description: string
display_name: string
display_label: string
download_count: number
drive_id: string
expiration: string
expired: boolean
file_id: string
file_id_list: string[]
icon: string
first_file?: IAliFileItem
preview_count: number
save_count: number
share_id: string
share_msg: string
full_share_msg: string
share_name: string
share_policy: string
share_pwd: string
share_url: string
status: string
updated_at: string
is_share_saved: boolean
share_saved: string
}
export interface IAliShareRecentItem {
popularity: number;
browse_count: number;
share_id: string;
share_msg: string;
share_name: string;
share_url: string;
creator: string;
file_id_list: string[];
preview_count: number;
save_count: number;
status: string;
share_subtitle: string;
gmt_created: string;
gmt_modified: string;
is_public: boolean;
file_list: {
name: string;
type: string;
category: string;
parent_file_id: string;
drive_id: string;
file_id: string;
created_at: string;
updated_at: string;
trashed_at: string | null;
}[];
creator_name: string;
creator_uid: string;
file_count: number;
is_punished: boolean;
share_creator: {
userId: string;
avatar: string;
displayName: string;
};
popularity_str: string;
popularity_emoji: string;
full_share_msg: string;
share_title: string;
display_name: string;
}
export interface IAliShareBottleFishItem {
bottleId: string;
gmtCreate: number;
id: number;
name: string;
saved: boolean;
shareId: string;
gmt_created: string;
saved_msg: string;
share_name: string;
display_name: string;
}
export interface IAliShareAnonymous {
shareinfo: {
share_id: string
creator_id: string
creator_name: string
creator_phone: string
display_name: string
expiration: string
file_count: number
share_name: string
created_at: string
updated_at: string
vip: string
is_photo_collection: boolean
album_id: string
}
shareinfojson: string
error: string
}
export interface IAliShareFileItem {
drive_id: string
// domain_id: string
file_id: string
name: string
type: string
created_at: string
updated_at: string
// hidden: boolean
// starred: boolean
// status: string
parent_file_id: string
// encrypt_mode: string
// revision_id: string
file_extension?: string
mime_extension: string
mime_type: string
size: number
// content_hash: string
// content_hash_name: string
category: string
punish_flag: number
isDir: boolean
sizeStr: string
timeStr: string
icon: string
}
export interface IAliGetForderSizeModel {
size: number
folder_count: number
file_count: number
reach_limit?: boolean
}
export interface IAliGetDirModel {
__v_skip: true
drive_id: string
file_id: string
album_id?: string
album_type?: string
parent_file_id: string
name: string
namesearch: string
size: number
time: number
punish_flag?: number
description: string
}
export interface IAliGetFileModel {
__v_skip: true
drive_id: string
file_id: string
parent_file_id: string
name: string
namesearch: string
ext: string
mime_type: string
mime_extension: string
category: string
icon: string
file_count?: number
size: number
sizeStr: string
time: number
timeStr: string
starred: boolean
isDir: boolean
thumbnail: string
punish_flag?: number
from_share_id?: string
description: string
album_id?: string
compilation_id?: string
download_url?: string
media_width?: number
media_height?: number
media_duration?: string
media_play_cursor?: string
media_time?: string
user_meta?: string
duration?: number
play_cursor?: number
season_poster?:string
episode_name?:string
episode_poster?:string
season_num?:number
episode_num?:number
// tvSeason?:{
// "air_date": string,
// "episode_count": number,
// "id": number,
// "name": string,
// "overview": string,
// "poster_path": string,
// "season_number": number,
// "vote_average": number
// }[]
minfo?: {
cached?: boolean
adult: boolean
backdrop_path: string
id: number
title: string
name: string
original_language: string
original_title: string
origin_country?: string[]
overview: string
poster_path: string
media_type: string
genre_ids: number[]
popularity: string
release_date?: string
video?: boolean
first_air_date?: string
vote_average: number
vote_count: number
}
m3u8_total_file_nums?:number
m3u8_parent_file_name?:string
}
export interface AliAlbumFileInfo {
album_name?:string
@@ -430,7 +63,6 @@ export interface AliAlbumFileInfo {
"next_marker"?: string
}
// [{"name": "cutecy", "friendly_name": "\u53ef\u53ef\u7231\u7231\n", "preview": ".DS_Store"}]
export interface IAliAlbumsList {
name:string
@@ -465,3 +97,251 @@ export interface IAliAlubmCreateInfo {
"created_at": number,
"updated_at": number,
}
export interface IAliFileItem {
trashed?: boolean
drive_id: string
domain_id: string
description?: string
file_id: string
compilation_id?: string
name: string
type: string
video_type?: string
content_type: string
created_at: string
updated_at: string
last_played_at?: string
gmt_cleaned?: string
gmt_deleted?: string
file_extension?: string
hidden: boolean
user_tags?: {
video_watch_progress?: string | number
}
labels?:[string]
size: number
starred: boolean
status: string
upload_id: string
parent_file_id: string
crc64_hash: string
content_hash: string
content_hash_name: string
download_url: string
url: string
category: string
encrypt_mode: string
punish_flag: number
thumbnail?: string
mime_extension: string
mime_type: string
play_cursor?: number
video_media_metadata?: {
duration?: string | number
height?: number
width?: number
time?: string
video_media_video_stream?: IAliFileVideoMeta[] | IAliFileVideoMeta
video_media_audio_stream?: IAliFileAudioMeta[] | IAliFileAudioMeta
}
video_preview_metadata?: {
duration?: string | number
height?: number
width?: number
time?: string
audio_format?: string
bitrate?: string
frame_rate?: string
video_format?: string
template_list?: [{ template_id: string; status: string }]
audio_template_list?: [{ template_id: string; status: string }]
}
image_media_metadata?: {
height?: number
width?: number
time?: string
exif?: string
}
cover_type?: string
cover_url?: string
grand_parent_file_id?: string
user_meta?: string
meta?: string
location?: string
deleted?: boolean
channel?: string
revision_id?: string
local_created_at?: string
local_modified_at?: string
trashed_at?: string
}
export interface IAliOtherFollowingModel {
avatar: string
description: string
is_following: boolean
nick_name: string
phone: string
user_id: string
follower_count: number
}
interface IAliMyFollowingMessageModel {
action: string
content: {
file_id_list: string[]
share: { popularity: number; popularity_emoji: string; popularity_str: string; share_id: string; share_pwd: string }
}
created: number
createdstr: string
creator: IAliOtherFollowingModel
creator_id: string
display_action: string
sequence_id: number
}
export interface IAliMyFollowingModel {
avatar: string
description: string
has_unread_message: boolean
is_following: boolean
latest_messages: IAliMyFollowingMessageModel[]
nick_name: string
phone: string
user_id: string
SearchName: string
}
export interface IAliShareItem {
created_at: string
creator: string
description: string
display_name: string
download_count: number
drive_id: string
expiration: string
expired: boolean
file_id: string
file_id_list: string[]
icon: string
first_file?: IAliFileItem
preview_count: number
save_count: number
share_id: string
share_msg: string
share_name: string
share_policy: string
share_pwd: string
share_url: string
status: string
updated_at: string
}
export interface IAliShareAnonymous {
shareinfo: {
share_id: string
creator_id: string
creator_name: string
creator_phone: string
display_name: string
expiration: string
file_count: number
share_name: string
created_at: string
updated_at: string
vip: string
is_photo_collection: boolean
album_id: string
}
shareinfojson: string
error: string
}
export interface IAliShareFileItem {
drive_id: string
// domain_id: string
file_id: string
name: string
type: string
// created_at: string
// updated_at: string
// hidden: boolean
// starred: boolean
// status: string
parent_file_id: string
// encrypt_mode: string
// revision_id: string
file_extension?: string
mime_extension: string
mime_type: string
size: number
// content_hash: string
// content_hash_name: string
category: string
punish_flag: number
isDir: boolean
sizeStr: string
icon: string
}
export interface IAliGetForderSizeModel {
size: number
folder_count: number
file_count: number
reach_limit?: boolean
}
export interface IAliGetDirModel {
__v_skip: true
drive_id: string
file_id: string
parent_file_id: string
name: string
namesearch: string
size: number
time: number
description: string
}
export interface IAliGetFileModel {
__v_skip: true
drive_id: string
file_id: string
parent_file_id: string
name: string
namesearch: string
ext: string
category: string
icon: string
size: number
sizeStr: string
time: number
timeStr: string
starred: boolean
isDir: boolean
thumbnail: string
description: string
compilation_id?: string
download_url?: string
media_width?: number
media_height?: number
media_duration?: string
media_time?: string
m3u8_total_file_nums?:number
m3u8_parent_file_name?:string
}

View File

@@ -139,7 +139,6 @@ export default class AliArchive {
static async ApiArchiveUncompress(user_id: string, drive_id: string, file_id: string, domain_id: string, archive_type: string, target_drive_id: string, target_file_id: string, password: string, file_list: string[]): Promise<IArchiveData | undefined> {
if (!user_id || !drive_id || !file_id || !target_drive_id || !target_file_id) return undefined
if (target_file_id.includes('root')) target_file_id = 'root'
const url = 'v2/archive/uncompress'
const postData: {
drive_id: string

View File

@@ -1,4 +1,4 @@
import { usePanFileStore, useServerStore, useSettingStore } from '../store'
import { usePanFileStore, useSettingStore } from '../store'
import TreeStore from '../store/treestore'
import DebugLog from '../utils/debuglog'
import { OrderDir, OrderFile } from '../utils/filenameorder'
@@ -8,10 +8,6 @@ import { HanToPin, MapValueToArray } from '../utils/utils'
import AliHttp, { IUrlRespData } from './alihttp'
import { IAliFileItem, IAliGetFileModel } from './alimodels'
import getFileIcon from './fileicon'
import { DecodeEncName, GetDriveID } from './utils'
import DB from '../utils/db'
import Config from '../config'
export interface IAliFileResp {
items: IAliGetFileModel[]
@@ -23,7 +19,6 @@ export interface IAliFileResp {
m_user_id: string
m_drive_id: string
dirID: string
albumID?: string
dirName: string
itemsTotal?: number
}
@@ -44,96 +39,94 @@ export function NewIAliFileResp(user_id: string, drive_id: string, dirID: string
}
export default class AliDirFileList {
static ItemJsonmask = 'category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription%2Cfrom_share_id'
static getFileInfo(user_id: string, item: IAliFileItem, downUrl: string): IAliGetFileModel {
static LimitMax = 100
static ItemJsonmask = 'category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription'
static getFileInfo(item: IAliFileItem, downUrl: string): IAliGetFileModel {
const size = item.size ? item.size : 0
const file_count = item.file_count || item.image_count || item.video_count || 0
const time = new Date(item.updated_at || item.created_at || item.gmt_deleted || item.last_played_at || '')
const timeStr = humanDateTimeDateStr(item.updated_at || item.created_at || item.gmt_deleted || item.last_played_at || '')
const date = new Date(item.updated_at || item.gmt_deleted || item.last_played_at || '')
const y = date.getFullYear().toString()
let m: number | string = date.getMonth() + 1
m = m < 10 ? '0' + m.toString() : m.toString()
let d: number | string = date.getDate()
d = d < 10 ? '0' + d.toString() : d.toString()
let h: number | string = date.getHours()
h = h < 10 ? '0' + h.toString() : h.toString()
let minute: number | string = date.getMinutes()
minute = minute < 10 ? '0' + minute.toString() : minute.toString()
let second: number | string = date.getSeconds()
second = second < 10 ? '0' + second.toString() : second.toString()
const isDir = item.type == 'folder'
const { name, mine_type, ext } = DecodeEncName(user_id, item)
const add: IAliGetFileModel = {
__v_skip: true,
drive_id: item.drive_id,
file_id: item.file_id,
parent_file_id: item.parent_file_id || '',
name: name,
namesearch: HanToPin(name),
ext: ext || '',
mime_type: mine_type || '',
mime_extension: item.mime_extension,
name: item.name,
namesearch: HanToPin(item.name),
ext: item.file_extension?.toLowerCase() || '',
category: item.category || '',
starred: item.starred || false,
time: time.getTime(),
file_count: file_count,
time: date.getTime() ,
size: size,
sizeStr: humanSize(size),
timeStr: timeStr,
timeStr: y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second ,
icon: 'iconfile-folder',
isDir: isDir,
thumbnail: '',
from_share_id: item.from_share_id,
punish_flag: item.punish_flag,
description: item.description || '',
user_meta: item.user_meta || ''
description: item.description || ''
}
if (!isDir) {
const icon = getFileIcon(add.category, add.ext, add.ext || item.mime_extension, add.mime_type, add.size)
const icon = getFileIcon(add.category, add.ext, item.mime_extension, item.mime_type, add.size)
add.category = icon[0]
add.icon = icon[1]
if (downUrl) {
if (downUrl == 'download_url') {
add.download_url = item.download_url || ''
} else if (add.category == 'image') {
add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&image_thumbnail_process=image%2Fresize%2Cl_260%2Fformat%2Cjpg%2Fauto-orient%2C1'
add.thumbnail = item?.thumbnail || ''
// add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&image_thumbnail_process=image%2Fresize%2Cl_260%2Fformat%2Cjpg%2Fauto-orient%2C1'
} else if (add.category == 'image2') {
add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id
add.thumbnail = item?.thumbnail || ''
// add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id
} else if (add.category.startsWith('video')) {
add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&video_thumbnail_process=video%2Fsnapshot%2Ct_106000%2Cf_jpg%2Car_auto%2Cm_fast'
add.thumbnail = item?.thumbnail || ''
// add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&video_thumbnail_process=video%2Fsnapshot%2Ct_106000%2Cf_jpg%2Car_auto%2Cw_260%2Cm_fast'
} else if (add.category == 'doc' || add.category == 'doc2') {
if (add.ext != 'txt' && add.ext != 'epub' && add.ext != 'azw' && add.ext != 'azw3') {
add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&office_thumbnail_process=image%2Fresize%2Cl_260%2Fformat%2Cjpg%2Fauto-orient%2C1'
}
add.thumbnail = item?.thumbnail || ''
// if (add.ext != 'txt' && add.ext != 'epub' && add.ext != 'azw' && add.ext != 'azw3') {
// add.thumbnail = downUrl + '&drive_id=' + add.drive_id + '&file_id=' + add.file_id + '&office_thumbnail_process=image%2Fresize%2Cl_260%2Fformat%2Cjpg%2Fauto-orient%2C1'
// }
}
}
if (item.video_media_metadata && Object.keys(item.video_media_metadata).length > 0) {
if (item.video_media_metadata) {
add.media_width = item.video_media_metadata.width || 0
add.media_height = item.video_media_metadata.height || 0
add.media_time = humanDateTimeDateStr(item.video_media_metadata.time)
add.media_duration = humanTime(item.video_media_metadata.duration)
} else if (item.video_preview_metadata && Object.keys(item.video_preview_metadata).length > 0) {
} else if (item.video_preview_metadata) {
add.media_width = item.video_preview_metadata.width || 0
add.media_height = item.video_preview_metadata.height || 0
add.media_time = humanDateTimeDateStr(item.video_preview_metadata.time)
add.media_duration = humanTime(item.video_preview_metadata.duration)
} else if (item.image_media_metadata && Object.keys(item.image_media_metadata).length > 0) {
} else if (item.image_media_metadata) {
add.media_width = item.image_media_metadata.width || 0
add.media_height = item.image_media_metadata.height || 0
add.media_time = humanDateTimeDateStr(item.image_media_metadata.time)
}
if (item.play_cursor) {
add.media_play_cursor = humanTime(item.play_cursor)
} else if (item.user_meta) {
const meta = JSON.parse(item.user_meta)
if (meta.play_cursor) {
add.media_play_cursor = humanTime(meta.play_cursor)
}
}
if (!add.media_duration && item.duration) {
add.media_duration = humanTime(item.duration)
}
}
// 完全违规和部分违规
if (item.punish_flag == 2) add.icon = 'iconweifa'
else if (item.punish_flag == 103) add.icon = 'iconpartweifa'
else if (item.punish_flag > 0) add.icon = 'iconweixiang'
return add
}
static async ApiDirFileList(user_id: string, drive_id: string, dirID: string, dirName: string, order: string, type: string = '', albumID?: string, refresh: boolean = true, openApi = false, search = true): Promise<IAliFileResp> {
static async ApiDirFileList(user_id: string, drive_id: string, dirID: string, dirName: string, order: string, type: string = ''): Promise<IAliFileResp> {
const dir: IAliFileResp = {
items: [],
itemsKey: new Set(),
@@ -142,24 +135,23 @@ export default class AliDirFileList {
m_user_id: user_id,
m_drive_id: drive_id,
dirID: dirID,
dirName: dirName,
albumID: albumID
dirName: dirName
}
if (!user_id || !drive_id || !dirID) return dir
if (!order) order = 'updated_at asc'
if (dirID.includes('video')) order = 'updated_at desc'
order = order.replace(' desc', ' DESC').replace(' asc', ' ASC')
const orders = order.split(' ')
let pageIndex = 0
// if (dirID == 'video') {
// await AliDirFileList._ApiVideoListRecent(orders[0], orders[1], dir, pageIndex)
// pageIndex++
// }
let max: number = useSettingStore().debugFileListMax
if (dirID == 'favorite' || dirID.startsWith('color')
|| dirID.startsWith('search') || dirID == 'trash'
|| dirID.startsWith('video') || dirID == 'recover') {
max = useSettingStore().debugFavorListMax
}
if (dirID == 'favorite' || dirID.startsWith('color') || dirID.startsWith('search') || dirID.startsWith('video')) max = useSettingStore().debugFavorListMax
let needTotal
do {
@@ -173,7 +165,8 @@ export default class AliDirFileList {
isGet = await AliDirFileList._ApiFavorFileListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID == 'trash') {
isGet = await AliDirFileList._ApiTrashFileListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID == 'recover') {
}
else if (dirID == 'recover') {
isGet = await AliDirFileList._ApiDeleteedFileListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID.startsWith('color')) {
if (!needTotal) {
@@ -189,28 +182,21 @@ export default class AliDirFileList {
})
}
isGet = await AliDirFileList._ApiSearchFileListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID == 'video') {
isGet = await AliDirFileList._ApiVideoListRecent(orders[0], orders[1], dir, pageIndex)
} else if (dirID === 'video') {
isGet = await AliDirFileList._ApiVideoListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID === 'video.recentplay') {
isGet = await AliDirFileList._ApiVideoListRecent(orders[0], orders[1], dir, pageIndex)
} else if (dirID === 'video.compilation') {
isGet = await AliDirFileList._ApiVideoListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (albumID && albumID.length > 0) {
isGet = await AliDirFileList._ApiAlbumListFilesOnePage(orders[0], orders[1], dir, pageIndex)
dir.itemsTotal = dir.items.length
} else if (dirID === 'mypic') {
isGet = await AliDirFileList._ApiAlbumListOnePage(orders[0], orders[1], dir, pageIndex)
} else if (dirID.startsWith('video')) {
isGet = await AliDirFileList._ApiVideoFileListOnePage(orders[0], orders[1], dir, pageIndex)
} else {
if (!needTotal) {
needTotal = AliDirFileList._ApiDirFileListCount(dir, dirID, type).then((total) => {
needTotal = AliDirFileList._ApiDirFileListCount(dir, type).then((total) => {
dir.itemsTotal = total
})
}
if (openApi || useSettingStore().uiFileListMode === 'movie') {
isGet = await AliDirFileList._ApiDirFileListOnePageOpenApi(orders[0], orders[1], dir, type, pageIndex, search)
} else {
isGet = await AliDirFileList._ApiDirFileListOnePage(orders[0], orders[1], dir, type, pageIndex)
}
isGet = await AliDirFileList._ApiDirFileListOnePageOpenApi(orders[0], orders[1], dir, type, pageIndex)
}
if (!isGet) {
@@ -231,207 +217,20 @@ export default class AliDirFileList {
pageIndex++
} while (dir.next_marker)
for (const fileItem of dir.items) {
const videoInfo = await DB.getVideoInfo(fileItem.file_id)
if (videoInfo) {
fileItem.duration = videoInfo.duration
fileItem.play_cursor = videoInfo.play_cursor
}
}
if (needTotal) await needTotal
if (useSettingStore().uiFileListMode === 'movie' && search) {
await this.searchTMDB(dir)
}
return dir
}
static seasonNumber(name: string): string {
const match = name.match(/(?:[sS](\d+)[ -]?[eE](\d+))|(?:[sS](\d+))|(?:[sS][eE](\d+))/)
if (match) {
return match[1] || match[3] || match[4] || "1";
} else {
const additionalMatch = name.match(/\s*(\d+)\s*第\s*\d+\s*集/);
if (additionalMatch) {
return additionalMatch[1] || "1";
}
}
return "1";
}
static episodeNumber(name: string) :string {
let match = name.match(/(?:[sS](\d+)[ -]?[eE](\d+))|(?:[sS](\d+))/)
if (match && match[2]) {
return match[2]
} else {
match = name.match(/[-\s]([0-9]+)[\s.\]]/)
if (match) {
return match[1] || "-1"
} else {
match = name.match(/第\s*(\d+)\s*集/)
if (match) {
return match[1] || "-1";
} else {
match = name.match(/(?:[sS][eE]?(\d+)[\.-](\d+))/)
if (match && match[2]) {
return match[2];
}
}
}
return "-1"
}
}
static async searchTMDB(dir: IAliFileResp): Promise<void> {
const regex = /(?:s|S)(\d+)(?:(?:e|E)(\d+))?|(\d{4})|(\d{3,4}p)|([sS][eE]\d+)|(\d{4})|(\d{3,4}p)|([sS][eE]\d+)|(\d+)\s*第\s*(\d+)集/g;
for (const fileItem of dir.items) {
// const videoInfo = await DB.getVideoInfo(fileItem.file_id)
// if (videoInfo) {
// fileItem.duration = videoInfo.duration
// fileItem.play_cursor = videoInfo.play_cursor
// }
const tmdb_cache = await DB.getValueString(fileItem.file_id)
if (tmdb_cache) {
const media_type = tmdb_cache.split(",")[0]
const tmdb_id = tmdb_cache.split(",")[1]
if (media_type === 'tv') {
const cacheData = await DB.getValueString(fileItem.file_id + "movieinfo")
if (cacheData) {
// @ts-ignore
fileItem.minfo = cacheData
if (fileItem.minfo) {
fileItem.minfo.cached = true
fileItem.minfo.media_type = 'tv'
}
} else {
const urlTv = `${Config.tmdbProxyUrl}/3/tv/${tmdb_id}?api_key=87d8eb3d0895eaf37c2929fd5d9f7cce&language=zh-CN`
const resp = await AliHttp.GetWithOutUserId(urlTv)
if (AliHttp.IsSuccess(resp.code)) {
fileItem.minfo = resp.body
await DB.saveValueString(fileItem.file_id+"movieinfo", resp.body)
if (fileItem.minfo) {
fileItem.minfo.cached = true
fileItem.minfo.media_type = 'tv'
}
}
}
} else if (media_type === 'movie') {
const cacheData = await DB.getValueString(fileItem.file_id+"movieinfo")
if (cacheData) {
// @ts-ignore
fileItem.minfo = cacheData
if (fileItem.minfo) {
fileItem.minfo.cached = true
fileItem.minfo.media_type = 'movie'
}
} else {
const url = `${Config.tmdbProxyUrl}/3/movie/${tmdb_id}?api_key=87d8eb3d0895eaf37c2929fd5d9f7cce&language=zh-CN`
const resp = await AliHttp.GetWithOutUserId(url)
if (AliHttp.IsSuccess(resp.code)) {
fileItem.minfo = resp.body
await DB.saveValueString(fileItem.file_id+"movieinfo", resp.body)
if (fileItem.minfo) {
fileItem.minfo.cached = true
fileItem.minfo.media_type = 'movie'
}
}
}
}
}
if (!fileItem.minfo) {
const cacheData = await DB.getValueString(fileItem.file_id+"movieinfo")
if (cacheData) {
// @ts-ignore
fileItem.minfo = cacheData
} else {
const parts = fileItem.name.split(regex)
const searchName = parts[0].replaceAll('.', ' ').replace(/\[.*?\]/g, '').replaceAll('&', ' ').trimEnd()
if (searchName.toLowerCase() == '4k'
|| searchName == '字幕' || searchName == 'test'
|| searchName.toLowerCase().includes('season')) continue
const url = `${Config.tmdbProxyUrl}/3/search/multi?api_key=87d8eb3d0895eaf37c2929fd5d9f7cce&language=zh-CN&query=${searchName}&page=1&include_adult=false`
const resp = await AliHttp.GetWithOutUserId(url);
if (AliHttp.IsSuccess(resp.code) && resp.body.results && resp.body.results.length > 0) {
fileItem.minfo = resp.body.results[0]
if (fileItem.minfo) {
await DB.saveValueString(fileItem.file_id+"movieinfo", resp.body.results[0])
}
}
}
}
if (fileItem.minfo && fileItem.minfo.media_type === 'tv') {
const seasonNum = this.seasonNumber(fileItem.name)
const episodeNum = this.episodeNumber(fileItem.name)
fileItem.season_num = Number(seasonNum)
fileItem.episode_num = Number(episodeNum)
// if (Number(seasonNum) != -1) {
// if (Number(episodeNum) != -1) {
// const season_poster = await DB.getValueString(fileItem.file_id+"season_poster")
// const episode_name = await DB.getValueString(fileItem.file_id+"episode_name")
// if (season_poster && episode_name) {
// fileItem.season_poster = season_poster
// fileItem.episode_name = episode_name
// } else {
// const urlSeason = `${Config.tmdbProxyUrl}/3/tv/${fileItem.minfo.id}/season/${fileItem.season_num}?api_key=87d8eb3d0895eaf37c2929fd5d9f7cce&language=zh-CN`
// const resp = await AliHttp.GetWithOutUserId(urlSeason)
// if (AliHttp.IsSuccess(resp.code)) {
// fileItem.season_poster = resp.body.poster_path
// // fileItem.season_poster = resp.body.episodes.filter(episode => episode.episode_number === fileItem.episode_num)[0].still_path
// // @ts-ignore
// const episodeRes = resp.body.episodes.filter(episode => episode.episode_number === fileItem.episode_num)
// if (episodeRes.length > 0) {
// fileItem.episode_name = episodeRes[0].name
// }
// if ( fileItem.season_poster) {
// await DB.saveValueString(fileItem.file_id+"season_poster", fileItem.season_poster)
// }
// if (fileItem.episode_name) {
// await DB.saveValueString(fileItem.file_id+"episode_name", fileItem.episode_name)
// }
// }
// }
// } else {
// const season_poster = await DB.getValueString(fileItem.file_id+"season_poster")
// if (season_poster) {
// fileItem.season_poster = season_poster
// } else {
// const tvApi = `${Config.tmdbProxyUrl}/3/tv/${fileItem.minfo.id}?api_key=87d8eb3d0895eaf37c2929fd5d9f7cce&language=zh-CN`
// const tvResp = await AliHttp.GetWithOutUserId(tvApi)
// if (AliHttp.IsSuccess(tvResp.code) && tvResp.body.seasons && tvResp.body.seasons.length > 0) {
// // fileItem.tvSeason = tvResp.body.seasons
// // @ts-ignore
// const filteredSeasons = tvResp.body.seasons.filter(season => season.season_number === Number(seasonNum))
// if (filteredSeasons && filteredSeasons.length > 0) {
// fileItem.season_poster = filteredSeasons[0].poster_path
// if ( fileItem.season_poster) {
// await DB.saveValueString(fileItem.file_id+"season_poster", fileItem.season_poster)
// }
// }
// }
// }
// }
// }
}
}
}
private static async _ApiDirFileListOnePage(orderby: string, order: string, dir: IAliFileResp, type: string, pageIndex: number): Promise<boolean> {
let url = 'adrive/v3/file/list'
if (useSettingStore().uiShowPanMedia == false) {
url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + ')'
} else {
url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + '%2Cuser_meta%2Cvideo_media_metadata(duration%2Cwidth%2Cheight%2Ctime)%2Cvideo_preview_metadata%2Fduration%2Cimage_media_metadata)'
}
let postData: any = {
if (useSettingStore().uiShowPanMedia == false) url += '?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(' + AliDirFileList.ItemJsonmask + ')'
else url += '?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(' + AliDirFileList.ItemJsonmask + '%2Cvideo_media_metadata(duration%2Cwidth%2Cheight%2Ctime)%2Cvideo_preview_metadata%2Fduration%2Cimage_media_metadata)'
let postData = {
drive_id: dir.m_drive_id,
parent_file_id: dir.dirID.includes('root') ? 'root' : dir.dirID,
parent_file_id: dir.dirID,
marker: dir.next_marker,
limit: 100,
limit: 200,
all: false,
url_expire_sec: 14400,
fields: '*',
@@ -446,21 +245,9 @@ export default class AliDirFileList {
return await AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex, type)
}
private static async _ApiDirFileListOnePageOpenApi(orderby: string, order: string, dir: IAliFileResp, type: string, pageIndex: number, search= true): Promise<boolean> {
private static async _ApiDirFileListOnePageOpenApi(orderby: string, order: string, dir: IAliFileResp, type: string, pageIndex: number): Promise<boolean> {
let url = 'adrive/v1.0/openFile/list'
let postData
if (useSettingStore().uiFileListMode === 'movie' && search) {
postData = {
drive_id: dir.m_drive_id,
parent_file_id: dir.dirID.includes('root') ? 'root' : dir.dirID,
marker: dir.next_marker,
category: "video",
limit: 200,
order_by: orderby,
order_direction: order.toUpperCase()
}
} else {
postData = {
let postData = {
drive_id: dir.m_drive_id,
parent_file_id: dir.dirID,
marker: dir.next_marker,
@@ -468,7 +255,6 @@ export default class AliDirFileList {
order_by: orderby,
order_direction: order.toUpperCase()
}
}
if (type) {
postData = Object.assign(postData, { type })
pageIndex = -1
@@ -478,36 +264,24 @@ export default class AliDirFileList {
}
private static async _ApiDirFileListCount(dir: IAliFileResp, dirID: string, type: string): Promise<number> {
let isPic = dirID.includes('pic')
type = isPic ? 'file' : type
let url = ''
if (!isPic) {
url = 'adrive/v1.0/openFile/search'
} else {
url = 'adrive/v3/file/search'
}
let parent_file_id = dir.dirID.includes('_root') ? 'root' : dir.dirID
const postData: any = {
private static async _ApiDirFileListCount(dir: IAliFileResp, type: string): Promise<number> {
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
const postData = {
drive_id: dir.m_drive_id,
marker: '',
limit: 1,
all: false,
url_expire_sec: 14400,
fields: 'thumbnail',
query: 'parent_file_id="' + parent_file_id + '"' + (type ? ' and type="' + type + '"' : ''),
query: 'parent_file_id="' + dir.dirID + '"' + (type ? ' and type="' + type + '"' : ''),
return_total_count: true
}
if (!isPic) {
delete postData.all
delete postData.url_expire_sec
}
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
return resp.body.total_count || 0
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ApiDirFileListCount err=' + dir.dirID + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ApiDirFileListCount err=' + dir.dirID + ' ' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ApiDirFileListCount ' + dir.dirID, err)
@@ -532,8 +306,8 @@ export default class AliDirFileList {
try {
if (AliHttp.IsSuccess(resp.code)) {
return resp.body.total_count || 0
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ApiFavoriteFileListCount err=' + dir.dirID + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ApiFavoriteFileListCount err=' + dir.dirID + ' ' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ApiFavoriteFileListCount ' + dir.dirID, err)
@@ -543,8 +317,8 @@ export default class AliDirFileList {
private static async _ApiFavorFileListOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
let url = 'v2/file/list_by_custom_index_key'
if (useSettingStore().uiShowPanMedia == false) url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + ')'
else url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + '%2Cuser_meta%2Cvideo_media_metadata(duration%2Cwidth%2Cheight%2Ctime)%2Cvideo_preview_metadata%2Fduration%2Cimage_media_metadata)'
if (useSettingStore().uiShowPanMedia == false) url += '?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(' + AliDirFileList.ItemJsonmask + ')'
else url += '?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(' + AliDirFileList.ItemJsonmask + '%2Cvideo_media_metadata(duration%2Cwidth%2Cheight%2Ctime)%2Cvideo_preview_metadata%2Fduration%2Cimage_media_metadata)'
const postData = {
drive_id: dir.m_drive_id,
@@ -562,7 +336,7 @@ export default class AliDirFileList {
}
private static async _ApiTrashFileListOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
const url = 'v2/recyclebin/list?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + ')'
const url = 'v2/recyclebin/list?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(' + AliDirFileList.ItemJsonmask + ')'
const postData = {
drive_id: dir.m_drive_id,
@@ -582,9 +356,7 @@ export default class AliDirFileList {
const url = 'adrive/v1/file/listDeleted'
const postData = {
drive_id: dir.m_drive_id,
limit: 100,
order_by: 'gmt_deleted',
order_direction: 'DESC',
album_drive_id: dir.m_drive_id,
marker: dir.next_marker
}
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
@@ -592,17 +364,14 @@ export default class AliDirFileList {
}
static async _ApiSearchFileListOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
let url = 'adrive/v3/file/search'
if (useSettingStore().uiShowPanMedia == false) url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + ')'
else url += '?jsonmask=next_marker%2Citems(' + AliDirFileList.ItemJsonmask + '%2Cuser_meta%2Cvideo_media_metadata(duration%2Cwidth%2Cheight%2Ctime)%2Cvideo_preview_metadata%2Fduration%2Cimage_media_metadata)'
let url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
let query = ''
let drive_id_list = []
if (dir.dirID.startsWith('color')) {
const color = dir.dirID.substring('color'.length).split(' ')[0].replace('#', 'c')
query = 'description="' + color + '"'
} else if (dir.dirID.startsWith('search')) {
const search = dir.dirID.substring('search'.length).split(' ')
let word = ''
for (let i = 0; i < search.length; i++) {
const itemstr = search[i]
@@ -610,15 +379,11 @@ export default class AliDirFileList {
word += itemstr + ' '
continue
}
const kv = search[i].split(':')
const k = kv[0]
const v = kv[1]
if (k == 'range') {
const arr = v.split(',')
for (let j = 0; j < arr.length; j++) {
drive_id_list.push(GetDriveID(dir.m_user_id, arr[j]))
}
} else if (k == 'type') {
if (k == 'type') {
const arr = v.split(',')
let type = ''
for (let j = 0; j < arr.length; j++) {
@@ -628,23 +393,24 @@ export default class AliDirFileList {
type = type.substring(0, type.length - 4).trim()
if (type && type.indexOf(' or ') > 0) query += '(' + type + ') and '
else if (type) query += type + ' and '
} else if (k == 'size') {
const size = parseInt(v)
if (size > 0) query += 'size = ' + v + ' and '
} else if (k == 'description') {
query += 'description = ' + v + ' and '
} else if (k == 'max') {
const max = parseInt(v)
if (max > 0) query += 'size <= ' + v + ' and '
} else if (k == 'min') {
const min = parseInt(v)
if (min > 0) query += 'size >= ' + v + ' and '
} else if (k == 'begin') {
}
// else if (k == 'size') {
// const size = parseInt(v)
// if (size > 0) query += 'size = ' + v + ' and '
// }
// else if (k == 'max') {
// const max = parseInt(v)
// if (max > 0) query += 'size <= ' + v + ' and '
// } else if (k == 'min') {
// const min = parseInt(v)
// if (min > 0) query += 'size >= ' + v + ' and '
// }
else if (k == 'begin') {
const dt = new Date(v).toISOString()
query += 'updated_at >= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
query += 'created_at >= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
} else if (k == 'end') {
const dt = new Date(v).toISOString()
query += 'updated_at <= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
query += 'created_at <= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
} else if (k == 'ext') {
const arr = v.split(',')
let extin = ''
@@ -660,23 +426,22 @@ export default class AliDirFileList {
if (query.length > 0) query = query.substring(0, query.length - 5)
if (query.startsWith('(') && query.endsWith(')')) query = query.substring(1, query.length - 1)
}
const postData: any = {
const postData = {
drive_id: dir.m_drive_id,
marker: dir.next_marker,
limit: 100,
limit: 100 ,
fields: '*',
query: query,
order_by: orderby + ' ' + order
}
if (drive_id_list.length > 0) postData.drive_id_list = drive_id_list
else postData.drive_id = dir.m_drive_id
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex)
}
static async _ApiSearchFileListCount(dir: IAliFileResp): Promise<number> {
const url = 'adrive/v3/file/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
let query = ''
let drive_id_list = []
if (dir.dirID.startsWith('color')) {
const color = dir.dirID.substring('color'.length).split(' ')[0].replace('#', 'c')
query = 'description="' + color + '"'
@@ -694,12 +459,7 @@ export default class AliDirFileList {
const kv = search[i].split(':')
const k = kv[0]
const v = kv[1]
if (k == 'range') {
const arr = v.split(',')
for (let j = 0; j < arr.length; j++) {
drive_id_list.push(GetDriveID(dir.m_user_id, arr[j]))
}
} else if (k == 'type') {
if (k == 'type') {
const arr = v.split(',')
let type = ''
for (let j = 0; j < arr.length; j++) {
@@ -709,23 +469,23 @@ export default class AliDirFileList {
type = type.substring(0, type.length - 4).trim()
if (type && type.indexOf(' or ') > 0) query += '(' + type + ') and '
else if (type) query += type + ' and '
} else if (k == 'size') {
const size = parseInt(v)
if (size > 0) query += 'size = ' + v + ' and '
} else if (k == 'description') {
query += 'description = ' + v + ' and '
} else if (k == 'max') {
const max = parseInt(v)
if (max > 0) query += 'size <= ' + v + ' and '
} else if (k == 'min') {
const min = parseInt(v)
if (min > 0) query += 'size >= ' + v + ' and '
} else if (k == 'begin') {
}
// else if (k == 'size') {
// const size = parseInt(v)
// if (size > 0) query += 'size = ' + v + ' and '
// } else if (k == 'max') {
// const max = parseInt(v)
// if (max > 0) query += 'size <= ' + v + ' and '
// } else if (k == 'min') {
// const min = parseInt(v)
// if (min > 0) query += 'size >= ' + v + ' and '
// }
else if (k == 'begin') {
const dt = new Date(v).toISOString()
query += 'updated_at >= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
query += 'created_at >= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
} else if (k == 'end') {
const dt = new Date(v).toISOString()
query += 'updated_at <= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
query += 'created_at <= "' + dt.substring(0, dt.lastIndexOf('.')) + '" and '
} else if (k == 'ext') {
const arr = v.split(',')
let extin = ''
@@ -741,21 +501,20 @@ export default class AliDirFileList {
if (query.length > 0) query = query.substring(0, query.length - 5)
if (query.startsWith('(') && query.endsWith(')')) query = query.substring(1, query.length - 1)
}
const postData: any = {
const postData = {
drive_id: dir.m_drive_id,
marker: dir.next_marker,
limit: 1,
limit: 1 ,
fields: '*',
query: query,
return_total_count: true
}
if (drive_id_list.length > 0) postData.drive_id_list = drive_id_list
else postData.drive_id = dir.m_drive_id
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
return (resp.body.total_count as number) || 0
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ApiSearchFileListCount err=' + dir.dirID + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ApiSearchFileListCount err=' + dir.dirID + ' ' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ApiSearchFileListCount ' + dir.dirID, err)
@@ -763,42 +522,8 @@ export default class AliDirFileList {
return 0
}
static async _ApiAlbumListOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
const url = 'adrive/v1/album/list'
const postData = {
limit: 100,
order_by: 'updated_at',
order_direction: 'DESC'
}
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex)
}
static async _ApiAlbumListFilesOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
const url = 'adrive/v1/album/list_files'
const postData = {
album_id: dir.albumID,
fields: '*',
filter: '',
limit: 100,
order_by: 'joined_at',
order_direction: 'DESC',
image_thumbnail_process: 'image/resize,w_400/format,jpeg',
image_url_process: 'image/resize,w_1920/format,jpeg',
video_thumbnail_process: 'video/snapshot,t_0,f_jpg,ar_auto,w_1000'
}
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex)
}
static async _ApiVideoListRecent(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise<boolean> {
let need_open_api = true
let url = ''
if (need_open_api) {
url = 'adrive/v1.1/openFile/video/recentList'
} else {
url = 'adrive/v2/video/recentList'
}
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/video/recentList'
const postData = {}
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex)
@@ -836,7 +561,7 @@ export default class AliDirFileList {
}
static _FileListOnePage(orderby: string, order: string, dir: IAliFileResp, resp: IUrlRespData, pageIndex: number, type: string = '', refresh: boolean = true): boolean {
static _FileListOnePage(orderby: string, order: string, dir: IAliFileResp, resp: IUrlRespData, pageIndex: number, type: string = ''): boolean {
try {
if (AliHttp.IsSuccess(resp.code)) {
const dirPart: IAliFileResp = {
@@ -852,48 +577,41 @@ export default class AliDirFileList {
dir.next_marker = resp.body.next_marker || ''
const isRecover = dir.dirID == 'recover'
const isDirFile = dir.dirID.includes('root') || (dir.dirID.length == 40 && !dir.dirID.startsWith('search'))
const isDirFile = dir.dirID == 'root' || (dir.dirID.length == 40 && !dir.dirID.startsWith('search'))
const isVideo = dir.dirID.startsWith('video')
const isPic = dir.dirID.includes('pic')
const downUrl = isRecover ? '' : 'https://api.alipan.com/v2/file/download?t=' + Date.now().toString()
// const issearch = dir.dirID.startsWith('search')
// const iscolor = dir.dirID.startsWith('color')
const downUrl = isRecover ? '' : 'https://api.aliyundrive.com/v2/file/download?t=' + Date.now().toString()
if (resp.body.items) {
let settingStore = useSettingStore()
const driverData = TreeStore.GetDriver(dir.m_drive_id)
const DirFileSizeMap = driverData?.DirFileSizeMap || {}
const DirTotalSizeMap = driverData?.DirTotalSizeMap || {}
const isFolderSize = settingStore.uiFolderSize
const isFolderSize = useSettingStore().uiFolderSize
let dirList: IAliGetFileModel[] = []
let fileList: IAliGetFileModel[] = []
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliFileItem
if (isVideo) {
if (!item.compilation_id && (!item.drive_id || !item.file_id)) continue
if (!item.compilation_id) {
item.type = 'file'
item.category = 'video'
item.compilation_id = item.drive_id + '_' + item.file_id
}
if (item.video_type == 'COMPILATION') {
item.type = 'folder'
item.drive_id = item.compilation_id.split('_')[0]
item.file_id = item.compilation_id.split('_')[1]
} else {
item.category = 'video'
}
}
if (isPic) {
if (!item.album_id && (!item.drive_id || !item.file_id)) continue
if (item.album_id) {
item.type = 'folder'
item.file_id = item.album_id
}
}
if (dir.itemsKey.has(item.file_id)) continue
const add = AliDirFileList.getFileInfo(dir.m_user_id, item, downUrl)
const add = AliDirFileList.getFileInfo(item, downUrl)
if (isRecover) add.description = item.content_hash
if (isVideo) add.compilation_id = item.compilation_id
if (isPic) add.album_id = item.album_id
if (isVideo) {
add.compilation_id = item.compilation_id
}
if (add.isDir) {
if (isFolderSize) {
add.size = DirTotalSizeMap[add.file_id] || DirFileSizeMap[add.file_id] || 0
@@ -915,22 +633,19 @@ export default class AliDirFileList {
dir.items.push(...fileList)
}
}
dirPart.punished_file_count = resp.body.punished_file_count || 0
dir.punished_file_count += resp.body.punished_file_count || 0
// 相册文件数
if (isPic && dir.dirID != 'pic_root') {
dir.itemsTotal = resp.body.totalCount
}
if (pageIndex >= 0 && type == '' && refresh) {
if (pageIndex >= 0 && type == '') {
const pan = usePanFileStore()
if (pan.DriveID == dir.m_drive_id) {
pan.mSaveDirFileLoadingPart(pageIndex, dirPart, dir.itemsTotal || 0)
}
if (pan.DriveID == dir.m_drive_id) pan.mSaveDirFileLoadingPart(pageIndex, dirPart, dir.itemsTotal || 0)
}
if (dirPart.next_marker == 'cancel') dir.next_marker = 'cancel'
if (isVideo && dir.items.length >= 500) dir.next_marker = ''
return true
} else if (resp.code == 404) {
dir.items.length = 0
dir.next_marker = ''
return true
@@ -939,8 +654,8 @@ export default class AliDirFileList {
dir.next_marker = resp.body.code
message.warning('列出文件出错 ' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_FileListOnePage err=' + dir.dirID + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_FileListOnePage err=' + dir.dirID + ' ' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_FileListOnePage ' + dir.dirID, err)
@@ -950,43 +665,61 @@ export default class AliDirFileList {
}
static async ApiDirFileSize(user_id: string, drive_id: string, file_idList: string[]): Promise<{
dirID: string;
size: number
}[] | undefined> {
static async ApiDirFileSize(user_id: string, drive_id: string, file_idList: string[]): Promise<{ dirID: string; size: number }[] | undefined> {
const list: Map<string, { dirID: string; size: number }> = new Map<string, { dirID: string; size: number }>()
let postData = '{"requests":['
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
list.set(file_idList[i], { dirID: file_idList[i], size: 0 })
let id = file_idList[i].includes('root') ? 'root' : file_idList[i]
let postData = {
if (i > 0) postData = postData + ','
const data2 = {
body: {
drive_id: drive_id,
query: 'parent_file_id="' + file_idList[i] + '" and type="file"',
limit: 100,
query: 'parent_file_id="' + id + '" and type="file"',
fields: 'thumbnail',
order_by: 'size DESC'
},
headers: { 'Content-Type': 'application/json' },
id: file_idList[i],
method: 'POST',
url: '/file/search'
}
const url = 'adrive/v3/file/search?jsonmask=next_marker%2Citems(size)'
postData = postData + JSON.stringify(data2)
}
postData += '],"resource":"file"}'
const url = 'v2/batch?jsonmask=responses(id%2Cstatus%2Cbody(next_marker%2Citems(size)))'
const resp = await AliHttp.Post(url, postData, user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
if (resp.body && resp.body.items && resp.body.items.length > 0) {
const responses = resp.body.responses
for (let j = 0, maxj = responses.length; j < maxj; j++) {
const respi = responses[j]
if (respi.id && respi.status && respi.status >= 200 && respi.status <= 205) {
if (respi.body && respi.body.items && respi.body.items.length > 0) {
let size = 0
const items = resp.body.items
const items = respi.body.items
for (let k = 0, maxk = items.length; k < maxk; k++) {
size += items[k].size || 0
}
const find = list.get(id)
const find = list.get(respi.id)
if (find) find.size = size
}
}
}
return MapValueToArray(list)
} else {
DebugLog.mSaveWarning('ApiDirFileSize err=' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('ApiDirFileSize err=' + (resp.code || ''))
return undefined
}
} catch (err: any) {
DebugLog.mSaveWarning('ApiDirFileSize', err)
}
}
return MapValueToArray(list)
}
}

View File

@@ -1,11 +1,13 @@
import DebugLog from '../utils/debuglog'
import { MapValueToArray } from '../utils/utils'
import AliHttp from './alihttp'
import { IAliGetDirModel, IAliGetFileModel } from './alimodels'
import {IAliFileItem, IAliGetDirModel, IAliGetFileModel} from './alimodels'
import AliDirFileList from './dirfilelist'
import dayjs from 'dayjs'
import AliTrash from './trash'
import { DirData } from '../store/treestore'
import AliUser from './user'
import AliFile from "./file";
export interface IAliDirResp {
items: IAliGetDirModel[]
@@ -123,11 +125,12 @@ export default class AliDirList {
break
}
} else if (resp.code == 402) {
DebugLog.mSaveWarning('ApiFastAllDirList err=' + drive_id + ' ' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('ApiFastAllDirList err=' + drive_id + ' ' + (resp.code || ''))
break
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
} else {
errorMessage += 'err' + (resp.code || '')
DebugLog.mSaveWarning('ApiFastAllDirList err=' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('ApiFastAllDirList err=' + (resp.code || ''))
}
} catch (err: any) {
@@ -145,6 +148,87 @@ export default class AliDirList {
return result
}
static async ApiBatchDirFileList(user_id: string, drive_id: string, dirList: IAliDirBatchResp[], limit: number, listTypeOrQuery: string): Promise<boolean> {
if (!user_id || !drive_id) return false
let postData = '{"requests":['
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
if (i > 0) postData = postData + ','
let query = 'parent_file_id="' + dirList[i].dirID + '"'
if (listTypeOrQuery == 'all') query = 'parent_file_id="' + dirList[i].dirID + '"'
else if (listTypeOrQuery == 'folder') query = 'parent_file_id="' + dirList[i].dirID + '" and type = "folder"'
else if (listTypeOrQuery == 'file') query = 'parent_file_id="' + dirList[i].dirID + '" and type = "file"'
else query = query + listTypeOrQuery
const data2 = {
body: {
drive_id: drive_id,
query: query,
marker: dirList[i].next_marker,
url_expire_sec: 14400,
limit: limit,
// fields: 'thumbnail',
image_thumbnail_process: '',
video_thumbnail_process: '',
image_url_process: ''
},
headers: { 'Content-Type': 'application/json' },
id: dirList[i].dirID,
method: 'POST',
url: '/file/search'
}
postData = postData + JSON.stringify(data2)
}
postData += '],"resource":"file"}'
const url = 'v2/batch'
const resp = await AliHttp.Post(url, postData, user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
const responses = resp.body.responses
for (let j = 0, maxj = responses.length; j < maxj; j++) {
const status = responses[j].status as number
if (status >= 200 && status <= 205) {
const respi = responses[j]
const id = respi.id || ''
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
if (dirList[i].dirID == id) {
const dir = dirList[i]
dir.next_marker = respi.body.next_marker
const items = respi.body.items
for (let i = 0, maxi = items.length; i < maxi; i++) {
const fileItem = items[i] as IAliFileItem
if (fileItem.type === 'file') {
const fileDetails = await AliFile.ApiFileInfoOpenApi(user_id, drive_id, fileItem.file_id);
fileItem.thumbnail = fileDetails?.thumbnail
fileItem.url = fileDetails?.url || fileItem.url
fileItem.download_url = fileDetails?.download_url || fileItem.download_url
fileItem.starred = fileDetails?.starred || fileItem.starred
fileItem.trashed = fileDetails?.trashed || fileItem.trashed
fileItem.deleted = fileDetails?.deleted || fileItem.deleted
fileItem.description = fileDetails?.description || fileItem.description
}
const add = AliDirFileList.getFileInfo(fileItem, '')
dir.items.push(add)
}
break
}
}
}
}
return true
} else {
DebugLog.mSaveWarning('ApiBatchDirFileList err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveWarning('ApiBatchDirFileList', err)
}
return false
}
static async ApiFastAllDirListByTime(user_id: string, drive_id: string, created_at: Date): Promise<IAliDirResp> {
const result: IAliDirResp = {
items: [],
@@ -180,13 +264,6 @@ export default class AliDirList {
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
if (i > 0) postData = postData + ','
const query = 'type="folder" and ' + dirList[i].dirID
let id = dirList[i].dirID
.replaceAll('"', '')
.replaceAll(' ', '')
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll(',', '')
if (id.includes('root')) id = 'root'
const data2 = {
body: {
drive_id: drive_id,
@@ -196,7 +273,7 @@ export default class AliDirList {
fields: 'thumbnail'
},
headers: { 'Content-Type': 'application/json' },
id: id,
id: dirList[i].dirID.replaceAll('"', '').replaceAll(' ', '').replaceAll('-', '').replaceAll(':', '').replaceAll(',', ''),
method: 'POST',
url: '/file/search'
}
@@ -217,13 +294,7 @@ export default class AliDirList {
const respi = responses[j]
const id = respi.id || ''
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
let pid = dirList[i].dirID
.replaceAll('"', '')
.replaceAll(' ', '')
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll(',', '')
if (pid == id) {
if (dirList[i].dirID.replaceAll('"', '').replaceAll(' ', '').replaceAll('-', '').replaceAll(':', '').replaceAll(',', '') == id) {
const dir = dirList[i]
const items = respi.body.items
dir.next_marker = respi.body.next_marker
@@ -252,9 +323,9 @@ export default class AliDirList {
}
dirList.length = 0
dirList = list
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
} else {
errorMessage = (resp.code || '').toString()
DebugLog.mSaveWarning('SSApiBatchDirFileList err=' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('SSApiBatchDirFileList err=' + (resp.code || ''))
}
} catch (err: any) {
errorMessage = err.message || ''
@@ -270,7 +341,7 @@ export default class AliDirList {
}
static async _ApiDirFileListInfo(user_id: string, drive_id: string) {
const url = 'adrive/v3/file/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
const postData = {
drive_id: drive_id,
marker: '',
@@ -289,8 +360,8 @@ export default class AliDirList {
const created_at = items.length > 0 ? new Date(items[0].created_at) : new Date()
const total_count = resp.body.total_count || 0
return { created_at, total_count }
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ApiDirFileListInfo err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ApiDirFileListInfo err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ApiDirFileListInfo', err)
@@ -299,13 +370,13 @@ export default class AliDirList {
}
static async ApiFastAllDirListByPID(user_id: string, drive_id: string, drive_root: string): Promise<IDirDataResp> {
static async ApiFastAllDirListByPID(user_id: string, drive_id: string): Promise<IDirDataResp> {
const result: IDirDataResp = {
items: [],
next_marker: '',
m_user_id: user_id,
m_drive_id: drive_id,
dirID: drive_root,
dirID: 'root',
dirName: ''
}
if (!user_id || !drive_id) return result
@@ -314,110 +385,24 @@ export default class AliDirList {
const dirCount = await AliUser.ApiUserDriveFileCount(user_id, '', 'folder')
const PIDList: string[] = []
const root = await AliTrash.ApiDirFileListNoLock(user_id, drive_id, drive_root, '', 'name ASC', 'folder', 0)
const root = await AliTrash.ApiDirFileListNoLock(user_id, drive_id, 'root', '', 'name ASC', 'folder', 0)
for (let i = 0, maxi = root.items.length; i < maxi; i++) {
const item = root.items[i]
if (item.parent_file_id === 'root') {
item.parent_file_id = drive_root
}
const add: DirData = {
file_id: item.file_id,
drive_id: item.drive_id,
parent_file_id: item.parent_file_id,
name: item.name,
time: item.time,
description: item.description,
size: 0
}
allMap.set(add.file_id, add)
PIDList.push(add.file_id)
}
let errorMessage = ''
let dirList: IAliDirBatchResp[] = []
let index = 0
while (true) {
while (dirList.length < 30) {
if (PIDList.length > index) {
let dirID = 'parent_file_id in ['
let add = 0
for (let maxj = PIDList.length; index < maxj; index++) {
let PID = PIDList[index].includes('root') ? 'root' : PIDList[index]
dirID += add == 0 ? '"' + PID + '"' : ',"' + PID + '"'
add++
if (add >= 50) break
}
dirID += ']'
dirList.push({ dirID: dirID, next_marker: '', items: [], itemsKey: new Set() } as IAliDirBatchResp)
} else break
}
if (dirList.length == 0) break
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
const dir = dirList[i]
if(!dir) break
const query = 'type="folder" and ' + dir.dirID
let postData = {
drive_id: drive_id,
limit: 100,
query: query,
fields: 'thumbnail',
order_by: 'name ASC',
marker: dir.next_marker
}
const url = 'adrive/v3/file/search?jsonmask=next_marker%2Citems(drive_id%2Ccreated_at%2Cfile_id%2Cname%2Cparent_file_id%2Cupdated_at%2Cdescription)'
const resp = await AliHttp.Post(url, postData, user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
const items = resp.body.items
const list: IAliDirBatchResp[] = []
dir.next_marker = resp.body.next_marker
if (dir.next_marker) {
list.push(dir)
}
for (let i = 0, maxi = items.length; i < maxi; i++) {
const item = items[i]
if (allMap.has(item.file_id)) continue
if (item.parent_file_id === 'root') {
item.parent_file_id = drive_root
}
const add: DirData = {
file_id: item.file_id,
drive_id: item.drive_id,
parent_file_id: item.parent_file_id,
name: item.name,
time: new Date(item.updated_at).getTime(),
description: item.description,
size: 0
}
allMap.set(add.file_id, add)
PIDList.push(add.file_id)
}
dirList.length = 0
dirList = list
if (window.WinMsgToMain) window.WinMsgToMain({
cmd: 'MainShowAllDirProgress',
drive_id,
index: allMap.size,
total: dirCount
})
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
errorMessage = (resp.code || '').toString()
DebugLog.mSaveWarning('SSApiBatchDirFileList err=' + (resp.code || ''), resp.body)
}
} catch (err: any) {
errorMessage = err.message || ''
DebugLog.mSaveWarning('ApiBatchDirFileList', err)
}
}
}
const list = MapValueToArray(allMap)
console.log('listcount', list.length)
result.items = errorMessage ? [] : list
result.next_marker = errorMessage
return result
}
static async ApiBatchLoadDirListByPID(user_id: string, drive_root: string, drive_id: string, PIDList: string[], allMap: Map<string, DirData>, dirCount: number, errorMessage: string = '') {
allMap.set(add.file_id, add)
PIDList.push(add.file_id)
}
let dirList: IAliDirBatchResp[] = []
let errorMessage = ''
let index = 0
while (true) {
while (dirList.length < 30) {
@@ -425,8 +410,7 @@ export default class AliDirList {
let dirID = 'parent_file_id in ['
let add = 0
for (let maxj = PIDList.length; index < maxj; index++) {
let PID = PIDList[index].includes('root') ? 'root' : PIDList[index]
dirID += add == 0 ? '"' + PID + '"' : ',"' + PID + '"'
dirID += add == 0 ? '"' + PIDList[index] + '"' : ',"' + PIDList[index] + '"'
add++
if (add >= 50) break
}
@@ -440,12 +424,6 @@ export default class AliDirList {
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
if (i > 0) postData = postData + ','
const query = 'type="folder" and ' + dirList[i].dirID
let id = dirList[i].dirID
.replaceAll('"', '')
.replaceAll(' ', '')
.replaceAll(',', '')
.substring(0, 54)
if (id.includes('root')) id = 'root'
const data2 = {
body: {
drive_id: drive_id,
@@ -456,7 +434,7 @@ export default class AliDirList {
order_by: 'name ASC'
},
headers: { 'Content-Type': 'application/json' },
id: id,
id: dirList[i].dirID.replaceAll('"', '').replaceAll(' ', '').replaceAll(',', '').substring(0, 54),
method: 'POST',
url: '/file/search'
}
@@ -477,29 +455,19 @@ export default class AliDirList {
const respi = responses[j]
const id = respi.id || ''
for (let i = 0, maxi = dirList.length; i < maxi; i++) {
let pid = dirList[i].dirID
.replaceAll('"', '')
.replaceAll(' ', '')
.replaceAll(',', '')
.substring(0, 54)
if (pid == id) {
if (dirList[i].dirID.replaceAll('"', '').replaceAll(' ', '').replaceAll(',', '').substring(0, 54) == id) {
const dir = dirList[i]
const items = respi.body.items
dir.next_marker = respi.body.next_marker
if (dir.next_marker) list.push(dir)
for (let i = 0, maxi = items.length; i < maxi; i++) {
if (allMap.has(items[i].file_id)) continue
const item = items[i]
if (allMap.has(item.file_id)) continue
if (item.parent_file_id === 'root') {
item.parent_file_id = drive_root
}
const add: DirData = {
file_id: item.file_id,
drive_id: item.drive_id,
parent_file_id: item.parent_file_id,
name: item.name,
time: new Date(item.updated_at).getTime(),
description: item.description,
size: 0
}
allMap.set(add.file_id, add)
@@ -512,20 +480,22 @@ export default class AliDirList {
}
dirList.length = 0
dirList = list
if (window.WinMsgToMain) window.WinMsgToMain({
cmd: 'MainShowAllDirProgress',
drive_id,
index: allMap.size,
total: dirCount
})
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
if (window.WinMsgToMain) window.WinMsgToMain({ cmd: 'MainShowAllDirProgress', drive_id, index: allMap.size, total: dirCount })
} else {
errorMessage = (resp.code || '').toString()
DebugLog.mSaveWarning('SSApiBatchDirFileList err=' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('SSApiBatchDirFileList err=' + (resp.code || ''))
}
} catch (err: any) {
errorMessage = err.message || ''
DebugLog.mSaveWarning('ApiBatchDirFileList', err)
}
}
const list = MapValueToArray(allMap)
console.log('listcount', list.length)
result.items = errorMessage ? [] : list
result.next_marker = errorMessage
return result
}
}

View File

@@ -1,50 +1,13 @@
import { useSettingStore } from '../store'
import DebugLog from '../utils/debuglog'
import { GetExpiresTime, HanToPin } from '../utils/utils'
import AliHttp from './alihttp'
import { IAliFileItem, IAliGetDirModel, IAliGetFileModel, IAliGetForderSizeModel } from './alimodels'
import AliDirFileList from './dirfilelist'
import { ICompilationList, IDownloadUrl, IOfficePreViewUrl, IVideoPreviewUrl, IVideoXBTUrl } from './models'
import { DecodeEncName, GetDriveType } from './utils'
import { getRawUrl } from '../utils/proxyhelper'
import message from '../utils/message'
import { GetOssExpires, HanToPin } from '../utils/utils'
import AliHttp from './alihttp'
import {IAliFileItem, IAliGetDirModel, IAliGetFileModel, IAliGetForderSizeModel} from './alimodels'
import AliDirFileList from './dirfilelist'
import {ICompilationList, IDownloadUrl, IOfficePreViewUrl, IVideoPreviewUrl, IVideoXBTUrl} from './models'
export default class AliFile {
static async ApiFileDownloadUrlOpenApi(user_id: string, drive_id: string, file_id: string, expire_sec= 14400): Promise<IDownloadUrl | string> {
if (!user_id || !drive_id || !file_id) return 'file_id错误'
const data: IDownloadUrl = {
drive_id: drive_id,
file_id: file_id,
expire_time: 0,
url: '',
size: 0
}
const url = 'adrive/v1.0/openFile/getDownloadUrl'
let postData = {
"expire_sec": 14400,
"drive_id": drive_id,
"file_id": file_id
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
data.url = resp.body.url
data.size = resp.body.size
data.expire_time = GetExpiresTime(data.url)
console.log("ApiFileDownloadUrlOpenApi: ", data)
return data
} else if (resp.body.code == 'NotFound.FileId') {
return '文件已从网盘中彻底删除'
} else if (resp.body.code == 'ForbiddenFileInTheRecycleBin') {
return '文件已放入回收站'
} else if (resp.body.code) {
return resp.body.code as string
} else {
DebugLog.mSaveWarning('ApiFileDownloadUrl err=' + file_id + ' ' + (resp.code || ''))
}
return '网络错误'
}
static async ApiFileInfoOpenApi(user_id: string, drive_id: string, file_id: string,
image_thumbnail_width=100,
@@ -69,22 +32,10 @@ export default class AliFile {
return undefined
}
static async ApiFileInfo(user_id: string, drive_id: string, file_id: string, ispic: boolean = false): Promise<any | undefined> {
static async ApiFileInfo(user_id: string, drive_id: string, file_id: string): Promise<IAliFileItem | undefined> {
if (!user_id || !drive_id || !file_id) return undefined
let url = ''
let postData = {}
if (!ispic) {
url = 'adrive/v1.0/openFile/get'
postData = {
drive_id: drive_id,
file_id: file_id,
image_thumbnail_width: 100,
video_thumbnail_width: 100,
video_thumbnail_time: 120000
}
} else {
url = 'v2/file/get'
postData = {
const url = 'v2/file/get'
const postData = {
drive_id: drive_id,
file_id: file_id,
url_expire_sec: 14400,
@@ -93,27 +44,14 @@ export default class AliFile {
image_url_process: 'image/resize,w_1920/format,jpeg',
video_thumbnail_process: 'video/snapshot,t_106000,f_jpg,ar_auto,m_fast,w_400'
}
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
let fileInfo = resp.body as IAliFileItem
if (fileInfo.name.toLowerCase() === 'default') {
fileInfo.name = '备份盘'
} else if (fileInfo.name.toLowerCase() === 'resource') {
fileInfo.name = '资源盘'
} else if (fileInfo.name.toLowerCase() === 'alibum') {
fileInfo.name = '相册'
return resp.body as IAliFileItem
} else {
fileInfo.name = DecodeEncName(user_id, fileInfo).name
DebugLog.mSaveWarning('ApiFileInfo err=' + file_id + ' ' + (resp.code || ''))
}
return fileInfo
} else if (AliHttp.HttpCodeBreak(resp.code)) {
return (resp.body.message || resp.body) as string
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileInfo err=' + file_id + ' ' + (resp.code || ''), resp.body)
}
return '网络错误'
return undefined
}
@@ -133,94 +71,108 @@ export default class AliFile {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
let fileInfo = resp.body as IAliFileItem
fileInfo.name = DecodeEncName(user_id, fileInfo).name
return fileInfo
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileInfoByPath err=' + file_path + ' ' + (resp.code || ''), resp.body)
return resp.body as IAliFileItem
} else {
DebugLog.mSaveWarning('ApiFileInfoByPath err=' + file_path + ' ' + (resp.code || ''))
}
return undefined
}
static async ApiFileDownloadUrl(user_id: string, drive_id: string, file_id: string, expire_sec: number): Promise<IDownloadUrl | string> {
if (!user_id || !drive_id || !file_id) return '参数错误'
static async ApiFileDownloadUrlOpenApi(user_id: string, drive_id: string, file_id: string, expire_sec= 14400): Promise<IDownloadUrl | string> {
if (!user_id || !drive_id || !file_id) return 'file_id错误'
const data: IDownloadUrl = {
drive_id: drive_id,
file_id: file_id,
expire_time: 0,
expire_sec: 0,
url: '',
size: 0
}
let url = ''
// 处理OpenApi无法访问相册
let isPic = GetDriveType(user_id, drive_id).name === 'pic'
if (!isPic) {
url = 'adrive/v1.0/openFile/getDownloadUrl'
} else {
url = 'v2/file/get_download_url'
}
const postData: any = {
drive_id: drive_id,
file_id: file_id,
expire_sec: expire_sec
}
if (isPic) {
delete postData.expire_sec
const url = 'adrive/v1.0/openFile/getDownloadUrl'
let postData = {
"expire_sec": 36000,
"drive_id": drive_id,
"file_id": file_id
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
data.url = resp.body.cdn_url || resp.body.url
data.url = resp.body.url
data.size = resp.body.size
data.expire_time = GetExpiresTime(data.url)
data.expire_sec = GetOssExpires(data.url)
console.log("ApiFileDownloadUrlOpenApi: ", data)
return data
} else if (resp.body.code == 'NotFound.FileId') {
return '文件已从网盘中彻底删除'
} else if (resp.body.code == 'ForbiddenFileInTheRecycleBin') {
return '文件已放入回收站'
} else if (AliHttp.HttpCodeBreak(resp.code)) {
return (resp.body.message || resp.body) as string
} else if (resp.body.code) {
return resp.body.code as string
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileDownloadUrl err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiFileDownloadUrl err=' + file_id + ' ' + (resp.code || ''))
}
return '网络错误'
}
static async ApiVideoPreviewUrl(user_id: string, drive_id: string, file_id: string): Promise<IVideoPreviewUrl | string> {
if (!user_id || !drive_id || !file_id) return '参数错误'
let url = ''
let need_open_api = true
if (need_open_api) {
url = 'adrive/v1.0/openFile/getVideoPreviewPlayInfo'
} else {
url = 'v2/file/get_video_preview_play_info'
}
const postData = {
static async ApiFileDownloadUrl(user_id: string, drive_id: string, file_id: string, expire_sec: number): Promise<IDownloadUrl | string> {
if (!user_id || !drive_id || !file_id) return 'file_id错误'
const data: IDownloadUrl = {
drive_id: drive_id,
file_id: file_id,
category: 'live_transcoding',
template_id: '',
get_subtitle_info: true,
url_expire_sec: 14400
expire_sec: 0,
url: '',
size: 0
}
const url = 'v2/file/get_download_url'
const postData = { drive_id: drive_id, file_id: file_id, expire_sec: expire_sec }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
data.url = resp.body.url
data.size = resp.body.size
data.expire_sec = GetOssExpires(data.url)
return data
} else if (resp.body.code == 'NotFound.FileId') {
return '文件已从网盘中彻底删除'
} else if (resp.body.code == 'ForbiddenFileInTheRecycleBin') {
return '文件已放入回收站'
} else if (resp.body.code) {
return resp.body.code as string
} else {
DebugLog.mSaveWarning('ApiFileDownloadUrl err=' + file_id + ' ' + (resp.code || ''))
}
return '网络错误'
}
static async ApiVideoPreviewUrlOpenApi(user_id: string, drive_id: string, file_id: string): Promise<IVideoPreviewUrl | undefined> {
if (!user_id || !drive_id || !file_id) return undefined
const url = 'adrive/v1.0/openFile/getVideoPreviewPlayInfo'
const postData = { drive_id: drive_id, file_id: file_id, category: 'live_transcoding', template_id: '', get_subtitle_info: true, url_expire_sec: 14400 }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.body.code == 'VideoPreviewWaitAndRetry') {
return '视频正在转码中,稍后重试'
message.warning('视频正在转码中,稍后重试')
return undefined
}
if (resp.body.code == 'ExceedCapacityForbidden') {
return '容量超限限制播放,需要扩容或者删除不必要的文件释放空间'
message.warning('容量超限, 请清理超出的容量')
return undefined
}
const data: IVideoPreviewUrl = {
drive_id: drive_id,
file_id: file_id,
size: 0,
expire_time: 0,
expire_sec: 0,
width: 0,
height: 0,
url: '',
duration: 0,
qualities: [],
urlQHD: '',
urlFHD: '',
urlHD: '',
urlSD: '',
urlLD: '',
subtitles: []
}
if (AliHttp.IsSuccess(resp.code)) {
@@ -231,41 +183,29 @@ export default class AliFile {
}
}
const taskList = resp.body.video_preview_play_info?.live_transcoding_task_list || []
const qualityMap: any = {
'LD': { label: '低清', value: '480p' },
'SD': { label: '标清', value: '540P' },
'HD': { label: '高清', value: '720P' },
'FHD': { label: '全高清', value: '1080p' },
'QHD': { label: '超高清', value: '2560p' }
}
for (let i = 0, maxi = taskList.length; i < maxi; i++) {
if (!taskList[i].url) {
continue
}
let templateId = taskList[i].template_id
if (templateId && taskList[i].status == 'finished') {
let quality = qualityMap[templateId]
data.qualities.push({
html: quality.label + ' ' + quality.value,
quality: templateId,
height: taskList[i].template_height || 0,
width: taskList[i].template_width || 0,
label: quality.label,
value: quality.value,
url: taskList[i].url
})
if (taskList[i].template_id && taskList[i].template_id == 'QHD' && taskList[i].status == 'finished') {
data.urlQHD = taskList[i].url
} else if (taskList[i].template_id && taskList[i].template_id == 'FHD' && taskList[i].status == 'finished') {
data.urlFHD = taskList[i].url
} else if (taskList[i].template_id && taskList[i].template_id == 'HD' && taskList[i].status == 'finished') {
data.urlHD = taskList[i].url
} else if (taskList[i].template_id && taskList[i].template_id == 'SD' && taskList[i].status == 'finished') {
data.urlSD = taskList[i].url
} else if (taskList[i].template_id && taskList[i].template_id == 'LD' && taskList[i].status == 'finished') {
data.urlLD = taskList[i].url
}
}
data.qualities = data.qualities.sort((a, b) => b.width - a.width)
data.url = data.urlQHD || data.urlFHD || data.urlHD || data.urlSD || data.urlLD || ''
data.duration = Math.floor(resp.body.video_preview_play_info?.meta?.duration || 0)
data.width = resp.body.video_preview_play_info?.meta?.width || 0
data.height = resp.body.video_preview_play_info?.meta?.height || 0
data.expire_time = GetExpiresTime(data.qualities[0].url)
data.expire_sec = GetOssExpires(data.url)
return data
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiVideoPreviewUrl err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiVideoPreviewUrl err=' + file_id + ' ' + (resp.code || ''))
}
return '网络错误'
return undefined
}
static async ApiListByFileInfo(user_id: string, drive_id: string, file_id: string, limit?: number): Promise<ICompilationList[] | undefined> {
@@ -287,21 +227,20 @@ export default class AliFile {
category: item.category,
drive_id: item.drive_id,
file_id: item.file_id,
file_extension: item.file_extension,
url: item.url,
expire_time: GetExpiresTime(item.url),
expire_sec: GetOssExpires(item.url),
play_cursor: Math.floor(item?.play_cursor || 0),
compilation_id: item.compilation_id
compilation_id: item.compilation_id,
})
}
return data
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiListByFileInfo err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiListByFileInfo err=' + file_id + ' ' + (resp.code || ''))
}
}
static async ApiAudioPreviewUrl(user_id: string, drive_id: string, file_id: string): Promise<IDownloadUrl | string> {
if (!user_id || !drive_id || !file_id) return '参数错误'
static async ApiAudioPreviewUrl(user_id: string, drive_id: string, file_id: string): Promise<IDownloadUrl | undefined> {
if (!user_id || !drive_id || !file_id) return undefined
const url = 'v2/file/get_audio_play_info'
@@ -309,13 +248,13 @@ export default class AliFile {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.body.code == 'AudioPreviewWaitAndRetry') {
return '音频正在转码中,稍后重试'
message.warning('音频正在转码中,稍后重试')
}
const data: IDownloadUrl = {
drive_id: drive_id,
file_id: file_id,
expire_time: 0,
expire_sec: 0,
url: '',
size: 0
}
@@ -323,28 +262,20 @@ export default class AliFile {
const template_list = resp.body.template_list || []
if (!data.url) {
for (let i = 0, maxi = template_list.length; i < maxi; i++) {
if (template_list[i].template_id
&& template_list[i].template_id == 'HQ'
&& template_list[i].status == 'finished') {
data.url = template_list[i].url
}
if (template_list[i].template_id && template_list[i].template_id == 'HQ' && template_list[i].status == 'finished') data.url = template_list[i].url
}
}
if (!data.url) {
for (let i = 0, maxi = template_list.length; i < maxi; i++) {
if (template_list[i].template_id
&& template_list[i].template_id == 'LQ'
&& template_list[i].status == 'finished') {
data.url = template_list[i].url
}
if (template_list[i].template_id && template_list[i].template_id == 'LQ' && template_list[i].status == 'finished') data.url = template_list[i].url
}
}
return data
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiAudioPreviewUrl err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiAudioPreviewUrl err=' + file_id + ' ' + (resp.code || ''))
}
return '网络错误'
return undefined
}
static async ApiOfficePreViewUrl(user_id: string, drive_id: string, file_id: string): Promise<IOfficePreViewUrl | undefined> {
@@ -362,30 +293,17 @@ export default class AliFile {
data.access_token = resp.body.access_token
data.preview_url = resp.body.preview_url
return data
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiOfficePreViewUrl err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiOfficePreViewUrl err=' + file_id + ' ' + (resp.code || ''))
}
return undefined
}
static async ApiGetFile(user_id: string, drive_id: string, file_id: string): Promise<IAliGetFileModel | undefined> {
if (!user_id || !drive_id || !file_id) return undefined
const url = 'v2/file/get'
const postData = {
drive_id: drive_id,
file_id: file_id,
url_expire_sec: 14400,
office_thumbnail_process: 'image/resize,w_400/format,jpeg',
image_thumbnail_process: 'image/resize,w_400/format,jpeg',
image_url_process: 'image/resize,w_1920/format,jpeg',
video_thumbnail_process: 'video/snapshot,t_106000,f_jpg,ar_auto,m_fast,w_400'
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return AliDirFileList.getFileInfo(user_id, resp.body as IAliFileItem, '')
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiGetFile err=' + file_id + ' ' + (resp.code || ''), resp.body)
const fileItem = await AliFile.ApiFileInfoOpenApi(user_id, drive_id, file_id);
if (fileItem) {
return AliDirFileList.getFileInfo(fileItem, '')
}
return undefined
}
@@ -399,43 +317,39 @@ export default class AliFile {
file_id: file_id
}
const resp = await AliHttp.Post(url, postData, user_id, '')
const driveType = GetDriveType(user_id, drive_id)
let items = resp.body.items
if (AliHttp.IsSuccess(resp.code) && items && items.length > 0) {
if (AliHttp.IsSuccess(resp.code) && resp.body.items && resp.body.items.length > 0) {
const list: IAliGetDirModel[] = []
list.push({
__v_skip: true,
drive_id: drive_id,
album_id: '',
file_id: driveType.key,
parent_file_id: '',
name: driveType.title,
namesearch: HanToPin(driveType.title),
size: 0,
time: 0,
description: ''
} as IAliGetDirModel)
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i]
if (item.name === 'Default' || item.name === 'resource' || item.name === 'alibum') {
continue
}
for (let i = resp.body.items.length - 1; i > 0; i--) {
const item = resp.body.items[i]
list.push({
__v_skip: true,
drive_id: item.drive_id,
album_id: '',
file_id: item.file_id,
parent_file_id: item.parent_file_id || '',
name: DecodeEncName(user_id, item).name,
name: item.name,
namesearch: HanToPin(item.name),
size: item.size || 0,
time: new Date(item.updated_at).getTime(),
description: item.description || ''
} as IAliGetDirModel)
}
list.push({
__v_skip: true,
drive_id: drive_id,
file_id: 'root',
parent_file_id: '',
name: '根目录',
namesearch: HanToPin('root'),
size: 0,
time: 0,
description: ''
} as IAliGetDirModel)
return list
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileGetPath err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiFileGetPath err=' + file_id + ' ' + (resp.code || ''))
}
return []
}
@@ -443,31 +357,23 @@ export default class AliFile {
static async ApiFileGetPathString(user_id: string, drive_id: string, file_id: string, dirsplit: string): Promise<string> {
if (!user_id || !drive_id || !file_id) return ''
if (file_id.includes('root')) {
if (file_id.startsWith('backup')) {
return '备份盘'
} else if (file_id.startsWith('resource')) {
return '资源盘'
} else if (file_id.startsWith('pic')) {
return '相册'
}
}
if (file_id == 'root') return '根目录'
const url = 'adrive/v1/file/get_path'
const postData = {
drive_id: drive_id,
file_id: file_id
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code) && resp.body.items && resp.body.items.length > 0) {
const driveType = GetDriveType(user_id, drive_id)
const list: string[] = [driveType.title]
const list: string[] = []
for (let i = resp.body.items.length - 1; i >= 0; i--) {
const item = resp.body.items[i]
list.push(DecodeEncName(user_id, item).name)
list.push(item.name)
}
return list.join(dirsplit)
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileGetPathString err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiFileGetPathString err=' + file_id + ' ' + (resp.code || ''))
}
return ''
}
@@ -485,16 +391,16 @@ export default class AliFile {
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IAliGetForderSizeModel
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileGetFolderSize err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiFileGetFolderSize err=' + file_id + ' ' + (resp.code || ''))
}
return { size: 0, folder_count: 0, file_count: 0, reach_limit: false }
}
static async ApiFileDownText(user_id: string, drive_id: string, file_id: string, filesize: number, maxsize: number, encType: string = '', password: string = ''): Promise<string> {
static async ApiFileDownText(user_id: string, drive_id: string, file_id: string, filesize: number, maxsize: number): Promise<string> {
if (!user_id || !drive_id || !file_id) return ''
const downUrl = await getRawUrl(user_id, drive_id, file_id, encType, password)
const downUrl = await AliFile.ApiFileDownloadUrlOpenApi(user_id, drive_id, file_id, 14400)
if (typeof downUrl == 'string') return downUrl
// 原始文件大小
if (filesize === -1) filesize = downUrl.size
@@ -503,8 +409,8 @@ export default class AliFile {
if (AliHttp.IsSuccess(resp.code)) {
if (typeof resp.body == 'string') return resp.body
return JSON.stringify(resp.body, undefined, 2)
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiFileDownText err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiFileDownText err=' + file_id + ' ' + (resp.code || ''))
}
return ''
}
@@ -523,12 +429,7 @@ export default class AliFile {
mtime += subtime
if (mtime > duration) break
const postData = {
body: {
drive_id: drive_id,
file_id: file_id,
url_expire_sec: 14400,
video_thumbnail_process: 'video/snapshot,t_' + mtime.toString() + '000,f_jpg,ar_auto,m_fast,w_' + imageWidth.toString()
},
body: { drive_id: drive_id, file_id: file_id, url_expire_sec: 14400, video_thumbnail_process: 'video/snapshot,t_' + mtime.toString() + '000,f_jpg,ar_auto,m_fast,w_' + imageWidth.toString() },
headers: { 'Content-Type': 'application/json' },
id: (i.toString() + file_id).substr(0, file_id.length),
method: 'POST',
@@ -572,33 +473,51 @@ export default class AliFile {
console.log(responses[i])
}
}
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiBiXueTuBatch err=' + file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiBiXueTuBatch err=' + file_id + ' ' + (resp.code || ''))
}
return imgList
}
static async ApiUpdateVideoTimeOpenApi(user_id: string, drive_id: string, file_id: string, play_cursor: number): Promise<IAliFileItem | undefined> {
if (!useSettingStore().uiAutoPlaycursorVideo) return
if (!user_id || !drive_id || !file_id) return undefined
const upateCursorUrl = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/video/updateRecord'
const postData = {
"drive_id": drive_id,
"file_id": file_id,
"play_cursor":play_cursor.toString()
}
const respvideo = await AliHttp.Post(upateCursorUrl, postData, user_id, '')
if (AliHttp.IsSuccess(respvideo.code)) {
return respvideo.body as IAliFileItem
} else {
DebugLog.mSaveWarning('ApiUpdateVideoTime2 err=' + file_id + ' ' + (respvideo.code || ''))
}
return undefined
}
static async ApiUpdateVideoTime(user_id: string, drive_id: string, file_id: string, play_cursor: number): Promise<IAliFileItem | undefined> {
if (!useSettingStore().uiAutoPlaycursorVideo) return
if (!user_id || !drive_id || !file_id) return undefined
let url = ''
let need_open_api = true
if (need_open_api) {
url = 'adrive/v1.0/openFile/video/updateRecord'
} else {
url = 'adrive/v2/video/update'
}
const resp = await AliFile.ApiFileInfoOpenApi(user_id, drive_id, file_id)
if (resp) {
const urlvideo = 'adrive/v2/video/update'
const postVideoData = {
drive_id: drive_id,
file_id: file_id,
play_cursor: Math.trunc(play_cursor).toString()
play_cursor: play_cursor.toString(),
thumbnail: resp.thumbnail || ''
}
const respvideo = await AliHttp.Post(url, postVideoData, user_id, '')
const respvideo = await AliHttp.Post(urlvideo, postVideoData, user_id, '')
if (AliHttp.IsSuccess(respvideo.code)) {
return respvideo.body as IAliFileItem
} else {
DebugLog.mSaveWarning('ApiUpdateVideoTime err=' + file_id + ' ' + (respvideo.code || ''), respvideo.body)
DebugLog.mSaveWarning('ApiUpdateVideoTime2 err=' + file_id + ' ' + (respvideo.code || ''))
}
} else {
DebugLog.mSaveWarning('ApiUpdateVideoTime err=' + file_id)
}
return undefined
}

View File

@@ -1,37 +1,42 @@
import DebugLog from '../utils/debuglog'
import message from '../utils/message'
import AliHttp from './alihttp'
import { IAliFileItem, IAliGetFileModel } from './alimodels'
import AliDirFileList from './dirfilelist'
import { ApiBatch, ApiBatchMaker, ApiBatchMaker2, ApiBatchSuccess, EncodeEncName } from './utils'
import { IDownloadUrl } from './models'
import AliFile from './file'
import message from '../utils/message'
import { ApiBatch, ApiBatchMaker, ApiBatchMaker2, ApiBatchSuccess } from './utils'
import AliFile from "./file";
import path from "path";
export default class AliFileCmd {
static async ApiCreatNewForder(
user_id: string, drive_id: string,
parent_file_id: string, creatDirName: string,
encType: string = '', check_name_mode: string = 'refuse'
): Promise<{ file_id: string; error: string }> {
static async ApiCreatNewForder(user_id: string, drive_id: string, parent_file_id: string, creatDirName: string): Promise<{ file_id: string; error: string }> {
const result = { file_id: '', error: '新建文件夹失败' }
if (!user_id || !drive_id || !parent_file_id) return result
if (parent_file_id.includes('root')) parent_file_id = 'root'
const url = 'adrive/v2/file/createWithFolders'
const name = EncodeEncName(user_id, creatDirName, true, encType)
const url = 'adrive/v1.0/openFile/create'
const pathSplitor = creatDirName.split('/');
if (pathSplitor.length > 1) {
let parentFileId = parent_file_id;
for (let i = 0; i < pathSplitor.length; i++) {
const folderName = pathSplitor[i];
const resp = await AliFileCmd.ApiCreatNewForder(user_id, drive_id, parentFileId, folderName);
parentFileId = resp.file_id
}
return { file_id:parentFileId, error: '' }
}
const postData = JSON.stringify({
drive_id: drive_id,
parent_file_id: parent_file_id,
name: name,
check_name_mode: check_name_mode,
type: 'folder',
description: encType
name: creatDirName,
check_name_mode: 'refuse',
type: 'folder'
})
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const file_id = resp.body.file_id as string | undefined
if (file_id) return { file_id, error: '' }
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiCreatNewForder err=' + parent_file_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiCreatNewForder err=' + parent_file_id + ' ' + (resp.code || ''))
}
if (resp.body?.code == 'QuotaExhausted.Drive') return { file_id: '', error: '网盘空间已满,无法创建' }
if (resp.body?.code) return { file_id: '', error: resp.body?.code }
@@ -55,12 +60,7 @@ export default class AliFileCmd {
}
static async ApiRenameBatch(user_id: string, drive_id: string, file_idList: string[], names: string[]): Promise<{
file_id: string;
parent_file_id: string;
name: string;
isDir: boolean
}[]> {
static async ApiRenameBatch(user_id: string, drive_id: string, file_idList: string[], names: string[]): Promise<{ file_id: string; parent_file_id: string; name: string; isDir: boolean }[]> {
const batchList = ApiBatchMaker2('/file/update', file_idList, names, (file_id: string, name: string) => {
return { drive_id: drive_id, file_id: file_id, name: name, check_name_mode: 'refuse' }
})
@@ -68,12 +68,7 @@ export default class AliFileCmd {
if (batchList.length == 0) return Promise.resolve([])
const successList: { file_id: string; parent_file_id: string; name: string; isDir: boolean }[] = []
const result = await ApiBatch(file_idList.length <= 1 ? '' : '批量重命名', batchList, user_id, '')
result.reslut.map((t) => successList.push({
file_id: t.file_id!,
name: t.name!,
parent_file_id: t.parent_file_id!,
isDir: t.type !== 'folder'
}))
result.reslut.map((t) => successList.push({ file_id: t.file_id!, name: t.name!, parent_file_id: t.parent_file_id!, isDir: t.type !== 'folder' }))
return successList
}
@@ -101,55 +96,16 @@ export default class AliFileCmd {
return ApiBatchSuccess(ismessage ? '从回收站还原' : '', batchList, user_id, '')
}
static async ApiFileHistoryBatch(user_id: string, drive_id: string, file_idList: string[]) {
let allTask = []
const loadingKey = 'filehistorybatch' + Date.now().toString()
message.loading('清除历史 执行中...', 60, loadingKey)
for (const file_id of file_idList) {
allTask.push(AliFile.ApiUpdateVideoTime(user_id, drive_id, file_id, 0))
if (allTask.length >= 3) {
await Promise.all(allTask).catch()
allTask = []
}
}
if (allTask.length > 0) {
await Promise.all(allTask).catch()
}
message.success('成功执行 清除历史', 1, loadingKey)
}
static async ApiFileColorBatch(user_id: string, drive_id: string, description: string, color: string, file_idList: string[]): Promise<string[]> {
// 防止加密标记清空
if (color && color != 'notEncrypt') {
let parts = description.split(',') || []
let encryptPart = parts.find((part: any) => part.includes('xbyEncrypt')) || ''
let colorPart = color || parts.find((part: any) => /c.{6}$/.test(part)) || ''
color = color ? [encryptPart, colorPart].filter(Boolean).join(',') : encryptPart
} else {
color = ''
}
let batchList = ApiBatchMaker('/file/update', file_idList, (file_id: string) => {
static async ApiFileColorBatch(user_id: string, drive_id: string, color: string, file_idList: string[]): Promise<string[]> {
const batchList = ApiBatchMaker('/file/update', file_idList, (file_id: string) => {
return { drive_id: drive_id, file_id: file_id, description: color }
})
let title = ''
if (color == '') {
title = '清除标记'
} else if (color.includes('ce74c3c')) {
title = ''
} else if (color.includes('xbyEncrypt')) {
title = '标记加密'
}
return ApiBatchSuccess(title, batchList, user_id, '')
return ApiBatchSuccess(color == '' ? '清除文件标记' : color == 'c5b89b8' ? '' : '标记文件', batchList, user_id, '')
}
static async ApiRecoverBatch(user_id: string, resumeList: {
drive_id: string;
file_id: string;
content_hash: string;
size: number;
name: string
}[]): Promise<string[] | string> {
static async ApiRecoverBatch(user_id: string, resumeList: { drive_id: string; file_id: string; content_hash: string; size: number; name: string }[]): Promise<string[]> {
const successList: string[] = []
if (!resumeList || resumeList.length == 0) return Promise.resolve(successList)
@@ -178,57 +134,54 @@ export default class AliFileCmd {
}
}
} else if (resp.code && resp.code == 403) {
if (resp.body?.code == 'UserNotVip') return '文件恢复功能需要开通阿里云盘会员'
else return resp.body?.code || '拒绝访问'
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiRecoverBatch err=' + (resp.code || ''), resp.body)
return '操作失败'
if (resp.body?.code == 'UserNotVip') message.error('文件恢复功能需要开通阿里云盘会员')
else message.error(resp.body?.code || '拒绝访问')
} else {
DebugLog.mSaveWarning('ApiRecoverBatch err=' + (resp.code || ''))
message.error('操作失败')
}
return successList
}
static async ApiMoveBatch(user_id: string, drive_id: string, file_idList: string[], to_drive_id: string, to_parent_file_id: string): Promise<string[]> {
if (to_parent_file_id.includes('root')) to_parent_file_id = 'root'
const batchList = ApiBatchMaker('/file/move', file_idList, (file_id: string) => {
if (drive_id == to_drive_id) return {
drive_id: drive_id,
file_id: file_id,
to_parent_file_id: to_parent_file_id,
auto_rename: true
}
else return {
drive_id: drive_id,
file_id: file_id,
to_drive_id: to_drive_id,
to_parent_file_id: to_parent_file_id,
auto_rename: true
}
if (drive_id == to_drive_id) return { drive_id: drive_id, file_id: file_id, to_parent_file_id: to_parent_file_id, auto_rename: true }
else return { drive_id: drive_id, file_id: file_id, to_drive_id: to_drive_id, to_parent_file_id: to_parent_file_id, auto_rename: true }
})
return ApiBatchSuccess(file_idList.length <= 1 ? '移动' : '批量移动', batchList, user_id, '')
}
static async ApiCopyBatch(user_id: string, drive_id: string, file_idList: string[], to_drive_id: string, to_parent_file_id: string): Promise<string[]> {
if (to_parent_file_id.includes('root')) to_parent_file_id = 'root'
const batchList = ApiBatchMaker('/file/copy', file_idList, (file_id: string) => {
if (drive_id == to_drive_id) return {
drive_id: drive_id,
file_id: file_id,
to_parent_file_id: to_parent_file_id,
auto_rename: true
}
else return {
drive_id: drive_id,
file_id: file_id,
to_drive_id: to_drive_id,
to_parent_file_id: to_parent_file_id,
auto_rename: true
}
if (drive_id == to_drive_id) return { drive_id: drive_id, file_id: file_id, to_parent_file_id: to_parent_file_id, auto_rename: true }
else return { drive_id: drive_id, file_id: file_id, to_drive_id: to_drive_id, to_parent_file_id: to_parent_file_id, auto_rename: true }
})
return ApiBatchSuccess(file_idList.length <= 1 ? '复制' : '批量复制', batchList, user_id, '')
}
static async ApiGetFileBatchOpenApi(user_id: string, drive_id: string, file_idList: string[]): Promise<IAliGetFileModel[]> {
const url = "adrive/v1.0/openFile/batch/get"
const file_list: { file_id: string, drive_id: string}[] = []
file_idList.map((file_id) => {
file_list.push({drive_id, file_id})
})
const postData = {
file_list: file_list
}
const successList: IAliGetFileModel[] = []
const result = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(result.code)) {
const fileItems = result.body.items as IAliFileItem[]
fileItems.forEach((fileItem) => {
successList.push(AliDirFileList.getFileInfo(fileItem, 'download_url'))
})
}
return successList
}
static async ApiGetFileBatch(user_id: string, drive_id: string, file_idList: string[]): Promise<IAliGetFileModel[]> {
const batchList = ApiBatchMaker('/file/get', file_idList, (file_id: string) => {
@@ -244,25 +197,21 @@ export default class AliFileCmd {
})
const successList: IAliGetFileModel[] = []
const result = await ApiBatch('', batchList, user_id, '')
result.reslut.map((t) => {
if (t.body) successList.push(AliDirFileList.getFileInfo(user_id, t.body as IAliFileItem, 'download_url'))
return true
})
return successList
result.reslut.map(async (t) => {
if (t.body) {
const fileItem = t.body as IAliFileItem
if (fileItem.type === 'file') {
const fileDetails = await AliFile.ApiFileInfoOpenApi(user_id, drive_id, fileItem.file_id);
fileItem.thumbnail = fileDetails?.thumbnail
fileItem.url = fileDetails?.url || fileItem.url
fileItem.download_url = fileDetails?.download_url || fileItem.download_url
fileItem.starred = fileDetails?.starred || fileItem.starred
fileItem.trashed = fileDetails?.trashed || fileItem.trashed
fileItem.deleted = fileDetails?.deleted || fileItem.deleted
fileItem.description = fileDetails?.description || fileItem.description
}
static async ApiGetFileDownloadUrlBatch(user_id: string, drive_id: string, file_idList: string[]): Promise<IDownloadUrl[]> {
const batchList = ApiBatchMaker('/file/get_download_url', file_idList, (file_id: string) => {
return {
drive_id: drive_id,
file_id: file_id,
expire_sec: 14400
successList.push(AliDirFileList.getFileInfo(t.body as IAliFileItem, 'download_url'))
}
})
const successList: IDownloadUrl[] = []
const result = await ApiBatch('', batchList, user_id, '')
result.reslut.map((t) => {
if (t.body) successList.push(t.body as IDownloadUrl)
return true
})
return successList

View File

@@ -66,24 +66,28 @@ export default function getFileIcon(category: string | undefined, ext: string |
if (';.pot.ett.'.indexOf(ext) > 0) return ['doc2', 'iconfile-doc']
if ((mimext.startsWith('.txt') || mimext.startsWith('.doc') || mimext.startsWith('.ppt')) && ';.dps.dpt.potm.potx.pps.ppsm.ppsx.ppt.pptm.pptx.'.indexOf(ext) > 0) return ['doc', 'iconfile-ppt']
if ((mimext.startsWith('.txt') || mimext.startsWith('.xls')) && ';.xls.xlsx.et.xlsm.xlt.xltm.xltx.'.indexOf(ext) > 0) return ['doc', 'iconfile-xsl']
if (mime.startsWith('text/')) return ['others', 'iconfile_txt2']
if (ext == '.json.') return ['others', 'iconfile_txt2']
if (category == 'video') {
return ['video', 'iconfile_video']
}
if (mime.startsWith('video/')) return ['video2', 'iconfile_video']
if (ext == '.ts.' && size > 5 * 1024 * 1024) return ['video2', 'iconfile_video']
if (';.3iv.cpk.divx.hdv.fli.f4v.f4p.m2t.m2ts.mts.trp.mkv.mp4.mpg4.nsv.nut.nuv.rm.rmvb.vob.wmv.mk3d.hevc.yuv.y4m.mov.avi.flv.mpg.3gp.m4v.mpeg.asf.wmz.webm.pmp.mpga'.indexOf(ext) > 0) {
return ['video2', 'iconfile_video']
}
if (ext == '.mp3.' && category == 'audio') return ['audio', 'iconfile-mp3']
if (category == 'audio' && mimext != '.unknown.') {
return ['audio', 'iconfile-audio']
}
if (mime.startsWith('audio/')) return ['audio', 'iconfile-audio']
if (';.ape.aac.cda.dsf.dtshd.eac3.m1a.m2a.m4a.mka.mpa.mpc.opus.ra.tak.tta.wma.wv.'.indexOf(ext) > 0) {
return ['audio2', 'iconfile-audio']
}
if (mime.startsWith('video/')) return ['video2', 'iconfile_video']
return ['others', 'iconwenjian']
}

View File

@@ -20,7 +20,7 @@ export default class AliFileWalk {
const orders = order.split(' ')
do {
const isGet = await AliFileWalk._ApiWalkFileListOnePage(orders[0], orders[1], dir, type)
if (!isGet) {
if (isGet != true) {
break
}
if (dir.items.length >= max && max > 0) {
@@ -32,7 +32,7 @@ export default class AliFileWalk {
}
private static async _ApiWalkFileListOnePage(orderby: string, order: string, dir: IAliFileResp, type: string = '') {
const url = 'v2/file/walk?jsonmask=next_marker%2Citems(category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
const url = 'v2/file/walk?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
let postData = {
drive_id: dir.m_drive_id,
parent_file_id: dir.dirID,
@@ -54,12 +54,12 @@ export default class AliFileWalk {
if (AliHttp.IsSuccess(resp.code)) {
dir.next_marker = resp.body.next_marker
const isRecover = dir.dirID == 'recover'
const downUrl = isRecover ? '' : 'https://api.alipan.com/v2/file/download?t=' + Date.now().toString()
const downUrl = isRecover ? '' : 'https://api.aliyundrive.com/v2/file/download?t=' + Date.now().toString()
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliFileItem
if (dir.itemsKey.has(item.file_id)) continue
const add = AliDirFileList.getFileInfo(dir.m_user_id, item, downUrl)
const add = AliDirFileList.getFileInfo(item, downUrl)
if (isRecover) add.description = item.content_hash
dir.items.push(add)
dir.itemsKey.add(item.file_id)
@@ -77,8 +77,8 @@ export default class AliFileWalk {
dir.next_marker = resp.body.code
// message.warning('列出文件出错 ' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_FileListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_FileListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_FileListOnePage ' + dir.dirID, err)

View File

@@ -2,7 +2,7 @@ import { humanTimeAgo } from '../utils/format'
import message from '../utils/message'
import DebugLog from '../utils/debuglog'
import AliHttp, { IUrlRespData } from './alihttp'
import { IAliMyFollowingModel, IAliOtherFollowingModel } from './alimodels'
import { IAliOtherFollowingModel, IAliMyFollowingModel } from './alimodels'
export interface IAliOtherFollowingResp {
items: IAliOtherFollowingModel[]
@@ -77,8 +77,8 @@ export default class AliFollowing {
dir.next_marker = resp.body.code
message.warning('列出官方推荐列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_OtherFollowingListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_OtherFollowingListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_OtherFollowingListOnePage', err)
@@ -101,7 +101,7 @@ export default class AliFollowing {
do {
const isGet = await AliFollowing.ApiMyFollowingListOnePage(dir)
if (!isGet) {
if (isGet != true) {
break
}
} while (dir.next_marker)
@@ -165,8 +165,8 @@ export default class AliFollowing {
dir.next_marker = resp.body.code
message.warning('列出订阅列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_MyFollowingListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_MyFollowingListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_MyFollowingListOnePage', err)
@@ -184,8 +184,8 @@ export default class AliFollowing {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
if (tip) message.success(isFollowing ? '订阅成功' : '取消订阅成功')
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiSetFollowing err=' + followingid + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiSetFollowing err=' + followingid + ' ' + (resp.code || ''))
message.error((isFollowing ? '订阅' : '取消订阅') + ' 操作失败,请稍后重试')
}
}
@@ -198,11 +198,11 @@ export default class AliFollowing {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiSetFollowingMarkRead err=' + followingid + ' ' + (resp.code || ''), resp.body)
}
} else {
DebugLog.mSaveWarning('ApiSetFollowingMarkRead err=' + followingid + ' ' + (resp.code || ''))
return false
}
}

View File

@@ -1,35 +1,12 @@
export interface IDownloadUrl {
drive_id: string
file_id: string
expire_time: number
expire_sec: number
url: string
size: number
}
export interface IVideoPreviewUrl {
drive_id: string
file_id: string
size: number
duration: number
expire_time: number
width: number
height: number
qualities: {
html: string
quality: string
height: number
width: number
label: string
value: string
url: string
}[]
subtitles: {
language: string
url: string
}[]
}
export interface ICompilationList {
name: string
type: string
@@ -39,14 +16,35 @@ export interface ICompilationList {
category: string
drive_id: string
file_id: string
file_extension: string
url: string
expire_time: number
expire_sec: number
play_cursor: number
compilation_id: string
}
export interface IVideoPreviewUrl {
drive_id: string
file_id: string
expire_sec: number
url: string
duration: number
width: number
height: number
urlQHD: string
urlFHD: string
urlHD: string
urlSD: string
urlLD: string
file_name?:string,
subtitles: {
language: string
url: string
}[]
}
export interface IOfficePreViewUrl {
drive_id: string
file_id: string
@@ -156,13 +154,11 @@ export interface IAliGetAlbumModel {
}
export interface IAliUserDriveDetails {
album_drive_used_size: number
backup_drive_used_size: number
default_drive_used_size: number
drive_total_size: number
drive_used_size: number
drive_total_size: number
default_drive_used_size: number
album_drive_used_size: number
note_drive_used_size: number
resource_drive_used_size: number
sbox_drive_used_size: number
share_album_drive_used_size: number
}

View File

@@ -1,256 +0,0 @@
import { b64decode, Sleep } from '../utils/format'
import { getPkgVersion } from '../utils/utils'
import axios, { AxiosResponse } from 'axios'
import { IShareSiteGroupModel, IShareSiteModel, useServerStore, useSettingStore, useUserStore } from '../store'
import ShareDAL from '../share/share/ShareDAL'
import { modalShowPost, modalUpdate } from '../utils/modal'
import { getResourcesPath } from '../utils/electronhelper'
import { existsSync, readFileSync } from 'fs'
import message from '../utils/message'
import path from 'path'
import DebugLog from '../utils/debuglog'
export interface IServerRespData {
state: string
msg: string
[k: string]: any
}
export interface IServerVerData {
version: string
verName: string
verUrl: string
verInfo: string
verHtml: string
fileExt: string
fileSize: number
}
export default class ServerHttp {
static baseApi = b64decode('aHR0cDovLzEyMS41LjE0NC44NDo1MjgyLw==')
static configUrl = b64decode('aHR0cHM6Ly9naXRlZS5jb20vemhhbm5hby9yZXNvdXJjZS9yYXcvbWFzdGVyL2ltYWdlcy9zaGFyZV9jb25maWcuanNvbg==')
static updateUrl = b64decode('aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9nYW96aGFuZ21pbi9hbGl5dW5wYW4vcmVsZWFzZXMvbGF0ZXN0')
static compareVer(version1: string, version2: string): number {
// Split version strings into arrays of numbers
const v1Parts = version1.split('.').map(Number)
const v2Parts = version2.split('.').map(Number)
// Pad the shorter version with zeros to make their lengths equal
const maxLength = Math.max(v1Parts.length, v2Parts.length)
v1Parts.push(...Array(maxLength - v1Parts.length).fill(0))
v2Parts.push(...Array(maxLength - v2Parts.length).fill(0))
// Compare each part of the version numbers
for (let i = 0; i < maxLength; i++) {
if (v1Parts[i] > v2Parts[i]) {
return 1
} else if (v1Parts[i] < v2Parts[i]) {
return -1
}
}
// Version numbers are equal
return 0
}
static compareVersions(version1: string, version2: string): number {
// Split version strings into arrays of numbers
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
// Pad the shorter version with zeros to make their lengths equal
const maxLength = Math.max(v1Parts.length, v2Parts.length);
v1Parts.push(...Array(maxLength - v1Parts.length).fill(0));
v2Parts.push(...Array(maxLength - v2Parts.length).fill(0));
// Compare each part of the version numbers
for (let i = 0; i < maxLength; i++) {
if (v1Parts[i] > v2Parts[i]) {
return 1;
} else if (v1Parts[i] < v2Parts[i]) {
return -1;
}
}
// Version numbers are equal
return 0;
}
static async Post(postData: any, isfirst = true): Promise<IServerRespData> {
const url = ServerHttp.baseApi + 'xby2'
return axios
.post(url, postData, {
responseType: 'arraybuffer',
timeout: 30000,
headers: {}
})
.then((response: AxiosResponse) => {
if (response.status != 200) return { state: 'error', msg: '网络错误' }
const buff = response.data as ArrayBuffer
const uint8array = new Uint8Array(buff)
for (let i = 0, maxi = uint8array.byteLength; i < maxi; i++) {
uint8array[i] ^= 9 + (i % 200)
}
const str = new TextDecoder().decode(uint8array)
return JSON.parse(str) as IServerRespData
})
.catch(() => {
return { state: 'error', msg: '网络错误' }
})
.then(async (resp) => {
if (resp.state == 'error' && resp.msg == '网络错误' && isfirst) {
await Sleep(2000)
return await ServerHttp.Post(postData, false)
} else return resp
})
}
static async PostToServer(postData: any): Promise<IServerRespData> {
postData.appVersion = getPkgVersion()
const str = JSON.stringify(postData)
if (window.postdataFunc) {
let enstr = ''
try {
enstr = window.postdataFunc(str)
console.log(enstr)
} catch {
return { state: 'error', msg: '联网失败' }
}
return ServerHttp.Post(enstr).catch(() => {
return { state: 'error', msg: '网络错误' }
})
} else {
return { state: 'error', msg: '程序错误' }
}
}
static async CheckConfigUpgrade(): Promise<void> {
axios
.get(ServerHttp.configUrl, {
withCredentials: false,
responseType: 'json',
timeout: 30000
})
.then(async (response: AxiosResponse) => {
console.log('CheckConfigUpgrade', response)
let GroupList: IShareSiteGroupModel[] = []
if (response.data.GroupList && response.data.GroupList.length > 0) {
const list = response.data.GroupList
for (let item of list) {
GroupList.push({ group: item.group, title: item.title })
}
} else {
GroupList = [
{ group: 'search', title: '搜索' },
{ group: 'nav', title: '导航' },
{ group: 'bbs', title: '论坛' }
]
}
ShareDAL.SaveShareSiteGroup(GroupList)
if (response.data.SSList && response.data.SSList.length > 0) {
const list: IShareSiteModel[] = []
const SSList = response.data.SSList
for (let item of SSList) {
const add: any = {
title: item.title,
url: item.url,
tip: item.tip,
group: item.group,
color: item.color
}
if (add.url.length > 0) list.push(add)
}
ShareDAL.SaveShareSite(list)
}
if (response.data.HELP && response.data.HELP.length > 0) {
useServerStore().mSaveHelpUrl(response.data.HELP)
}
if (response.data.POST && response.data.POST.length > 0) {
let postId = localStorage.getItem('postmodal')
if (!postId || postId != response.data.POST_ID) {
modalShowPost(response.data.POST, response.data.POST_ID)
}
}
}).catch((err: any) => {
DebugLog.mSaveDanger('CheckConfigUpgrade', err)
})
}
static async CheckUpgrade(showMessage: boolean = true): Promise<void> {
axios
.get(ServerHttp.updateUrl, {
withCredentials: false,
responseType: 'json',
timeout: 30000
})
.then(async (response: AxiosResponse) => {
console.log('CheckUpgrade', response)
if (!response.data || !response.data.assets || !response.data.html_url) {
showMessage && message.error('获取新版本出错')
return
}
let tagName = response.data.tag_name // 版本号
let remoteVer = tagName.replaceAll('v', '').trim()
let verHtml = response.data.html_url // 详情
let verInfo = response.data.body // 日志
let verData: IServerVerData = {
version: remoteVer,
verName: '',
verUrl: '',
verInfo: verInfo,
verHtml: verHtml,
fileExt: '',
fileSize: 0
}
let assets = response.data.assets // 文件
function isMatchingPlatformAndExtension(platform: string, fileName: string, extension: string): boolean {
return platform === window.platform && fileName.indexOf(process.arch) > 0 && fileName.endsWith(extension)
}
for (let asset of assets) {
if (asset.name.endsWith('.asar')) {
verData.fileSize = asset.size
verData.fileExt = path.extname(asset.name)
verData.verUrl = asset.browser_download_url
verData.verName = asset.name
break
}
if (isMatchingPlatformAndExtension('win32', asset.name, '.exe') ||
isMatchingPlatformAndExtension('darwin', asset.name, '.dmg')) {
verData.fileSize = asset.size
verData.fileExt = path.extname(asset.name)
verData.verUrl = asset.browser_download_url
verData.verName = asset.name
}
}
if (remoteVer) {
let configVer = getPkgVersion().replaceAll('v', '').trim()
if (window.platform !== 'linux') {
let localVersion = getResourcesPath('localVersion')
if (localVersion && existsSync(localVersion)) {
configVer = readFileSync(localVersion, 'utf-8').replaceAll('v', '').trim()
}
}
if (useSettingStore().uiUpdateProxyEnable &&
useSettingStore().uiUpdateProxyUrl.length > 0) {
verData.verUrl = useSettingStore().uiUpdateProxyUrl + '/' + verData.verUrl
}
if (this.compareVersions(remoteVer, configVer) > 0) {
// 打开更新弹窗
modalUpdate(verData)
} else if (showMessage) {
message.info('已经是最新版 ' + configVer, 6)
}
}
})
.catch((err: any) => {
showMessage && message.info('检查更新失败,请检查网络是否正常')
// DebugLog.mSaveDanger('CheckUpgrade', err)
})
}
}

View File

@@ -0,0 +1,387 @@
import { B64decode, b64decode, humanSize } from '../utils/format'
import axios, { AxiosResponse } from 'axios'
import Config from '../utils/config'
import message from '../utils/message'
import { IShareSiteModel, useServerStore } from '../store'
import { Modal, Button, Space } from '@arco-design/web-vue'
import { h } from 'vue'
import { getAppNewPath, getResourcesPath, openExternal, getUserDataPath } from '../utils/electronhelper'
import ShareDAL from '../share/share/ShareDAL'
import DebugLog from '../utils/debuglog'
import { writeFile, rmSync, existsSync, readFileSync } from 'fs'
import { execFile, SpawnOptions } from 'child_process'
import path from 'path'
const { shell } = require('electron')
export interface IServerRespData {
state: string
msg: string
[k: string]: any
}
export default class ServerHttp {
static baseApi = b64decode('aHR0cDovLzEyMS41LjE0NC44NDo1MjgyLw==')
static async PostToServer(postData: any): Promise<IServerRespData> {
postData.appVersion = Config.appVersion
const str = JSON.stringify(postData)
if (window.postdataFunc) {
let enstr = ''
try {
enstr = window.postdataFunc(str)
console.log(enstr)
} catch {
return { state: 'error', msg: '联网失败' }
}
return ServerHttp.Post(enstr).catch(() => {
return { state: 'error', msg: '网络错误' }
})
} else {
return { state: 'error', msg: '程序错误' }
}
}
static async Post(postData: any, isfirst = true): Promise<IServerRespData> {
const url = ServerHttp.baseApi + 'xby2'
return axios
.post(url, postData, {
responseType: 'arraybuffer',
timeout: 30000,
headers: {}
})
.then((response: AxiosResponse) => {
if (response.status != 200) return { state: 'error', msg: '网络错误' }
const buff = response.data as ArrayBuffer
const uint8array = new Uint8Array(buff)
for (let i = 0, maxi = uint8array.byteLength; i < maxi; i++) {
uint8array[i] ^= 9 + (i % 200)
}
const str = new TextDecoder().decode(uint8array)
return JSON.parse(str) as IServerRespData
})
.catch(() => {
return { state: 'error', msg: '网络错误' }
})
.then((resp) => {
if (resp.state == 'error' && resp.msg == '网络错误' && isfirst) {
return ServerHttp.Sleep(2000).then(() => {
return ServerHttp.Post(postData, false)
})
} else return resp
})
}
static Sleep(msTime: number) {
return new Promise((resolve) =>
setTimeout(
() =>
resolve({
success: true,
time: msTime
}),
msTime
)
)
}
static configUrl = b64decode('aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9nYW96aGFuZ21pbi9zdGF0aWNSZXNvdXJjZS9jb250ZW50cy9pbWFnZXMvc2hhcmVfY29uZmlnLmpzb24=')
static updateUrl = b64decode('aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9nYW96aGFuZ21pbi9hbGl5dW5wYW4vcmVsZWFzZXMvbGF0ZXN0')
static async CheckConfigUpgrade(): Promise<void> {
axios
.get(ServerHttp.configUrl, {
withCredentials: false,
responseType: 'json',
timeout: 30000
})
.then(async (response: AxiosResponse) => {
const content = B64decode(response.data.content)
const config = JSON.parse(content)
if (config.SIP) {
ServerHttp.baseApi = B64decode(config.SIP)
}
if (config.SSList) {
const list: IShareSiteModel[] = []
for (let i = 0, maxi = config.SSList.length; i < maxi; i++) {
const item = config.SSList[i]
const add = { title: item.title, url: item.url, tip: item.tip }
if (add.url.length > 0) list.push(add)
}
ShareDAL.SaveShareSite(list)
}
if (config.HELP) {
useServerStore().mSaveHelpUrl(response.data.HELP)
}
})
}
static async CheckUpgrade(showMessage: boolean = true): Promise<void> {
axios
.get(ServerHttp.updateUrl, {
withCredentials: false,
responseType: 'json',
timeout: 30000
})
.then(async (response: AxiosResponse) => {
console.log('CheckUpgrade', response)
if (!response.data || !response.data.assets || !response.data.html_url) {
showMessage && message.error('获取新版本出错')
return
}
let platform = process.platform
let tagName = response.data.tag_name // 版本号
let assets = response.data.assets // 文件
let html_url = response.data.html_url // 详情
let asarFileUrl = ''
let updateData = { name: '', url: '', size: 0 }
for (let asset of assets) {
const fileData = {
name: asset.name,
url: asset.browser_download_url,
size: asset.size
}
if (platform === 'win32'
&& fileData.name.indexOf(process.arch) > 0
&& fileData.name.endsWith('.exe')) {
updateData = fileData
break
} else if (platform === 'darwin'
&& fileData.name.indexOf(process.arch) > 0
&& fileData.name.endsWith('.dmg')) {
updateData = fileData
break
} else if (fileData.name.endsWith('.asar')) {
asarFileUrl = 'https://ghproxy.com/' + fileData.url
break
}
}
if (tagName) {
let configVer = Config.appVersion.replaceAll('v', '').trim()
if (process.platform !== 'linux') {
let localVersion = getResourcesPath('localVersion')
if (localVersion && existsSync(localVersion)) {
configVer = readFileSync(localVersion, 'utf-8').replaceAll('v', '').trim()
}
}
const remoteVer = tagName.replaceAll('v', '').trim()
const verInfo = this.dealText(response.data.body as string)
let verUrl = ''
if (updateData.url) {
verUrl = 'https://ghproxy.com/' + updateData.url
}
if (remoteVer !== configVer) {
Modal.confirm({
mask: true,
alignCenter: true,
title: () => h('div', {
innerHTML: `有新版本<span class='vertip'>${tagName}</span>`,
class: { vermodalhead: true },
style: { maxWidth: '540px' }
}),
content: () => h('div', {
innerHTML: ' <div style="display: flex; justify-content: center; align-items: center;">\n' +
' <img src="./images/qrcode_258.jpg" alt="公众号">\n' +
' </div>\n' +
' <p style="text-indent: 40px; color: red;">Github需要代理如果下载失败请关注公众号从网盘下载</p>' + verInfo,
class: { vermodal: true }
}),
onClose: () => {
if (updateData.name) {
let resourcesPath = getResourcesPath(updateData.name)
if (existsSync(resourcesPath)) {
rmSync(resourcesPath, { force: true })
}
}
return true
},
footer: () => h(Space, {}, () => [
h(Button, {
innerHTML: '取消',
onClick: async () => {
if (updateData.name) {
let resourcesPath = getResourcesPath(updateData.name)
if (existsSync(resourcesPath)) {
rmSync(resourcesPath, { force: true })
}
}
try {
// @ts-ignore
document.querySelector('.arco-overlay-modal').remove()
} catch (err) {
}
return true
}
}),
h(Button, {
type: 'outline',
style: asarFileUrl.length == 0 ? '' : 'display: none',
innerHTML: platform !== 'linux' && verUrl.length > 0 ? '全量更新' : '详情',
onClick: async () => {
if (verUrl.length > 0 && platform !== 'linux') {
// 下载安装
const msgKey = 'download_' + Date.now().toString()
await this.AutoDownload(verUrl, html_url, updateData.name, false, msgKey)
} else {
openExternal(html_url)
}
return true
}
}),
h(Button, {
type: 'primary',
style: asarFileUrl.length > 0 && platform !== 'linux' ? '' : 'display: none',
innerHTML: '热更新',
onClick: async () => {
if (asarFileUrl.length > 0 && platform !== 'linux') {
// 下载安装
const msgKey = 'download_' + Date.now().toString()
const flag = await this.AutoDownload(asarFileUrl, html_url, updateData.name, true, msgKey)
// 更新本地版本号
if (flag && tagName) {
const localVersion = getResourcesPath('localVersion')
if (localVersion) {
writeFile(localVersion, tagName, async (err)=> {
if (err) {
return false
} else {
message.info('热更新完毕,自动重启应用中...', 0, msgKey)
await this.Sleep(2000)
window.WebRelaunch()
return true
}
})
}
}
}
return false
}
})
])
})
} else if (showMessage && remoteVer <= configVer) {
message.info('已经是最新版 ' + tagName, 6)
}
}
})
.catch((err: any) => {
showMessage && message.info('检查更新失败,请检查网络是否正常')
DebugLog.mSaveDanger('CheckUpgrade', err)
})
}
static dealText(context: string): string {
let splitTextArr = context.trim().split(/\r\n/g)
let resultTextArr: string[] = []
splitTextArr.forEach((item, i) => {
let links = item.match(/!?\[.+?\]\(https?:\/\/.+\)/g)
// 处理链接
if (links != null) {
for (let index = 0; index < links.length; index++) {
const text_link = links[index].match(/[^!\[\(\]\)]+/g)//提取文字和链接
if (text_link) {
if (links[index][0] == '!') { //解析图片
item = item.replace(links[index], '<img src="' + text_link[1] + '" loading="lazy" alt="' + text_link[0] + '" />')
} else { //解析超链接
item = item.replace(links[index], `<i>【${text_link[0]}】</i>`)
}
}
}
}
if (item.indexOf('- ')) { // 无序列表
item = item.replace(/.*-\s+(.*)/g, '<strong>$1</strong>')
}
if (item.indexOf('* ')) { // 无序列表
item = item.replace(/.*\*\s+(.*)/g, '<strong>$1</strong>')
}
if (item.includes('**')) {
item = item.replaceAll(/\*\*/g, '')
}
if (item.startsWith('# ')) { // 1 级标题h1
resultTextArr.push(`<h1>${item.replace('# ', '')}</h1>`)
} else if (item.startsWith('## ')) { // 2 级标题h2
resultTextArr.push(`<h2>${item.replace('## ', '')}</h2>`)
} else if (item.startsWith('### ')) { // 3 级标题h3
resultTextArr.push(`<h3>${item.replace('### ', '')}</h3>`)
} else if (item.indexOf('---') == 0) {
resultTextArr.push(item.replace('---', '<hr>'))
} else { // 普通的段落
resultTextArr.push(`${item}`)
}
})
return resultTextArr.join('<br>')
}
static async AutoDownload(appNewUrl: string, html_url: string, file_name: string, hot: boolean, msgKey: string): Promise<boolean> {
const resourcesPath = hot ? getAppNewPath() : getUserDataPath(file_name)
if (!hot && existsSync(resourcesPath)) {
await this.autoInstallNewVersion(resourcesPath, msgKey)
return true
}
message.loading('新版本正在后台下载中,请耐心等待。。。', 0, msgKey)
return axios
.get(appNewUrl, {
withCredentials: false,
responseType: 'arraybuffer',
timeout: 60000,
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
Expires: '0'
}
})
.then(async (response: AxiosResponse) => {
writeFile(resourcesPath, Buffer.from(response.data), (err) => {
if(err) {
message.error('下载更新失败请检查【Resources文件夹】是否有写入权限',5, msgKey)
return false
}
})
if (!hot) {
await this.Sleep(2000)
await this.autoInstallNewVersion(resourcesPath, msgKey)
}
return true
})
.catch(() => {
message.error('新版本下载失败,关注公众号从网盘下载', 5)
rmSync(resourcesPath, { force: true })
openExternal(html_url)
return false
})
}
static async autoInstallNewVersion(resourcesPath: string, msgKey: string) {
// 自动安装
const options: SpawnOptions = { shell: true, windowsVerbatimArguments: true }
if (process.platform === 'win32') {
execFile('\"' + resourcesPath + '\"', options, error => {
if(error) {
message.info('安装失败,请前往文件夹手动安装', 5, msgKey)
const resources = getResourcesPath('')
shell.openPath(path.join(resources, '/'))
} else {
message.info('安装成功,请重新打开', 5, msgKey)
window.WebToElectron({ cmd: 'exit' })
}
})
} else if (process.platform === 'darwin') {
execFile('open ' + '\"' + resourcesPath + '\"', options, error => {
if(error) {
message.info('安装失败,请前往文件夹手动安装', 5, msgKey)
const resources = getResourcesPath('')
shell.openPath(path.join(resources, '/'))
} else {
message.info('请手动移动到应用程序目录,完成安装', 5, msgKey)
window.WebToElectron({ cmd: 'exit' })
}
})
}
}
}

View File

@@ -1,11 +1,11 @@
import DebugLog from '../utils/debuglog'
import { humanDateTime, humanDateTimeDateStr, humanExpiration, humanSize } from '../utils/format'
import { humanDateTime, humanExpiration, humanSize } from '../utils/format'
import message from '../utils/message'
import AliHttp, { IUrlRespData } from './alihttp'
import ServerHttp from './server'
import { ApiBatch, ApiBatchMaker, ApiBatchSuccess } from './utils'
import { useSettingStore } from '../store'
import { IAliFileItem, IAliShareAnonymous, IAliShareBottleFish, IAliShareFileItem, IAliShareItem } from './alimodels'
import { IAliShareItem, IAliShareAnonymous, IAliShareFileItem } from './alimodels'
import getFileIcon from './fileicon'
import { IAliBatchResult } from './models'
@@ -31,20 +31,10 @@ export interface UpdateShareModel {
export default class AliShare {
static async ApiShareFileCheckAvailable(user_id: string, drive_id: string, file_id_list: string[]) {
if (!user_id || !drive_id || !file_id_list) return []
const url = 'adrive/v2/share_link/check_available'
const postData = { drive_id, file_id_list }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body.invalid_items as IAliFileItem[]
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiShareFileCheckAvailable err=' + (resp.code || ''))
}
return []
}
static async ApiGetShareAnonymous(share_id: string): Promise<IAliShareAnonymous> {
const share: IAliShareAnonymous = {
shareinfo: {
share_id: share_id,
@@ -65,7 +55,7 @@ export default class AliShare {
error: '解析分享链接失败'
}
if (!share_id) return share
const url = 'adrive/v3/share_link/get_share_by_anonymous?share_id=' + share_id
const url = 'adrive/v2/share_link/get_share_by_anonymous?share_id=' + share_id
const postData = { share_id: share_id }
const resp = await AliHttp.Post(url, postData, '', '')
if (AliHttp.IsSuccess(resp.code)) {
@@ -87,12 +77,11 @@ export default class AliShare {
share.error = ''
return share
}
} else if (resp.code != 429 && !AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiGetShareAnonymous err=' + share_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiGetShareAnonymous err=' + share_id + ' ' + (resp.code || ''))
}
if (resp.body?.code == 'TooManyRequests') share.error = '429'
else if (resp.body?.code == 'ShareLink.Cancelled') share.error = '分享链接被取消分享了'
if (resp.body?.code == 'ShareLink.Cancelled') share.error = '分享链接被取消分享了'
else if (resp.body?.code == 'ShareLink.Expired') share.error = '分享链接过期失效了'
else if (resp.body?.code == 'ShareLink.Forbidden') share.error = '分享链接违规禁止访问'
else if (resp.body?.code) share.error = resp.body.code
@@ -108,14 +97,16 @@ export default class AliShare {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApisSubscription err=' + share_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApisSubscription err=' + share_id + ' ' + (resp.code || ''))
}
return false
}
static async ApiGetShareToken(share_id: string, pwd: string): Promise<string> {
if (!share_id) return ',分享链接错误'
const url = 'v2/share_link/get_share_token'
const postData = { share_id: share_id, share_pwd: pwd }
@@ -133,21 +124,20 @@ export default class AliShare {
}
}
}
if (resp.body?.code == 'InvalidResource.SharePwd') return ',提取码错误'
if (resp.body?.code == 'ShareLink.Cancelled') return ',分享链接被取消分享了'
if (resp.body?.code == 'ShareLink.Expired') return ',分享链接过期失效了'
if (resp.body?.code == 'ShareLink.Forbidden') return ',分享链接违规禁止访问'
if (resp.body?.code) return '' + resp.body.code
if (AliHttp.IsSuccess(resp.code)) {
if (useSettingStore().yinsiLinkPassword && !isgetpwd) ServerHttp.PostToServer({
cmd: 'PostAliShare',
shareid: share_id,
password: postData.share_pwd
})
if (useSettingStore().yinsiLinkPassword && isgetpwd == false) ServerHttp.PostToServer({ cmd: 'PostAliShare', shareid: share_id, password: postData.share_pwd })
return (resp.body.share_token as string | undefined) || 'share_token错误'
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiGetShareToken err=' + share_id + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiGetShareToken err=' + share_id + ' ' + (resp.code || ''))
}
return ',网络错误请重试'
}
@@ -166,7 +156,7 @@ export default class AliShare {
}
do {
const isGet = await AliShare.ApiShareFileListOnePage(dir, share_token)
if (!isGet) {
if (isGet != true) {
break
}
} while (dir.next_marker)
@@ -177,7 +167,7 @@ export default class AliShare {
static async ApiShareFileListOnePage(dir: IAliShareFileResp, share_token: string): Promise<boolean> {
const url =
'adrive/v3/file/list?jsonmask=next_marker%2Citems(category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
'adrive/v3/file/list?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
let postData = {
share_id: dir.m_share_id,
parent_file_id: dir.dirID,
@@ -205,6 +195,7 @@ export default class AliShare {
name: item.name,
type: item.type,
parent_file_id: item.parent_file_id,
file_extension: item.file_extension || '',
mime_extension: item.mime_extension || '',
mime_type: item.mime_type || '',
@@ -212,9 +203,6 @@ export default class AliShare {
category: item.category || '',
punish_flag: item.punish_flag || 0,
isDir: item.type == 'folder',
created_at: item.created_at,
updated_at: item.updated_at,
timeStr: humanDateTimeDateStr(item.updated_at || item.created_at),
sizeStr: item.type == 'folder' ? '' : humanSize(item.size),
icon: getFileIcon(item.category, item.file_extension, item.mime_extension, item.mime_type, item.size)[1]
}
@@ -233,8 +221,8 @@ export default class AliShare {
dir.next_marker = resp.body.code
message.warning('列出分享链接内文件出错 ' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ShareFileListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ShareFileListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ShareFileListOnePage ' + dir.dirID, err)
@@ -247,8 +235,9 @@ export default class AliShare {
static async ApiCreatShare(user_id: string, drive_id: string, expiration: string, share_pwd: string, share_name: string, file_id_list: string[]): Promise<string | IAliShareItem> {
if (!user_id || !drive_id || file_id_list.length == 0) return '创建分享链接失败数据错误'
const url = 'adrive/v2/share_link/create'
const postData = { drive_id, expiration, share_pwd, share_name, file_id_list }
const postData = JSON.stringify({ drive_id, expiration, share_pwd: share_pwd, share_name: share_name, file_id_list })
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const item = resp.body as IAliShareItem
const add: IAliShareItem = Object.assign({}, item, { first_file: undefined, icon: 'iconwenjian' })
@@ -256,15 +245,14 @@ export default class AliShare {
if (item.updated_at) add.updated_at = humanDateTime(item.updated_at)
add.share_msg = humanExpiration(item.expiration)
return add
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiCreatShare err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiCreatShare err=' + (resp.code || ''))
}
if (resp.body?.code.startsWith('UserPunished')) return '账号分享行为异常,无法分享'
else if (resp.body?.code == 'InvalidParameter.FileIdList') return '选择文件过多,无法分享'
else if (resp.body?.message && resp.body.message.indexOf('size of file_id_list') >= 0) return '选择文件过多,无法分享'
else if (resp.body?.code == 'FileShareNotAllowed') return '这个文件禁止分享'
else if (resp.body?.code == 'SharelinkCreateExceedDailyLimit') return '今日分享次数过多,请明天再试'
else if (resp.body?.code == 'FeatureTemporaryDisabled') return '分享功能维护中'
else if (resp.body?.code) return resp.body.code.toString()
else return '创建分享链接失败'
@@ -289,7 +277,8 @@ export default class AliShare {
}
batchList.push(JSON.stringify(postData))
}
return await ApiBatch('', batchList, user_id, '')
const result = await ApiBatch('', batchList, user_id, '')
return result
}
@@ -308,12 +297,7 @@ export default class AliShare {
for (let i = 0, maxi = share_idList.length; i < maxi; i++) {
batchList.push(
JSON.stringify({
body: {
share_id: share_idList[i],
share_pwd: share_pwdList[i],
expiration: expirationList[i],
share_name: share_nameList[i]
},
body: { share_id: share_idList[i], share_pwd: share_pwdList[i], expiration: expirationList[i], share_name: share_nameList[i] },
headers: { 'Content-Type': 'application/json' },
id: share_idList[i],
method: 'POST',
@@ -323,36 +307,25 @@ export default class AliShare {
}
} else {
for (let i = 0, maxi = share_idList.length; i < maxi; i++) {
batchList.push(JSON.stringify({
body: {
share_id: share_idList[i],
share_pwd: share_pwdList[i],
expiration: expirationList[i]
},
headers: { 'Content-Type': 'application/json' },
id: share_idList[i],
method: 'POST',
url: '/share_link/update'
}))
batchList.push(JSON.stringify({ body: { share_id: share_idList[i], share_pwd: share_pwdList[i], expiration: expirationList[i] }, headers: { 'Content-Type': 'application/json' }, id: share_idList[i], method: 'POST', url: '/share_link/update' }))
}
}
const successList: UpdateShareModel[] = []
const result = await ApiBatch(share_idList.length > 1 ? '批量更新分享链接' : '更新分享链接', batchList, user_id, '')
result.reslut.map((t) => successList.push({
share_id: t.share_id!,
share_pwd: t.share_pwd!,
expiration: t.expiration!,
share_name: t.share_name!
} as UpdateShareModel))
result.reslut.map((t) => successList.push({ share_id: t.share_id!, share_pwd: t.share_pwd!, expiration: t.expiration!, share_name: t.share_name! } as UpdateShareModel))
return successList
}
static async ApiSaveShareFilesBatch(share_id: string, share_token: string, user_id: string, drive_id: string, parent_file_id: string, file_idList: string[]): Promise<string> {
if (!share_id || !share_token || !user_id || !drive_id || !parent_file_id) return 'error'
if (!file_idList || file_idList.length == 0) return 'success'
if (parent_file_id.includes('root')) parent_file_id = 'root'
const batchList: string[] = []
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
const postData =
@@ -382,18 +355,6 @@ export default class AliShare {
}
return 'error'
}
}
static async ApiShareBottleFish(user_id: string) {
if (!user_id) return '获取好运瓶失败'
const url = 'adrive/v1/bottle/fish'
const postData = {}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IAliShareBottleFish
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
return resp.body.display_message || '获取好运瓶失败'
}
return '获取好运瓶失败'
}
}

View File

@@ -1,10 +1,9 @@
import DebugLog from '../utils/debuglog'
import { humanDateTime, humanExpiration } from '../utils/format'
import { humanDateTime, humanExpiration, Sleep } from '../utils/format'
import message from '../utils/message'
import AliHttp, { IUrlRespData } from './alihttp'
import { IAliShareBottleFishItem, IAliShareItem, IAliShareRecentItem } from './alimodels'
import { IAliShareItem } from './alimodels'
import AliDirFileList from './dirfilelist'
import { useSettingStore } from '../store'
export interface IAliShareResp {
items: IAliShareItem[]
@@ -14,26 +13,6 @@ export interface IAliShareResp {
m_time: number
m_user_id: string
}
export interface IAliShareRecentResp {
items: IAliShareRecentItem[]
itemsKey: Set<string>
next_marker: string
m_time: number
m_user_id: string
}
export interface IAliShareBottleFishResp {
items: IAliShareBottleFishItem[]
itemsKey: Set<string>
next_marker: string
m_time: number
m_user_id: string
}
export default class AliShareList {
static async ApiShareListAll(user_id: string): Promise<IAliShareResp> {
@@ -57,6 +36,7 @@ export default class AliShareList {
static async ApiShareListOnePage(dir: IAliShareResp): Promise<boolean> {
const url = 'adrive/v3/share_link/list'
const postData = {
marker: dir.next_marker,
creator: dir.m_user_id,
include_canceled: false,
@@ -71,7 +51,7 @@ export default class AliShareList {
try {
if (AliHttp.IsSuccess(resp.code)) {
dir.next_marker = resp.body.next_marker
const downUrl = 'https://api.alipan.com/v2/file/download?t=' + Date.now().toString()
const downUrl = 'https://api.aliyundrive.com/v2/file/download?t=' + Date.now().toString()
const timeNow = new Date().getTime()
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareItem
@@ -79,7 +59,7 @@ export default class AliShareList {
let icon = 'iconwenjian'
let first_file
if (item.first_file) {
first_file = AliDirFileList.getFileInfo(dir.m_user_id, item.first_file, downUrl)
first_file = AliDirFileList.getFileInfo(item.first_file, downUrl)
icon = first_file.icon || 'iconwenjian'
}
const add = Object.assign({}, item, { first_file, icon }) as IAliShareItem
@@ -95,6 +75,7 @@ export default class AliShareList {
} else {
add.created_at = ''
}
add.share_msg = humanExpiration(item.expiration, timeNow)
if (item.status == 'forbidden') add.share_msg = '分享违规'
dir.items.push(add)
@@ -103,6 +84,7 @@ export default class AliShareList {
return true
} else if (resp.code == 404) {
dir.items.length = 0
dir.next_marker = ''
return true
@@ -111,8 +93,8 @@ export default class AliShareList {
dir.next_marker = resp.body.code
message.warning('列出分享列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ShareListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ShareListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_ShareListOnePage', err)
@@ -121,161 +103,28 @@ export default class AliShareList {
return false
}
static async ApiShareRecentListAll(user_id: string): Promise<IAliShareRecentResp> {
const dir: IAliShareRecentResp = {
items: [],
itemsKey: new Set(),
next_marker: '',
m_time: 0,
m_user_id: user_id
}
let max: number = useSettingStore().debugFavorListMax
do {
const isGet = await AliShareList.ApiShareRecentListOnePage(dir)
if (!isGet) {
break
}
if (dir.items.length >= max && max > 0) {
dir.next_marker = ''
break
}
} while (dir.next_marker)
return dir
}
static async ApiShareBottleFishListAll(user_id: string): Promise<IAliShareBottleFishResp> {
const dir: IAliShareBottleFishResp = {
items: [],
itemsKey: new Set(),
next_marker: '',
m_time: 0,
m_user_id: user_id
}
do {
const isGet = await AliShareList.ApiShareBottleFishListOnePage(dir)
if (!isGet) {
break
}
} while (dir.next_marker)
return dir
}
static async ApiShareRecentListOnePage(dir: IAliShareRecentResp): Promise<boolean> {
const url = 'adrive/v2/share_link/recent_copy_list'
const postData = { limit: 50, marker: dir.next_marker, order_by: 'gmt_modified DESC' /* 更新时间 降序 */ }
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliShareList._ShareRecentListOnePage(dir, resp)
}
static async ApiShareBottleFishListOnePage(dir: IAliShareBottleFishResp): Promise<boolean> {
const url = 'adrive/v1/bottle/list'
const postData = { limit: 100 }
const resp = await AliHttp.Post(url, postData, dir.m_user_id, '')
return AliShareList._ShareBottleFishListOnePage(dir, resp)
}
static _ShareRecentListOnePage(dir: IAliShareRecentResp, resp: IUrlRespData): boolean {
try {
if (AliHttp.IsSuccess(resp.code)) {
dir.next_marker = resp.body.next_marker
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareRecentItem
const add = Object.assign({}, item) as IAliShareRecentItem
if (dir.itemsKey.has(item.share_id)) continue
if (!add.share_msg) add.share_msg = ''
if (!add.share_name) add.share_name = 'share_name'
if (!add.preview_count) add.preview_count = 0
if (!add.file_count) add.file_count = 0
if (!add.save_count) add.save_count = 0
if (!add.browse_count) add.browse_count = 0
if (add.gmt_created) {
add.gmt_created = humanDateTime(new Date(add.gmt_created).getTime())
} else {
add.gmt_created = ''
}
if (add.gmt_modified) {
add.gmt_modified = humanDateTime(new Date(add.gmt_modified).getTime())
} else {
add.gmt_modified = ''
}
if (add.status == 'forbidden') add.share_msg = '分享违规'
dir.items.push(add)
dir.itemsKey.add(add.share_id)
}
return true
} else if (resp.code == 404) {
dir.items.length = 0
dir.next_marker = ''
return true
} else if (resp.body && resp.body.code) {
dir.items.length = 0
dir.next_marker = resp.body.code
message.warning('列出历史分享列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ShareRecentListOnePage err=' + (resp.code || ''), resp.body)
}
} catch (err: any) {
DebugLog.mSaveDanger('_ShareRecentListOnePage', err)
}
dir.next_marker = 'error ' + resp.code
return false
}
static _ShareBottleFishListOnePage(dir: IAliShareBottleFishResp, resp: IUrlRespData): boolean {
try {
if (AliHttp.IsSuccess(resp.code)) {
dir.next_marker = ''
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareBottleFishItem
const add = Object.assign({}, item) as IAliShareBottleFishItem
if (dir.itemsKey.has(item.bottleId)) continue
if (!add.share_name) add.share_name = 'share_name'
if (add.gmtCreate) {
add.gmt_created = humanDateTime(new Date(add.gmtCreate).getTime())
} else {
add.gmt_created = ''
}
if (add.saved) add.saved_msg = '已保存'
else add.saved_msg = '未保存'
dir.items.push(add)
dir.itemsKey.add(add.bottleId)
}
return true
} else if (resp.code == 404) {
dir.items.length = 0
dir.next_marker = ''
return true
} else if (resp.body && resp.body.code) {
dir.items.length = 0
dir.next_marker = resp.body.code
message.warning('列出好运瓶领取记录列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ShareBottleFishListOnePage err=' + (resp.code || ''), resp.body)
}
} catch (err: any) {
DebugLog.mSaveDanger('_ShareBottleFishListOnePage', err)
}
dir.next_marker = 'error ' + resp.code
return false
}
static async ApiShareListUntilShareID(user_id: string, share_id: string): Promise<boolean> {
const url = 'adrive/v3/share_link/list'
const postData = {
marker: '',
creator: user_id,
include_canceled: false,
order_by: 'created_at',
order_direction: 'DESC'
}
for (let j = 0; j < 10; j++) {
const resp = await AliHttp.Post(url, postData, user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareItem
if (item.share_id == share_id) return true
}
}
} catch {}
await Sleep(500)
}
return false
}
}

View File

@@ -1,44 +0,0 @@
import DebugLog from '../utils/debuglog'
import { humanExpiration } from '../utils/format'
import AliHttp from './alihttp'
import { IAliShareItem } from './alimodels'
export default class AliTransferShare {
static async ApiCreatTransferShare(user_id: string, drive_id: string, file_id_list: string[]): Promise<string | IAliShareItem> {
if (!user_id || !drive_id || file_id_list.length == 0) return '快传分享链接失败数据错误'
const drive_file_list = []
for (let i = 0, maxi = file_id_list.length; i < maxi; i++) {
drive_file_list.push({ drive_id, file_id: file_id_list[i] })
}
const url = 'adrive/v1/share/create'
const postData = JSON.stringify({ drive_file_list })
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
const item = resp.body as IAliShareItem
const add: IAliShareItem = Object.assign({}, item, { first_file: undefined, icon: 'iconwenjian' })
add.share_msg = humanExpiration(item.expiration)
return add
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiCreatShare err=' + (resp.code || ''), resp.body)
}
if (resp.body?.code.startsWith('UserPunished')) return '账号分享行为异常,无法分享'
else if (resp.body?.code == 'InvalidParameter.FileIdList') return '选择文件过多,无法分享'
else if (resp.body?.code == 'CreateShareCountExceed') return '今日快传已达上限,请明天再来'
else if (resp.body?.message && resp.body.message.indexOf('size of file_id_list') >= 0) return '选择文件过多,无法分享'
else if (resp.body?.code) return resp.body.code.toString()
else return '创建快传链接失败'
}
static async ApiCancelTransferShareBatch(user_id: string, share_idList: string[]): Promise<any> {
const url = 'adrive/v1/share/cancel'
const success: string[] = []
for (let share_id of share_idList) {
let resp = await AliHttp.Post(url, { share_id }, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
success.push(share_id)
}
}
return success
}
}

View File

@@ -1,119 +0,0 @@
import DebugLog from '../utils/debuglog'
import { humanDateTime, humanExpiration } from '../utils/format'
import message from '../utils/message'
import AliHttp, { IUrlRespData } from './alihttp'
import { IAliShareItem } from './alimodels'
export interface IAliShareResp {
items: IAliShareItem[]
itemsKey: Set<string>
next_marker: string
m_time: number
m_user_id: string
}
export interface IAliShareResp {
items: IAliShareItem[]
itemsKey: Set<string>
next_marker: string
m_time: number
m_user_id: string
}
export default class AliTransferShareList {
static async ApiTransferShareListAll(user_id: string): Promise<IAliShareResp> {
const dir: IAliShareResp = {
items: [],
itemsKey: new Set(),
next_marker: '',
m_time: 0,
m_user_id: user_id
}
await AliTransferShareList.ApiShareListOnePage(user_id, dir)
return dir
}
static async ApiShareListOnePage(user_id: string, dir: IAliShareResp): Promise<boolean> {
const url = 'adrive/v1/share/list'
const postData = {
limit: 20,
order_by: 'created_at',
order_direction: 'DESC'
}
const resp = await AliHttp.Post(url, postData,user_id,'')
return await AliTransferShareList._TransferShareListOnePage(user_id, dir, resp)
}
static async _TransferShareListOnePage(user_id: string, dir: IAliShareResp, resp: IUrlRespData): Promise<boolean> {
try {
if (AliHttp.IsSuccess(resp.code)) {
const timeNow = new Date().getTime()
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareItem
if (dir.itemsKey.has(item.share_id)) continue
let icon = 'iconwenjian'
let first_file: any = item.share_id && await AliTransferShareList.ApiTransferShareFileStatus(user_id, item.share_id)
const add = Object.assign({}, item, { first_file, icon }) as IAliShareItem
if (!add.full_share_msg) add.full_share_msg = ''
if (!add.share_msg) add.share_msg = ''
if (!add.share_name) add.share_name = 'share_name'
if (!add.is_share_saved) add.share_saved = '未保存'
else add.share_saved = '已保存'
if (!add.expired) add.expired = false
if (item.created_at) {
add.created_at = humanDateTime(new Date(item.created_at).getTime())
} else {
add.created_at = ''
}
add.share_msg = humanExpiration(item.expiration, timeNow)
if (item.status == 'forbidden') add.share_msg = '分享违规'
dir.items.push(add)
dir.itemsKey.add(add.share_id)
}
return true
} else if (resp.code == 404) {
dir.items.length = 0
return true
} else if (resp.body && resp.body.code) {
dir.items.length = 0
message.warning('列出分享列表出错' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ShareListOnePage err=' + (resp.code || ''), resp.body)
}
} catch (err: any) {
DebugLog.mSaveDanger('_ShareListOnePage', err)
}
return false
}
static async ApiTransferShareFileStatus(user_id: string, share_id: string): Promise<any> {
const url = 'adrive/v1/share/get'
const postData = { share_id }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body as IAliShareItem
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiTransferShareFileStatus err=' + (resp.code || ''), resp.body)
}
return false
}
static async ApiTransferShareListUntilShareID(user_id: string, share_id: string, limit: number): Promise<boolean> {
const url = 'adrive/v1/share/list'
const postData = {
limit: limit || 20,
order_by: 'created_at',
order_direction: 'DESC'
}
const resp = await AliHttp.Post(url, postData, user_id, '')
try {
if (AliHttp.IsSuccess(resp.code)) {
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliShareItem
if (item.share_id == share_id) return true
}
}
} catch {}
return false
}
}

View File

@@ -8,7 +8,7 @@ export default class AliTrash {
static async ApiTrashFileListOnePageForClean(orderby: string, order: string, dir: IAliFileResp): Promise<boolean> {
const url =
'v2/recyclebin/list?jsonmask=next_marker%2Citems(category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
'v2/recyclebin/list?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
const postData = {
drive_id: dir.m_drive_id,
marker: dir.next_marker,
@@ -26,7 +26,7 @@ export default class AliTrash {
static async ApiFavorFileListOnePageForClean(orderby: string, order: string, dir: IAliFileResp): Promise<boolean> {
const url =
'v2/file/list_by_custom_index_key?jsonmask=next_marker%2Citems(category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
'v2/file/list_by_custom_index_key?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
const postData = {
drive_id: dir.m_drive_id,
marker: dir.next_marker,
@@ -59,7 +59,7 @@ export default class AliTrash {
const orders = order.split(' ')
do {
const isGet = await AliTrash._ApiDirFileListOnePage(orders[0], orders[1], dir, type)
if (!isGet) {
if (isGet != true) {
break
}
if (dir.items.length >= max && max > 0) {
@@ -72,10 +72,10 @@ export default class AliTrash {
static async _ApiDirFileListOnePage(orderby: string, order: string, dir: IAliFileResp, type: string = ''): Promise<boolean> {
const url =
'adrive/v3/file/list?jsonmask=next_marker%2Citems(category%2Ccreated_at%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
'adrive/v3/file/list?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)'
let postData = {
drive_id: dir.m_drive_id,
parent_file_id: dir.dirID.includes('root') ? 'root': dir.dirID,
parent_file_id: dir.dirID,
marker: dir.next_marker,
limit: 100,
all: false,
@@ -94,12 +94,12 @@ export default class AliTrash {
if (AliHttp.IsSuccess(resp.code)) {
dir.next_marker = resp.body.next_marker
const isrecover = dir.dirID == 'recover'
const downurl = isrecover ? '' : 'https://api.alipan.com/v2/file/download?t=' + Date.now().toString()
const downurl = isrecover ? '' : 'https://api.aliyundrive.com/v2/file/download?t=' + Date.now().toString()
for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) {
const item = resp.body.items[i] as IAliFileItem
if (dir.itemsKey.has(item.file_id)) continue
const add = AliDirFileList.getFileInfo(dir.m_user_id, item, downurl)
const add = AliDirFileList.getFileInfo(item, downurl)
if (isrecover) add.description = item.content_hash
dir.items.push(add)
dir.itemsKey.add(item.file_id)
@@ -108,6 +108,7 @@ export default class AliTrash {
return true
} else if (resp.code == 404) {
dir.items.length = 0
dir.next_marker = ''
return true
@@ -116,8 +117,8 @@ export default class AliTrash {
dir.next_marker = resp.body.code
message.warning('列出文件出错 ' + resp.body.code, 2)
return false
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_FileListOnePage err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_FileListOnePage err=' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('_FileListOnePage ' + dir.dirID, err)
@@ -140,7 +141,8 @@ export default class AliTrash {
order_direction: order
}
const resp = await AliHttp.Post(url, postdata, dir.m_user_id, '')
// todo:: 这里不完善
//todo:: 这里不完善
return AliDirFileList._FileListOnePage(orderby, order, dir, resp, -1)
//return Promise.resolve(false)
}
}

View File

@@ -1,14 +1,10 @@
import DebugLog from '../utils/debuglog'
import AliHttp from './alihttp'
import { IUploadCreat, IUploadInfo } from './models'
import { EncodeEncName } from './utils'
export default class AliUpload {
static async UploadCreatFileWithPreHash(
user_id: string, drive_id: string, parent_file_id: string,
filename: string, fileSize: number,
prehash: string, check_name_mode: string, encType: string = ''
): Promise<IUploadCreat> {
static async UploadCreatFileWithPreHash(user_id: string, drive_id: string, parent_file_id: string, name: string, fileSize: number, prehash: string, check_name_mode: string): Promise<IUploadCreat> {
const result: IUploadCreat = {
user_id,
drive_id,
@@ -19,11 +15,11 @@ export default class AliUpload {
part_info_list: [],
errormsg: ''
}
if (!user_id || !drive_id || !parent_file_id || !filename) {
if (!user_id || !drive_id || !parent_file_id || !name) {
result.errormsg = '创建文件失败(数据错误)'
return result
}
if (parent_file_id.includes('root')) parent_file_id = 'root'
const url = 'adrive/v2/file/createWithFolders'
const postData: {
drive_id: string
@@ -38,7 +34,7 @@ export default class AliUpload {
} = {
drive_id,
parent_file_id: parent_file_id,
name: filename,
name: name,
type: 'file',
check_name_mode: check_name_mode == 'ignore' ? 'refuse' : check_name_mode,
size: fileSize,
@@ -49,7 +45,9 @@ export default class AliUpload {
let partSize = 10485760
if (fileSize > 0) {
let partIndex = 0
while (fileSize > partSize * 8000) partSize = partSize + 10485760
while (partIndex * partSize < fileSize) {
postData.part_info_list.push({ part_number: partIndex + 1, part_size: partSize })
partIndex++
@@ -58,13 +56,16 @@ export default class AliUpload {
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (typeof resp.body === 'object' && JSON.stringify(resp.body).indexOf('file size is exceed') > 0) {
result.errormsg = '创建文件失败(单文件最大100GB/2TB)'
return result
}
if (resp.body && resp.body.code) {
if (resp.body?.code == 'PreHashMatched') {
result.errormsg = 'PreHashMatched'
} else if (resp.body?.code == 'QuotaExhausted.Drive') {
result.errormsg = '出错暂停,网盘空间已满'
@@ -72,6 +73,7 @@ export default class AliUpload {
result.errormsg = resp.body?.code || '创建失败,网络错误'
DebugLog.mSaveDanger('createWithFolders', result.errormsg + ' ' + name)
}
return result
}
@@ -79,10 +81,13 @@ export default class AliUpload {
if (AliHttp.IsSuccess(resp.code)) {
result.file_id = resp.body.file_id
if (resp.body.exist) {
if (check_name_mode == 'ignore') {
await AliUpload.UploadFileDelete(user_id, drive_id, result.file_id).catch()
return await AliUpload.UploadCreatFileWithPreHash(user_id, drive_id, parent_file_id, filename, fileSize, prehash, check_name_mode)
await AliUpload.UploadFileDelete(user_id, drive_id, result.file_id).catch(() => {})
return await AliUpload.UploadCreatFileWithPreHash(user_id, drive_id, parent_file_id, name, fileSize, prehash, check_name_mode)
} else {
result.errormsg = '出错暂停,网盘内有重名文件'
}
}
@@ -93,12 +98,7 @@ export default class AliUpload {
const part_info_list = resp.body.part_info_list
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
result.part_info_list.push({
upload_url: item.upload_url,
part_number: item.part_number,
part_size: partSize,
isupload: false
})
result.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
}
return result
@@ -109,12 +109,7 @@ export default class AliUpload {
}
}
static async UploadCreatFileWithFolders(
user_id: string, drive_id: string,
parent_file_id: string, filename: string,
fileSize: number, hash: string, proof_code: string,
check_name_mode: string, encType: string = ''
): Promise<IUploadCreat> {
static async UploadCreatFileWithFolders(user_id: string, drive_id: string, parent_file_id: string, name: string, fileSize: number, hash: string, proof_code: string, check_name_mode: string): Promise<IUploadCreat> {
const result: IUploadCreat = {
user_id,
drive_id,
@@ -125,13 +120,14 @@ export default class AliUpload {
part_info_list: [],
errormsg: ''
}
if (!user_id || !drive_id || !parent_file_id || !filename) {
if (!user_id || !drive_id || !parent_file_id || !name) {
result.errormsg = '创建文件失败(数据错误)'
return result
}
if (parent_file_id.includes('root')) parent_file_id = 'root'
const url = 'adrive/v2/file/createWithFolders'
const name = EncodeEncName(user_id, filename, false, encType)
const postData: {
drive_id: string
parent_file_id: string
@@ -144,8 +140,7 @@ export default class AliUpload {
proof_code?: string
proof_version?: string
part_info_list: { part_number: number; part_size: number }[]
ignore_rapid?: boolean,
description: string
ignore_rapid?: boolean
} = {
drive_id,
parent_file_id: parent_file_id,
@@ -153,20 +148,16 @@ export default class AliUpload {
type: 'file',
check_name_mode: check_name_mode == 'ignore' ? 'refuse' : check_name_mode,
size: fileSize,
part_info_list: [],
description: encType
part_info_list: []
}
if (hash) {
postData.content_hash = hash.toUpperCase()
postData.content_hash_name = 'sha1'
postData.proof_version = 'v1'
postData.proof_code = proof_code
} else {
postData.content_hash = ''
postData.content_hash_name = 'none'
postData.proof_version = 'v1'
postData.proof_code = ''
}
let partSize = 10485760
@@ -214,7 +205,8 @@ export default class AliUpload {
result.errormsg = ''
} else {
if (check_name_mode == 'ignore') {
await AliUpload.UploadFileDelete(user_id, drive_id, result.file_id).catch()
await AliUpload.UploadFileDelete(user_id, drive_id, result.file_id).catch(() => {})
return await AliUpload.UploadCreatFileWithFolders(user_id, drive_id, parent_file_id, name, fileSize, hash, proof_code, check_name_mode)
} else {
@@ -229,12 +221,7 @@ export default class AliUpload {
const part_info_list = resp.body.part_info_list
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
result.part_info_list.push({
upload_url: item.upload_url,
part_number: item.part_number,
part_size: partSize,
isupload: false
})
result.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
}
return result
@@ -255,11 +242,10 @@ export default class AliUpload {
const content_hash = resp.body.content_hash.toUpperCase()
hash = hash.toUpperCase()
return hash === content_hash
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('UploadFileCheckHash err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('UploadFileCheckHash err=' + (resp.code || ''))
return false
}
return false
}
@@ -270,11 +256,10 @@ export default class AliUpload {
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('UploadFileDelete err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('UploadFileDelete err=' + (resp.code || ''))
return false
}
return false
}
@@ -285,14 +270,19 @@ export default class AliUpload {
let resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.code == 400 || resp.code == 429) {
resp = await AliHttp.Post(url, postData, user_id, '')
}
if (AliHttp.IsSuccess(resp.code)) {
if (resp.body.size == fileSize) {
if (fileSha1) {
if (resp.body.content_hash && resp.body.content_hash == fileSha1) {
return true
} else {
await AliUpload.UploadFileDelete(user_id, drive_id, file_id, true).catch()
await AliUpload.UploadFileDelete(user_id, drive_id, file_id, true).catch(() => {})
DebugLog.mSaveDanger('UploadFileComplete', '合并文件后发现SHA1不一致删除已上传的文件重新上传')
return false
}
@@ -302,7 +292,8 @@ export default class AliUpload {
return true
}
} else {
await AliUpload.UploadFileDelete(user_id, drive_id, file_id, true).catch()
await AliUpload.UploadFileDelete(user_id, drive_id, file_id, true).catch(() => {})
DebugLog.mSaveDanger('UploadFileComplete', '合并文件后发现大小不一致,删除已上传的文件,重新上传')
return false
}
@@ -350,12 +341,7 @@ export default class AliUpload {
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
uploadInfo.part_info_list.push({
upload_url: item.upload_url,
part_number: item.part_number,
part_size: partSize,
isupload: false
})
uploadInfo.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
} else {
@@ -368,7 +354,7 @@ export default class AliUpload {
return 'success'
} else {
uploadInfo.part_info_list = []
DebugLog.mSaveWarning('UploadFilePartUrl err=' + upload_id + ' ' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('UploadFilePartUrl err=' + upload_id + ' ' + (resp.code || ''))
return 'error'
}
}
@@ -400,12 +386,11 @@ export default class AliUpload {
}
if (resp.body.next_part_number_marker && parseInt(resp.body.next_part_number_marker) > 0) {
const next = parseInt(resp.body.next_part_number_marker)
await AliUpload.UploadFileListUploadedParts(user_id, drive_id, file_id, upload_id, next, uploadInfo).catch(() => {
})
await AliUpload.UploadFileListUploadedParts(user_id, drive_id, file_id, upload_id, next, uploadInfo).catch(() => {})
}
return 'success'
} else {
DebugLog.mSaveWarning('UploadFileListUploadedParts err=' + upload_id + ' ' + (resp.code || ''), resp.body)
DebugLog.mSaveWarning('UploadFileListUploadedParts err=' + upload_id + ' ' + (resp.code || ''))
return 'error'
}
}

View File

@@ -0,0 +1,416 @@
import DebugLog from '../utils/debuglog'
import AliHttp from './alihttp'
import { IUploadCreat, IUploadInfo } from './models'
import path from "path";
import AliFileCmd from "./filecmd";
import AliAlbum from './album'
export default class AliUploadOpenApi {
static async UploadCreatFileWithPreHash(user_id: string, drive_id: string, parent_file_id: string, name: string, fileSize: number, prehash: string, check_name_mode: string): Promise<IUploadCreat> {
const result: IUploadCreat = {
user_id,
drive_id,
israpid: false,
isexist: false,
upload_id: '',
file_id: '',
part_info_list: [],
errormsg: ''
}
if (!user_id || !drive_id || !parent_file_id || !name) {
result.errormsg = '创建文件失败(数据错误)'
return result
}
const pathSplitor = name.split(path.sep);
let newFileName = name;
if (pathSplitor.length > 1) {
const dirFullName = pathSplitor.slice(0, pathSplitor.length - 1).join(path.sep);
newFileName = pathSplitor[pathSplitor.length-1]
const resp = await AliFileCmd.ApiCreatNewForder(user_id, drive_id, parent_file_id, dirFullName);
parent_file_id = resp.file_id
}
let url = 'adrive/v1.0/openFile/create'
if (name.includes('_album_id')) {
url = 'adrive/v1/biz/albums/file/create'
newFileName = name.split('album_id=')[0]
}
const postData: {
drive_id: string
parent_file_id: string
name: string
type: string
check_name_mode: string
size: number
pre_hash: string
part_info_list: { part_number: number; part_size: number }[]
ignore_rapid?: boolean
} = {
drive_id,
parent_file_id: parent_file_id,
name: newFileName,
type: 'file',
check_name_mode: check_name_mode == 'ignore' ? 'refuse' : check_name_mode,
size: fileSize,
pre_hash: prehash,
part_info_list: []
}
let partSize = 10485760
if (fileSize > 0) {
let partIndex = 0
while (fileSize > partSize * 8000) partSize = partSize + 10485760
while (partIndex * partSize < fileSize) {
postData.part_info_list.push({ part_number: partIndex + 1, part_size: partSize })
partIndex++
}
postData.part_info_list[partIndex - 1].part_size = fileSize - (partIndex - 1) * partSize
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (typeof resp.body === 'object' && JSON.stringify(resp.body).indexOf('file size is exceed') > 0) {
result.errormsg = '创建文件失败(单文件最大100GB/2TB)'
return result
}
if (resp.body && resp.body.code) {
if (resp.body?.code == 'PreHashMatched') {
result.errormsg = 'PreHashMatched'
} else if (resp.body?.code == 'QuotaExhausted.Drive') {
result.errormsg = '出错暂停,网盘空间已满'
} else {
result.errormsg = resp.body?.code || '创建失败,网络错误'
DebugLog.mSaveDanger('createWithFolders', result.errormsg + ' ' + name)
}
return result
}
if (AliHttp.IsSuccess(resp.code)) {
result.file_id = resp.body.file_id
if (resp.body.exist) {
if (check_name_mode == 'ignore') {
await this.UploadFileDelete(user_id, drive_id, result.file_id).catch(() => {})
return await this.UploadCreatFileWithPreHash(user_id, drive_id, parent_file_id, name, fileSize, prehash, check_name_mode)
} else {
result.errormsg = '出错暂停,网盘内有重名文件'
}
}
result.isexist = resp.body.exist || false
result.israpid = false
result.upload_id = resp.body.upload_id || ''
if (resp.body.part_info_list && resp.body.part_info_list.length > 0) {
const part_info_list = resp.body.part_info_list
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
result.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
}
return result
} else {
DebugLog.mSaveWarning('UploadCreatFileWithFolders err=' + (resp.code || ''))
result.errormsg = '创建文件失败' + resp.code.toString()
return result
}
}
static async UploadCreatFileWithFolders(user_id: string, drive_id: string,
parent_file_id: string, name: string, fileSize: number,
hash: string, proof_code: string, check_name_mode: string): Promise<IUploadCreat> {
const result: IUploadCreat = {
user_id,
drive_id,
israpid: false,
isexist: false,
upload_id: '',
file_id: '',
part_info_list: [],
errormsg: ''
}
if (!user_id || !drive_id || !parent_file_id || !name) {
result.errormsg = '创建文件失败(数据错误)'
return result
}
let newFileName = name
const pathSplitor = name.split(path.sep);
if (pathSplitor.length > 1) {
newFileName = pathSplitor[pathSplitor.length-1]
const dirFullName = pathSplitor.slice(0, pathSplitor.length - 1).join(path.sep);
const resp = await AliFileCmd.ApiCreatNewForder(user_id, drive_id, parent_file_id, dirFullName);
parent_file_id = resp.file_id
}
const url = 'adrive/v1.0/openFile/create'
const postData: {
drive_id: string
parent_file_id: string
name: string
type: string
check_name_mode: string
size: number
content_hash?: string
content_hash_name?: string
proof_code?: string
proof_version?: string
part_info_list: { part_number: number; part_size: number }[]
} = {
drive_id,
parent_file_id: parent_file_id,
name: newFileName,
type: 'file',
check_name_mode: check_name_mode == 'ignore' ? 'refuse' : check_name_mode,
size: fileSize,
part_info_list: []
}
if (hash) {
postData.content_hash = hash.toUpperCase()
postData.content_hash_name = 'sha1'
postData.proof_version = 'v1'
postData.proof_code = proof_code
}
let partSize = 10485760
if (fileSize > 0) {
let partIndex = 0
while (fileSize > partSize * 8000) partSize = partSize + 10485760
while (partIndex * partSize < fileSize) {
postData.part_info_list.push({ part_number: partIndex + 1, part_size: partSize })
partIndex++
}
postData.part_info_list[partIndex - 1].part_size = fileSize - (partIndex - 1) * partSize
}
const resp = await AliHttp.Post(url, postData, user_id, '')
if (typeof resp.body === 'object' && JSON.stringify(resp.body).indexOf('file size is exceed') > 0) {
result.errormsg = '创建文件失败(单文件最大100GB/2TB)'
return result
}
if (resp.body && resp.body.code) {
if (resp.body?.code == 'QuotaExhausted.Drive') {
result.errormsg = '出错暂停,网盘空间已满'
} else if (resp.body?.code == 'InvalidRapidProof') {
result.errormsg = resp.body.code
DebugLog.mSaveDanger('InvalidRapidProof', name)
} else {
result.errormsg = resp.body?.code || '创建失败,网络错误'
DebugLog.mSaveDanger('createWithFolders', result.errormsg + ' ' + name)
}
return result
}
if (AliHttp.IsSuccess(resp.code)) {
result.file_id = resp.body.file_id
if (resp.body.exist) {
const issame = await this.UploadFileCheckHash(user_id, drive_id, result.file_id, hash)
if (issame) {
result.errormsg = ''
} else {
if (check_name_mode == 'ignore') {
await this.UploadFileDelete(user_id, drive_id, result.file_id).catch(() => {})
return await this.UploadCreatFileWithFolders(user_id, drive_id, parent_file_id, name, fileSize, hash, proof_code, check_name_mode)
} else {
result.errormsg = '出错暂停,网盘内有重名文件'
}
}
}
result.isexist = resp.body.exist || false
result.israpid = result.israpid || resp.body.rapid_upload || false
result.upload_id = resp.body.upload_id || ''
if (resp.body.part_info_list && resp.body.part_info_list.length > 0) {
const part_info_list = resp.body.part_info_list
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
result.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
}
return result
} else {
DebugLog.mSaveWarning('UploadCreatFileWithFolders err=' + (resp.code || ''))
result.errormsg = '创建文件失败' + resp.code.toString()
return result
}
}
static async UploadFileCheckHash(user_id: string, drive_id: string, file_id: string, hash: string): Promise<boolean> {
if (!user_id || !drive_id || !file_id) return false
const url = 'v2/file/get?jsonmask=content_hash'
const postData = { drive_id: drive_id, file_id: file_id }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code) && resp.body.content_hash) {
const content_hash = resp.body.content_hash.toUpperCase()
hash = hash.toUpperCase()
return hash === content_hash
} else {
DebugLog.mSaveWarning('UploadFileCheckHash err=' + (resp.code || ''))
return false
}
}
static async UploadFileDelete(user_id: string, drive_id: string, file_id: string, permanently: boolean = false): Promise<boolean> {
if (!user_id || !drive_id || !file_id) return false
const url = 'adrive/v1.0/openFile/recyclebin/trash'
const postData = { drive_id: drive_id, file_id: file_id }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
return true
} else {
DebugLog.mSaveWarning('UploadFileDelete err=' + (resp.code || ''))
return false
}
}
static async UploadFileComplete(user_id: string, drive_id: string, file_id: string, upload_id: string, fileSize: number, fileSha1: string): Promise<boolean> {
if (!user_id || !drive_id || !file_id || !upload_id) return false
const url = 'adrive/v1.0/openFile/complete'
const postData = { drive_id: drive_id, upload_id: upload_id, file_id: file_id }
let resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.code == 400 || resp.code == 429) {
resp = await AliHttp.Post(url, postData, user_id, '')
}
if (AliHttp.IsSuccess(resp.code)) {
if (resp.body.size == fileSize) {
if (fileSha1) {
if (resp.body.content_hash && resp.body.content_hash == fileSha1) {
return true
} else {
await this.UploadFileDelete(user_id, drive_id, file_id, true).catch(() => {})
DebugLog.mSaveDanger('UploadFileComplete', '合并文件后发现SHA1不一致删除已上传的文件重新上传')
return false
}
} else if (fileSize < 10485760) {
return true
} else {
return true
}
} else {
await this.UploadFileDelete(user_id, drive_id, file_id, true).catch(() => {})
DebugLog.mSaveDanger('UploadFileComplete', '合并文件后发现大小不一致,删除已上传的文件,重新上传')
return false
}
} else {
DebugLog.mSaveDanger('UploadFileComplete', '合并文件时出错' + resp.code + ' ' + JSON.stringify(resp.header || {}) + ' ' + JSON.stringify(resp.body || {}))
return false
}
}
static async UploadFilePartUrl(user_id: string, drive_id: string, file_id: string, upload_id: string, fileSize: number, uploadInfo: IUploadInfo): Promise<'neterror' | 'success' | 'error'> {
const url = 'adrive/v1.0/openFile/getUploadUrl'
const postData: {
drive_id: string
upload_id: string
file_id: string
part_info_list: { part_number: number; part_size: number }[]
} = {
drive_id: drive_id,
upload_id: upload_id,
file_id: file_id,
part_info_list: []
}
let partIndex = 0
let partSize = 10485760
while (fileSize > partSize * 8000) partSize = partSize + 10485760
while (partIndex * partSize < fileSize) {
postData.part_info_list.push({ part_number: partIndex + 1, part_size: partSize })
partIndex++
}
postData.part_info_list[partIndex - 1].part_size = fileSize - (partIndex - 1) * partSize
const resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.code >= 600 && resp.code <= 610) {
return 'neterror'
}
if (AliHttp.IsSuccess(resp.code)) {
if (resp.body.part_info_list && resp.body.part_info_list.length > 0) {
const part_info_list = resp.body.part_info_list
if (uploadInfo.part_info_list.length == 0) {
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
uploadInfo.part_info_list.push({ upload_url: item.upload_url, part_number: item.part_number, part_size: partSize, isupload: false })
}
} else {
for (let i = 0, maxi = part_info_list.length; i < maxi; i++) {
const item = part_info_list[i]
uploadInfo.part_info_list[item.part_number - 1].upload_url = item.upload_url
}
}
}
return 'success'
} else {
uploadInfo.part_info_list = []
DebugLog.mSaveWarning('UploadFilePartUrl err=' + upload_id + ' ' + (resp.code || ''))
return 'error'
}
}
static async UploadFileListUploadedParts(user_id: string, drive_id: string, file_id: string, upload_id: string, part_number_marker: string, uploadInfo: IUploadInfo): Promise<'neterror' | 'success' | 'error'> {
if (!user_id || !drive_id || !file_id || !upload_id) return 'error'
const url = 'adrive/v1.0/openFile/listUploadedParts'
const postData = { drive_id: drive_id, upload_id: upload_id, file_id: file_id, next_part_number_marker:part_number_marker /* 1开始 */ }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (resp.code >= 600 && resp.code <= 610) {
return 'neterror'
}
if (AliHttp.IsSuccess(resp.code)) {
if (resp.body.uploaded_parts && resp.body.uploaded_parts.length > 0) {
const uploaded_parts = resp.body.uploaded_parts
for (let i = 0, maxi = uploaded_parts.length; i < maxi; i++) {
const item = uploaded_parts[i]
const part_number = item.part_number
const uploadpart = uploadInfo.part_info_list[part_number - 1]
if (uploadpart.part_size != item.part_size) {
DebugLog.mSaveDanger('list_uploaded_parts', '分片数据错误 uploadpart=' + uploadpart.part_size + ' item=' + item.part_size)
return 'error'
}
uploadInfo.part_info_list[part_number - 1].isupload = true
}
}
if (resp.body.next_part_number_marker && parseInt(resp.body.next_part_number_marker) > 0) {
const next = resp.body.next_part_number_marker
await this.UploadFileListUploadedParts(user_id, drive_id, file_id, upload_id, next, uploadInfo).catch(() => {})
}
return 'success'
} else {
DebugLog.mSaveWarning('UploadFileListUploadedParts err=' + upload_id + ' ' + (resp.code || ''))
return 'error'
}
}
static isNetworkError(e: Error): boolean {
return e.message == 'Network Error' || e.message.includes('socket hang up') || e.message.includes('getaddrinfo ENOTFOUND') || e.message.includes('timeout of') || e.message.includes('connect ECONNRESET') || e.message.includes('connect ETIMEDOUT') || e.message.includes('EPIPE')
}
}

View File

@@ -4,22 +4,23 @@ import { OpenFileHandle } from '../utils/filehelper'
import { FileHandle, FileReadResult } from 'fs/promises'
import { IUploadInfo } from './models'
import AliUpload from './upload'
/*import HttpsProxyAgent from 'https-proxy-agent'
import { SocksProxyAgent } from 'socks-proxy-agent'
import { useSettingStore } from '../store'*/
import DBCache from '../utils/dbcache'
import UserDAL from '../user/userdal'
import { Sleep } from '../utils/format'
import AliUploadHashPool from './uploadhashpool'
import nodehttps from 'https'
import type { ClientRequest } from 'http'
import path from 'path'
import { Howl } from 'howler'
import { useSettingStore } from '../store'
import FlowEnc from '../module/flow-enc'
import { getFlowEnc } from '../utils/proxyhelper'
import AliUploadOpenApi from "./uploadOpenApi";
const sound = new Howl({
src: ['./audio/upload_finished.mp3'], // 音频文件路径
autoplay: false, // 是否自动播放
volume: 1.0 // 音量,范围 0.0 ~ 1.0
volume: 1.0, // 音量,范围 0.0 ~ 1.0
})
const filePosMap = new Map<number, number>()
@@ -27,31 +28,24 @@ let UploadSpeedTotal = 0
export default class AliUploadDisk {
static async UploadOneFile(uploadInfo: IUploadInfo, fileui: IUploadingUI): Promise<string> {
const flowEnc = getFlowEnc(fileui.user_id, fileui.File.size, fileui.encType)
if (uploadInfo.part_info_list.length > 1) {
return AliUploadDisk.UploadOneFileBig(uploadInfo, fileui, flowEnc)
}
if (uploadInfo.part_info_list.length > 1) return AliUploadDisk.UploadOneFileBig(uploadInfo, fileui)
const upload_url = uploadInfo.part_info_list[0].upload_url
const fileHandle = await OpenFileHandle(path.join(fileui.localFilePath, fileui.File.partPath))
if (fileHandle.error) return fileHandle.error
filePosMap.set(fileui.UploadID, 0)
let isok = ''
for (let i = 0; i < 3; i++) {
isok = await AliUploadDisk.UploadOneFilePartNode(
fileui, flowEnc, fileHandle.handle,
0, fileui.File.size, upload_url
)
isok = await AliUploadDisk.UploadOneFilePartNode(fileui.user_id, fileui.UploadID, fileHandle.handle, 0, fileui.File.size, upload_url)
if (isok == 'success') {
break
}
}
if (fileHandle.handle) await fileHandle.handle.close()
return AliUpload.UploadFileComplete(
fileui.user_id, fileui.drive_id,
fileui.Info.up_file_id, fileui.Info.up_upload_id,
fileui.File.size, uploadInfo.sha1
)
.then(async (isSuccess) => {
return AliUploadOpenApi.UploadFileComplete(fileui.user_id, fileui.drive_id, fileui.Info.up_file_id, fileui.Info.up_upload_id, fileui.File.size, uploadInfo.sha1)
.then((isSuccess) => {
fileui.File.uploaded_file_id = fileui.Info.up_file_id
fileui.File.uploaded_is_rapid = false
fileui.Info.up_file_id = ''
@@ -61,7 +55,8 @@ export default class AliUploadDisk {
sound.play()
}
return 'success'
} else return '合并文件时出错,请重试'
}
else return '合并文件时出错,请重试'
})
.catch((err: any) => {
DebugLog.mSaveDanger('合并文件时出错', err)
@@ -69,37 +64,43 @@ export default class AliUploadDisk {
})
}
static async UploadOneFileBig(uploadInfo: IUploadInfo, fileui: IUploadingUI, flowEnc: FlowEnc | null): Promise<string> {
static async UploadOneFileBig(uploadInfo: IUploadInfo, fileui: IUploadingUI): Promise<string> {
filePosMap.set(fileui.UploadID, 0)
const fileHandle = await OpenFileHandle(path.join(fileui.localFilePath, fileui.File.partPath))
if (fileHandle.error) return fileHandle.error
const fileSize = fileui.File.size
for (let i = 0, maxi = uploadInfo.part_info_list.length; i < maxi; i++) {
let part = uploadInfo.part_info_list[i]
const partStart = (part.part_number - 1) * part.part_size
const partEnd = partStart + part.part_size
const partSize = partEnd > fileSize ? fileSize - partStart : part.part_size
const part_size = partEnd > fileSize ? fileSize - partStart : part.part_size
if (part.isupload) {
filePosMap.set(fileui.UploadID, partStart + partSize)
filePosMap.set(fileui.UploadID, partStart + part_size)
} else {
const url = part.upload_url
let expires = url.substring(url.indexOf('x-oss-expires=') + 'x-oss-expires='.length)
expires = expires.substring(0, expires.indexOf('&'))
const lastTime = parseInt(expires) - Date.now() / 1000
if (lastTime < 5 * 60) {
await AliUpload.UploadFilePartUrl(
fileui.user_id, fileui.drive_id, fileui.Info.up_file_id,
fileui.Info.up_upload_id, fileui.File.size, uploadInfo
).catch()
await AliUploadOpenApi.UploadFilePartUrl(fileui.user_id, fileui.drive_id, fileui.Info.up_file_id, fileui.Info.up_upload_id, fileui.File.size, uploadInfo).catch(() => {})
if (uploadInfo.part_info_list.length == 0) return '获取分片信息失败,请重试'
part = uploadInfo.part_info_list[i]
}
let isok = ''
for (let j = 0; j < 3; j++) {
isok = await AliUploadDisk.UploadOneFilePartNode(
fileui, flowEnc, fileHandle.handle,
partStart, partSize, part.upload_url
)
isok = await AliUploadDisk.UploadOneFilePartNode(fileui.user_id, fileui.UploadID, fileHandle.handle, partStart, part_size, part.upload_url)
// isok = await AliUploadDisk.UploadOneFilePartNodeXHR(file.File.user_id, file.UploadID, fileHandle.handle, partStart, part_size, part.upload_url)
if (isok == 'success') {
part.isupload = true
break
@@ -107,7 +108,7 @@ export default class AliUploadDisk {
if (!fileui.IsRunning) break
}
if (!fileui.IsRunning) break
if (!part.isupload) {
if (part.isupload == false) {
if (fileHandle.handle) await fileHandle.handle.close()
return isok
}
@@ -115,14 +116,16 @@ export default class AliUploadDisk {
}
if (fileHandle.handle) await fileHandle.handle.close()
if (!fileui.IsRunning) return '已暂停'
for (let i = 0, maxi = uploadInfo.part_info_list.length; i < maxi; i++) {
if (!uploadInfo.part_info_list[i].isupload) {
if (uploadInfo.part_info_list[i].isupload == false) {
return '有分片上传失败,请重试'
}
}
if (!fileui.encType && !uploadInfo.sha1) {
if (!uploadInfo.sha1) {
if (fileui.File.size >= 1024000) {
const prehash = await AliUploadHashPool.GetFilePreHash(path.join(fileui.localFilePath, fileui.File.partPath))
if (fileui.File.size >= 10240000 && !prehash.startsWith('error')) {
uploadInfo.sha1 = await DBCache.getFileHash(fileui.File.size, fileui.File.mtime, prehash, path.basename(fileui.File.name))
@@ -130,14 +133,16 @@ export default class AliUploadDisk {
}
}
return AliUpload.UploadFileComplete(fileui.user_id, fileui.drive_id, fileui.Info.up_file_id, fileui.Info.up_upload_id, fileui.File.size, uploadInfo.sha1)
.then(async (isSuccess) => {
return AliUploadOpenApi.UploadFileComplete(fileui.user_id, fileui.drive_id, fileui.Info.up_file_id, fileui.Info.up_upload_id, fileui.File.size, uploadInfo.sha1)
.then((isSuccess) => {
if (isSuccess) {
if (useSettingStore().downFinishAudio && !sound.playing()) {
sound.play()
}
return 'success'
} else return '合并文件时出错,请重试'
}
else return '合并文件时出错,请重试'
})
.catch((err: any) => {
DebugLog.mSaveDanger('合并文件时出错', err)
@@ -146,9 +151,9 @@ export default class AliUploadDisk {
}
static UploadOneFilePartNode(fileui: IUploadingUI, flowEnc: FlowEnc | null, fileHandle: FileHandle, partStart: number, partSize: number, upload_url: string): Promise<string> {
static UploadOneFilePartNode(user_id: string, UploadID: number, fileHandle: FileHandle, partStart: number, partSize: number, upload_url: string): Promise<string> {
return new Promise<string>(async (resolve) => {
const token = await UserDAL.GetUserTokenFromDB(fileui.user_id)
const token = await UserDAL.GetUserTokenFromDB(user_id)
if (!token || !token.access_token) {
resolve('找不到上传token请重试')
return
@@ -158,28 +163,35 @@ export default class AliUploadDisk {
method: 'PUT',
strictSSL: false,
rejectUnauthorized: false,
timeout: 15000,
timeout: 15000 ,
headers: {
'Content-Type': '',
'Content-Type': '' ,
'Content-Length': partSize,
'Transfer-Encoding': 'chunked',
'Transfer-Encoding': 'chunked' ,
Authorization: token.token_type + ' ' + token.access_token,
Connection: 'keep-alive'
}
}
const winfo = {
UploadID: fileui.UploadID,
isstop: false,
partSize, partStart,
buff: Buffer.alloc(40960),
flowEnc: flowEnc
/*const settingStore = useSettingStore()
const proxy = settingStore.proxyUseProxy ? settingStore.getProxy() : undefined
if (proxy) {
if (settingStore.proxyType.startsWith('http')) {
const agenth = HttpsProxyAgent(proxy)
option = Object.assign(option, { agent: agenth })
} else {
const agents = new SocksProxyAgent(proxy)
option = Object.assign(option, { agent: agents })
}
const req: ClientRequest = nodehttps.request(upload_url, option, function(res: any) {
}*/
const winfo = { UploadID, isstop: false, partSize, partStart, buff: Buffer.alloc(40960) }
const req = nodehttps.request(upload_url, option, function (res: any) {
let _data = ''
res.on('data', function(chunk: string) {
res.on('data', function (chunk: string) {
_data += chunk
})
res.on('end', function() {
res.on('end', function () {
winfo.isstop = true
if (res.statusCode == 200) {
resolve('success')
@@ -199,7 +211,7 @@ export default class AliUploadDisk {
resolve('分片上传失败,稍后重试' + message)
})
while (winfo.partSize > 0 && !winfo.isstop) {
while (winfo.partSize > 0 && winfo.isstop == false) {
const result = await AliUploadDisk._WriteToRequest(req, fileHandle, winfo)
if (result != 'success') {
resolve('读取文件数据失败,请重试')
@@ -210,28 +222,19 @@ export default class AliUploadDisk {
})
}
static async _WriteToRequest(req: ClientRequest, fileHandle: FileHandle, winfo: {
UploadID: number;
isstop: boolean;
partSize: number;
partStart: number;
buff: Buffer,
flowEnc: FlowEnc | null
}): Promise<string> {
static async _WriteToRequest(req: any, fileHandle: FileHandle, winfo: { UploadID: number; isstop: boolean; partSize: number; partStart: number; buff: Buffer }): Promise<string> {
return new Promise<string>((resolve) => {
try {
const flowEnc = winfo.flowEnc
const redLen = Math.min(40960, winfo.partSize)
if (redLen != winfo.buff.length) winfo.buff = Buffer.alloc(redLen)
fileHandle
.read(winfo.buff, 0, redLen, winfo.partStart)
.then(async (rbuff: FileReadResult<Buffer>) => {
.then((rbuff: FileReadResult<Buffer>) => {
if (redLen == rbuff.bytesRead) {
winfo.partStart += redLen
winfo.partSize -= redLen
const uploadpos = winfo.partStart
const bufferData = winfo.flowEnc ? winfo.flowEnc.encryptBuff(rbuff.buffer) : rbuff.buffer
req.write(bufferData, async function() {
req.write(rbuff.buffer, async function () {
filePosMap.set(winfo.UploadID, uploadpos)
UploadSpeedTotal += redLen
window.speedLimte -= redLen
@@ -246,19 +249,114 @@ export default class AliUploadDisk {
resolve('读取文件数据失败,请重试')
}
})
.catch((error) => {
console.error(error)
.catch(() => {
winfo.isstop = true
resolve('读取文件数据失败,请重试')
})
} catch (error) {
console.error(error)
} catch {
winfo.isstop = true
resolve('读取文件数据失败,请重试')
}
})
}
static UploadOneFilePartNodeXHR(user_id: string, UploadID: number, fileHandle: FileHandle, partStart: number, partSize: number, upload_url: string): Promise<string> {
return new Promise<string>(async (resolve) => {
const token = await UserDAL.GetUserTokenFromDB(user_id)
if (!token || !token.access_token) {
resolve('找不到上传token请重试')
return
}
const winfo = { UploadID, isstop: false, partSize: partSize, partStart: partStart, buff: Buffer.alloc(40960) }
const client = new XMLHttpRequest()
client.open('PUT', upload_url)
client.timeout = 15000
client.setRequestHeader('ContenpartSize', partSize.toString())
client.setRequestHeader('Content-Type', '')
client.onreadystatechange = function () {
switch (client.readyState) {
case 1: // OPENED
// do something
break
case 2: // HEADERS_RECEIVED
// do something
break
case 3: // LOADING
// do something
break
case 4: // DONE
// do something
break
}
}
client.upload.onprogress = function updateProgress(event) {
if (event.lengthComputable) {
const completedPercent = event.loaded / event.total
console.log('onprogress', event, completedPercent)
filePosMap.set(winfo.UploadID, partStart + event.loaded)
}
}
client.onabort = function () {
resolve('用户暂停')
}
client.ontimeout = function () {
resolve('网络超时')
}
client.onerror = function () {
resolve('网络出错')
}
client.onloadend = function () {
if ((client.status >= 200 && client.status < 300) || client.status == 409) {
resolve('success')
} else {
resolve('分片上传失败,稍后重试' + client.status)
DebugLog.mSaveDanger('分片上传失败,稍后重试' + client.status)
}
}
const data = await this._ReadPartBuffer(fileHandle, winfo)
if (data != 'success') resolve(data)
else {
try {
client.send(winfo.buff)
} catch (err: any) {
console.log('send', err)
resolve('联网发送失败,请重试')
}
}
})
}
static async _ReadPartBuffer(fileHandle: FileHandle, winfo: { UploadID: number; isstop: boolean; partSize: number; partStart: number; buff: Buffer }): Promise<string> {
return new Promise<string>((resolve) => {
try {
const redLen = winfo.partSize
if (redLen != winfo.buff.length) winfo.buff = Buffer.alloc(redLen)
fileHandle
.read(winfo.buff, 0, redLen, winfo.partStart)
.then((rbuff: FileReadResult<Buffer>) => {
if (redLen == rbuff.bytesRead) {
resolve('success')
} else {
winfo.isstop = true
resolve('读取文件数据失败,请重试')
}
})
.catch(() => {
winfo.isstop = true
resolve('读取文件数据失败,请重试')
})
} catch {
winfo.isstop = true
resolve('读取文件数据失败,请重试')
}
})
}
static GetFileUploadSpeed(UploadID: number): number {
return filePosMap.get(UploadID) || 0
}
@@ -270,7 +368,7 @@ export default class AliUploadDisk {
static GetFileUploadSpeedTotal(): number {
const speed = Number(UploadSpeedTotal)
const speed = UploadSpeedTotal + 0
UploadSpeedTotal = 0
return speed
}

View File

@@ -2,8 +2,8 @@ import { IUploadingUI } from '../utils/dbupload'
import { OpenFileHandle } from '../utils/filehelper'
import DBCache from '../utils/dbcache'
import Sha1WorkerPool from '../utils/sha1workerpool'
import path from 'node:path'
import crypto from 'crypto'
const path = window.require('path')
const crypto = window.require('crypto')
const sha1PosMap = new Map<number, number>()

View File

@@ -3,29 +3,25 @@ import DebugLog from '../utils/debuglog'
import axios from 'axios'
import AliUpload from './upload'
import AliUploadHashPool from './uploadhashpool'
import { getFlowEnc } from '../utils/proxyhelper'
import AliUploadOpenApi from "./uploadOpenApi";
export default class AliUploadMem {
static async UploadMem(user_id: string, drive_id: string, parent_file_id: string, CreatFileName: string, context: string, encType: string = '') {
static async UploadMem(user_id: string, drive_id: string, parent_file_id: string, CreatFileName: string, context: string) {
const token = await UserDAL.GetUserTokenFromDB(user_id)
if (!token || !token.access_token) return '账号失效,操作取消'
if (!token || !token.access_token || !token.access_token_v2) return '账号失效,操作取消'
let hash = 'DA39A3EE5E6B4B0D3255BFEF95601890AFD80709'
let proof = ''
let buff = Buffer.from([])
if (context.length > 0) {
buff = Buffer.from(context, 'utf-8')
if (encType) {
let flowEnc = getFlowEnc(user_id, buff.length, encType)
buff = flowEnc && flowEnc.encryptBuff(buff) || buff
}
const dd = await AliUploadHashPool.GetBuffHashProof(token!.access_token, buff)
const dd = await AliUploadHashPool.GetBuffHashProof(token!.access_token_v2, buff)
hash = dd.sha1
proof = dd.proof_code
}
const size = buff.length
const upinfo = await AliUpload.UploadCreatFileWithFolders(user_id, drive_id, parent_file_id, CreatFileName, size, hash, proof, 'refuse', encType)
const upinfo = await AliUploadOpenApi.UploadCreatFileWithFolders(user_id, drive_id, parent_file_id, CreatFileName, size, hash, proof, 'refuse')
if (upinfo.errormsg != '') {
return upinfo.errormsg
}
@@ -37,6 +33,7 @@ export default class AliUploadMem {
responseType: 'text',
timeout: 30000,
headers: {
'Content-Type': '',
Authorization: token!.token_type + ' ' + token!.access_token
}
@@ -44,7 +41,7 @@ export default class AliUploadMem {
.catch(function (err: any) {
DebugLog.mSaveDanger('UploadMemError', err)
})
const result = await AliUpload.UploadFileComplete(user_id, drive_id, upinfo.file_id, upinfo.upload_id, size, hash)
const result = await AliUploadOpenApi.UploadFileComplete(user_id, drive_id, upinfo.file_id, upinfo.upload_id, size, hash)
if (result) return 'success'
else return '合并文件失败'
}

View File

@@ -1,30 +1,22 @@
import UserDAL from '../user/userdal'
import { humanDateTime, humanDateTimeDateStr, humanSize, Sleep } from '../utils/format'
import { ITokenInfo } from '../user/userstore'
import AliHttp from './alihttp'
import AliHttp, {IUrlRespData} from './alihttp'
import message from '../utils/message'
import DebugLog from '../utils/debuglog'
import { IAliUserDriveCapacity, IAliUserDriveDetails } from './models'
import { GetSignature } from './utils'
import getUuid from 'uuid-by-string'
import { useSettingStore } from '../store'
import Config from '../config'
import Config from "../utils/config";
export const TokenReTimeMap = new Map<string, number>()
export const TokenLockMap = new Map<string, number>()
export const OpenApiTokenReTimeMap = new Map<string, number>()
export const OpenApiTokenLockMap = new Map<string, number>()
export const SessionLockMap = new Map<string, number>()
export const SessionReTimeMap = new Map<string, number>()
export default class AliUser {
static async ApiSessionRefreshAccount(token: ITokenInfo, showMessage: boolean, forceRefresh: boolean = false): Promise<boolean> {
if (!token.user_id) return false
if (!forceRefresh && new Date(token.session_expires_in).getTime() >= Date.now()) return true
if (forceRefresh) {
SessionLockMap.delete(token.user_id)
SessionReTimeMap.delete(token.user_id)
}
static async ApiSessionRefreshAccount(token: ITokenInfo, showMessage: boolean): Promise<boolean> {
if(!token.user_id) return false
while (true) {
const lock = SessionLockMap.has(token.user_id)
if (lock) await Sleep(1000)
@@ -36,7 +28,7 @@ export default class AliUser {
SessionLockMap.delete(token.user_id)
return true
}
const apiUrl = 'https://api.alipan.com/users/v1/users/device/create_session'
const apiUrl = 'https://api.aliyundrive.com/users/v1/users/device/create_session'
let { signature, publicKey } = GetSignature(0, token.user_id, token.device_id)
const postData = {
'deviceName': 'Edge浏览器',
@@ -47,12 +39,11 @@ export default class AliUser {
SessionLockMap.delete(token.user_id)
if (AliHttp.IsSuccess(resp.code)) {
SessionReTimeMap.set(token.user_id, Date.now())
token.session_expires_in = Date.now() + 30 * 60 * 1000
token.signature = signature
UserDAL.SaveUserToken(token)
return true
} else {
DebugLog.mSaveWarning('ApiSessionRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || ''), resp.body)
DebugLog.mSaveWarning('ApiSessionRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || ''))
if (showMessage) {
message.error('刷新账号[' + token.user_name + '] session 失败')
}
@@ -60,13 +51,27 @@ export default class AliUser {
return false
}
static async ApiTokenRefreshAccount(token: ITokenInfo, showMessage: boolean, forceRefresh: boolean = false): Promise<boolean> {
if (!token.refresh_token) return false
if (!forceRefresh && new Date(token.expire_time).getTime() >= Date.now()) return true
if (forceRefresh) {
TokenLockMap.delete(token.user_id)
TokenReTimeMap.delete(token.user_id)
static async ApiTokenRefreshAccountV2(token: ITokenInfo): Promise<IUrlRespData> {
const postData = {
refresh_token: token.refresh_token_v2,
grant_type: 'refresh_token',
client_secret: 'a3d3a7036fa9417399eef14891f6084f',
client_id: 'e90a7b360e894c60b7b314579f42827d'
}
return await AliHttp.Post(Config.accessTokenUrl, postData, '', '')
}
static async ApiTokenRefreshAccountV2_TMP(token: ITokenInfo): Promise<IUrlRespData> {
const postData = {
refresh_token: token.refresh_token_v2,
grant_type: 'refresh_token'
}
return await AliHttp._Post(Config.tmpUrl, postData, '', '')
}
static async ApiTokenRefreshAccount(token: ITokenInfo, showMessage: boolean): Promise<boolean> {
if (!token.refresh_token) return false
while (true) {
const lock = TokenLockMap.has(token.user_id)
if (lock) await Sleep(1000)
@@ -79,17 +84,25 @@ export default class AliUser {
return true
}
const url = 'https://auth.alipan.com/v2/account/token'
const url = 'https://auth.aliyundrive.com/v2/account/token'
const postData = { refresh_token: token.refresh_token, grant_type: 'refresh_token' }
const resp = await AliHttp.Post(url, postData, '', '')
const respV2 = await this.ApiTokenRefreshAccountV2_TMP(token)
TokenLockMap.delete(token.user_id)
if (AliHttp.IsSuccess(resp.code)) {
if (AliHttp.IsSuccess(resp.code) && AliHttp.IsSuccess(respV2.code)) {
TokenReTimeMap.set(resp.body.user_id, Date.now())
token.tokenfrom = 'account'
token.access_token = resp.body.access_token
token.refresh_token = resp.body.refresh_token
token.expires_in = resp.body.expires_in
token.token_type = resp.body.token_type
token.refresh_token_v2 = respV2.body.refresh_token
token.access_token_v2 = respV2.body.access_token
token.expires_in_v2 = respV2.body.expires_in
token.token_type_v2 = respV2.body.token_type
token.user_id = resp.body.user_id
token.user_name = resp.body.user_name
token.avatar = resp.body.avatar
@@ -114,7 +127,7 @@ export default class AliUser {
return true
} else {
if (resp.body?.code != 'InvalidParameter.RefreshToken') {
DebugLog.mSaveWarning('ApiTokenRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || ''), resp.body)
DebugLog.mSaveWarning('ApiTokenRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || ''))
}
if (showMessage) {
message.error('刷新账号[' + token.user_name + '] token 失败,需要重新登录')
@@ -127,197 +140,34 @@ export default class AliUser {
}
static async OpenApiTokenRefreshAccount(token: ITokenInfo, showMessage: boolean, forceRefresh: boolean = false): Promise<boolean> {
if (!token.open_api_refresh_token) return false
if (!forceRefresh && new Date(token.open_api_expires_in).getTime() >= Date.now()) return true
// 防止重复刷新
if (forceRefresh) {
OpenApiTokenLockMap.delete(token.user_id)
OpenApiTokenReTimeMap.delete(token.user_id)
}
while (true) {
const lock = OpenApiTokenLockMap.has(token.user_id)
if (lock) await Sleep(1000)
else break
}
OpenApiTokenLockMap.set(token.user_id, Date.now())
const time = OpenApiTokenReTimeMap.get(token.user_id) || 0
if (Date.now() - time < 1000 * 60 * 5) {
OpenApiTokenLockMap.delete(token.user_id)
return true
}
let { uiEnableOpenApiType, uiOpenApiClientId, uiOpenApiClientSecret } = useSettingStore()
let url = 'https://openapi.alipan.com/oauth/access_token'
let client_id = Config.client_id
let client_secret = Config.client_secret
if (uiEnableOpenApiType === 'custom') {
client_id = uiOpenApiClientId
client_secret = uiOpenApiClientSecret
}
const postData = {
refresh_token: token.open_api_refresh_token,
grant_type: 'refresh_token',
client_id: client_id,
client_secret: client_secret
}
const resp = await AliHttp.Post(url, postData, '', '')
OpenApiTokenLockMap.delete(token.user_id)
if (AliHttp.IsSuccess(resp.code)) {
const { access_token, refresh_token, token_type, expires_in } = resp.body
OpenApiTokenReTimeMap.set(token.user_id, Date.now())
token.open_api_token_type = token_type
token.open_api_access_token = access_token
token.open_api_refresh_token = refresh_token
token.open_api_expires_in = Date.now() + expires_in * 1000
window.WebUserToken({
user_id: token.user_id,
name: token.user_name,
access_token: token.access_token,
open_api_access_token: token.open_api_access_token,
refresh: true
})
UserDAL.SaveUserToken(token)
return true
} else {
DebugLog.mSaveWarning('OpenApiTokenRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || ''), resp.body)
if (showMessage) {
message.error('刷新账号[' + token.user_name + '] OpenApiToken 失败, 请重新获取', 5)
}
}
return false
}
static async OpenApiQrCodeUrl(client_id: string, client_secret: string, width: number = 348, height: number = 348): Promise<any> {
const postData = {
client_id: client_id,
client_secret: client_secret,
scopes: ['user:base', 'file:all:read', 'file:all:write'],
width: width,
height: height
}
const url = 'https://openapi.alipan.com/oauth/authorize/qrcode'
const resp = await AliHttp.Post(url, postData, '', '')
if (AliHttp.IsSuccess(resp.code)) {
return resp.body.qrCodeUrl
} else {
message.error('获取二维码失败,请重新输入')
return ''
}
}
static async OpenApiQrCodeStatus(qrCodeUrl: string): Promise<any> {
const resp = await AliHttp.Get(qrCodeUrl + '/status', '')
const statusJudge = (status: string) => {
switch (status) {
case 'WaitLogin':
return { type: 'info', tips: '状态:等待扫码登录' }
case 'ScanSuccess':
return { type: 'warning', tips: '状态:扫码成功,点击允许' }
case 'LoginSuccess':
return { type: 'success', tips: '状态:登录成功' }
case 'QRCodeExpired':
return { type: 'error', tips: '状态:二维码过期,点击刷新' }
default:
return { type: 'error', tips: '状态:二维码过期,点击刷新' }
}
}
if (AliHttp.IsSuccess(resp.code)) {
let statusCode = resp.body.status
let statusData = statusJudge(statusCode)
return {
authCode: statusCode === 'LoginSuccess' ? resp.body.authCode : '',
statusCode: statusCode,
statusType: statusData.type || '',
statusTips: statusData.tips || ''
}
} else {
message.error('获取二维码状态失败[' + resp.body?.message + '],请检查配置')
}
return false
}
static async OpenApiLoginByAuthCode(token: ITokenInfo, client_id: string, client_secret: string, authCode: string, issave: boolean = false): Promise<any> {
// 构造请求体
const postData: any = {
code: authCode,
grant_type: 'authorization_code',
client_id: client_id,
client_secret: client_secret
}
const url = 'https://openapi.alipan.com/oauth/access_token'
const resp = await AliHttp.Post(url, postData, '', '')
if (AliHttp.IsSuccess(resp.code)) {
const { access_token, refresh_token, token_type, expires_in } = resp.body
token.open_api_token_type = token_type
token.open_api_access_token = access_token
token.open_api_refresh_token = refresh_token
token.open_api_expires_in = Date.now() + expires_in * 1000
if (issave) {
window.WebUserToken({
user_id: token.user_id,
name: token.user_name,
access_token: token.access_token,
open_api_access_token: token.open_api_access_token,
refresh: true
})
UserDAL.SaveUserToken(token)
}
return true
} else {
if (resp.body?.code === 'InvalidCode') {
message.error('授权码无效, 请检查配置')
} else {
message.error('登录失败[' + resp.body?.message + ']')
}
}
return false
}
static async ApiUserInfo(token: ITokenInfo): Promise<boolean> {
if (!token.user_id) return false
const url = 'v2/databox/get_personal_info'
if (!token.user_id || !token.access_token_v2) return false
const url = 'adrive/v1.0/user/getDriveInfo'
const spaceUrl = 'adrive/v1.0/user/getSpaceInfo'
const postData = ''
const resp = await AliHttp.Post(url, postData, token.user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
token.used_size = resp.body.personal_space_info.used_size
token.total_size = resp.body.personal_space_info.total_size
token.spu_id = resp.body.personal_rights_info.spu_id
token.is_expires = resp.body.personal_rights_info.is_expires
token.name = resp.body.personal_rights_info.name
const spaceResp = await AliHttp.Post(spaceUrl, postData, token.user_id, '')
if (AliHttp.IsSuccess(spaceResp.code)) {
token.used_size = spaceResp.body.personal_space_info.used_size
token.total_size = spaceResp.body.personal_space_info.total_size
token.spaceinfo = humanSize(token.used_size) + ' / ' + humanSize(token.total_size)
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserInfo err=' + (resp.code || ''), resp.body)
}
return false
}
static async ApiUserDriveInfo(token: ITokenInfo): Promise<boolean> {
if (!token.user_id) return false
let url = ''
let need_open_api = false
if (need_open_api) {
url = 'https://openapi.alipan.com/adrive/v1.0/user/getDriveInfo'
} else {
url = 'https://user.alipan.com/v2/user/get'
DebugLog.mSaveWarning('getSpaceInfo err=' + (resp.code || ''))
}
const resp = await AliHttp.Post(url, {}, token.user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
token.default_drive_id = resp.body.default_drive_id
token.backup_drive_id = resp.body.backup_drive_id || resp.body.default_drive_id
token.resource_drive_id = resp.body.resource_drive_id
token.sbox_drive_id = resp.body.sbox_drive_id || ''
token.phone = resp.body.user_name.replace('***', '')
token.spu_id = ''
token.is_expires = resp.body.status === 'enabled'
token.name = resp.body.nick_name===''?resp.body.phone:resp.body.nick_name
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserDriveInfo err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserInfo err=' + (resp.code || ''))
}
return false
}
static async ApiUserSign(token: ITokenInfo): Promise<number> {
if (!token.user_id) return -1
const signUrl = 'https://member.alipan.com/v1/activity/sign_in_list'
const signUrl = 'https://member.aliyundrive.com/v1/activity/sign_in_list'
const signResp = await AliHttp.Post(signUrl, {}, token.user_id, '')
// console.log(JSON.stringify(resp))
if (AliHttp.IsSuccess(signResp.code)) {
@@ -341,7 +191,7 @@ export default class AliUser {
}
let reward = '无奖励'
if (!sign_data['isReward']) {
const rewardUrl = 'https://member.alipan.com/v1/activity/sign_in_reward'
const rewardUrl = 'https://member.aliyundrive.com/v1/activity/sign_in_reward'
const rewardResp = await AliHttp.Post(rewardUrl, { signInDay: signInCount }, token.user_id, '')
if (AliHttp.IsSuccess(rewardResp.code)) {
if (!rewardResp.body || !rewardResp.body.result || !rewardResp.body.success) {
@@ -349,10 +199,10 @@ export default class AliUser {
return -1
}
const result = rewardResp.body.result
reward = `${result['notice']}`
reward = `获得${result['name']} - ${result['description']}`
}
} else {
reward = `${sign_data['reward']['notice']}`
reward = `获得${sign_data['reward']['name']} - ${sign_data['reward']['description']}`
}
message.info(`${token.nick_name || token.user_name}】本月累计签到${signInCount}次,本次签到 ${reward}`)
return parseInt(sign_data['calendarDay'])
@@ -363,47 +213,21 @@ export default class AliUser {
}
static async ApiUserRewardSpace(user_id: string, gift_code: string) {
if (!user_id) return false
const url = 'https://member.alipan.com/v1/users/rewards'
const postData = { code: gift_code }
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
if (!resp.body || !resp.body.result) {
return { status: false, message: resp.body?.message }
}
if (!resp.body.success) {
return { status: false, message: resp.body?.message }
} else {
return { status: true, message: resp.body.result?.message }
}
} else {
return { status: false, message: resp.body?.message }
}
}
static async ApiUserVip(token: ITokenInfo): Promise<boolean> {
if (!token.user_id) return false
const url = 'business/v1.0/users/vip/info'
const url = 'adrive/v1.0/user/getVipInfo'
const postData = {}
const resp = await AliHttp.Post(url, postData, token.user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
let vipList = resp.body.vipList || []
vipList = vipList.sort((a: any, b: any) => b.expire - a.expire)
if (vipList.length > 0 && new Date(vipList[0].expire * 1000) > new Date()) {
token.vipname = vipList[0].name
token.vipIcon = resp.body.mediumIcon
token.vipexpire = humanDateTime(vipList[0].expire)
token.vipname = resp.body.identity
if (resp.body.expire && new Date(resp.body.expire * 1000) > new Date()) {
token.vipexpire = humanDateTime(resp.body.expire)
} else {
token.vipname = '免费用户'
token.vipIcon = ''
token.vipexpire = ''
token.vipexpire = '';
}
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserPic err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserPic err=' + (resp.code || ''))
}
return false
}
@@ -412,15 +236,13 @@ export default class AliUser {
static async ApiUserPic(token: ITokenInfo): Promise<boolean> {
if (!token.user_id) return false
const url = 'adrive/v1/user/albums_info'
const postData = {}
const resp = await AliHttp.Post(url, postData, token.user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
token.pic_drive_id = resp.body.data.driveId
return true
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserPic err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserPic err=' + (resp.code || ''))
}
return false
}
@@ -428,13 +250,11 @@ export default class AliUser {
static async ApiUserDriveDetails(user_id: string): Promise<IAliUserDriveDetails> {
const detail: IAliUserDriveDetails = {
album_drive_used_size: 0,
backup_drive_used_size: 0,
default_drive_used_size: 0,
drive_total_size: 0,
drive_used_size: 0,
drive_total_size: 0,
default_drive_used_size: 0,
album_drive_used_size: 0,
note_drive_used_size: 0,
resource_drive_used_size: 0,
sbox_drive_used_size: 0,
share_album_drive_used_size: 0
}
@@ -443,17 +263,15 @@ export default class AliUser {
const postData = '{}'
const resp = await AliHttp.Post(url, postData, user_id, '')
if (AliHttp.IsSuccess(resp.code)) {
detail.album_drive_used_size = resp.body.album_drive_used_size || 0
detail.backup_drive_used_size = resp.body.backup_drive_used_size || 0
detail.default_drive_used_size = resp.body.default_drive_used_size || 0
detail.drive_total_size = resp.body.drive_total_size || 0
detail.drive_used_size = resp.body.drive_used_size || 0
detail.drive_total_size = resp.body.drive_total_size || 0
detail.default_drive_used_size = resp.body.default_drive_used_size || 0
detail.album_drive_used_size = resp.body.album_drive_used_size || 0
detail.note_drive_used_size = resp.body.note_drive_used_size || 0
detail.resource_drive_used_size = resp.body.resource_drive_used_size || 0
detail.sbox_drive_used_size = resp.body.sbox_drive_used_size || 0
detail.share_album_drive_used_size = resp.body.share_album_drive_used_size || 0
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserDriveDetails err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserDriveDetails err=' + (resp.code || ''))
}
return detail
}
@@ -462,9 +280,9 @@ export default class AliUser {
if (!user_id) return 0
const token = await UserDAL.GetUserTokenFromDB(user_id)
if (!token) return 0
const url = 'adrive/v3/file/search'
const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search'
const postData = {
drive_id_list: [token.backup_drive_id, token.resource_drive_id],
drive_id: token?.default_drive_id,
marker: '',
limit: 1,
all: false,
@@ -477,8 +295,8 @@ export default class AliUser {
try {
if (AliHttp.IsSuccess(resp.code)) {
return resp.body.total_count || 0
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserDriveFileCount err=' + category + ' ' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserDriveFileCount err=' + category + ' ' + (resp.code || ''))
}
} catch (err: any) {
DebugLog.mSaveDanger('ApiUserDriveFileCount' + category, err)
@@ -520,8 +338,8 @@ export default class AliUser {
} as IAliUserDriveCapacity)
}
result = result.sort((a, b) => a.latest_receive_time.localeCompare(b.latest_receive_time))
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiUserCapacityDetails err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiUserCapacityDetails err=' + (resp.code || ''))
}
return result
}

View File

@@ -1,4 +1,4 @@
import { useFootStore, useSettingStore } from '../store'
import { ITokenInfo, useFootStore } from '../store'
import UserDAL from '../user/userdal'
import DebugLog from '../utils/debuglog'
import { Sleep } from '../utils/format'
@@ -9,45 +9,38 @@ import { IAliBatchResult } from './models'
import { SHA256 } from 'crypto-js'
import { ecdsaSign, publicKeyCreate } from 'secp256k1'
import { IAliFileItem, IAliGetFileModel } from './alimodels'
import { decodeName, encodeName } from '../module/flow-enc/utils'
import path from 'path'
import mime from 'mime-types'
import { getEncPassword, getEncType } from '../utils/proxyhelper'
export function GetDriveID(user_id: string, drive: string): string {
export declare type Drive = 'pan' | 'pic' | 'safe'
export function GetDriveID(user_id: string, drive: Drive): string {
const token = UserDAL.GetUserToken(user_id)
if (token) {
if (drive.includes('backup')) {
return token.backup_drive_id
} else if (drive.includes('resource')) {
return token.resource_drive_id
} else if (drive.includes('pic')) {
switch (drive) {
case 'pan':
return token.default_drive_id
case 'pic':
return token.pic_drive_id
} else if (drive.includes('safe')) {
case 'safe':
return token.default_sbox_drive_id
}
}
return ''
}
export function GetDriveType(user_id: string, drive_id: string): any {
const token = UserDAL.GetUserToken(user_id)
export function GetDriveID2(token: ITokenInfo, driveName: string): string {
if (token) {
switch (drive_id) {
case token.backup_drive_id:
return { title: '备份盘', name: 'backup', key: 'backup_root' }
case token.resource_drive_id:
return { title: '资源盘', name: 'resource', key: 'resource_root' }
case token.pic_drive_id:
return { title: '全部相册', name: 'pic', key: 'pic_root' }
case token.default_sbox_drive_id:
return { title: '安全盘', name: 'safe', key: 'safe_root' }
default:
return { title: '备份盘', name: 'backup', key: 'backup_root' }
switch (driveName) {
case 'pan':
return token.default_drive_id
case 'pic':
return token.pic_drive_id
case 'safe':
return token.default_sbox_drive_id
}
}
return { title: '备份盘', name: 'backup', key: 'backup_root' }
return driveName
}
export function GetSignature(nonce: number, user_id: string, deviceId: string) {
@@ -75,69 +68,6 @@ export function GetSignature(nonce: number, user_id: string, deviceId: string) {
return { signature, publicKey }
}
export function EncodeEncName(user_id: string, name: string, isDir: boolean, encType: string, inputpassword: string = '') {
let settingStore = useSettingStore()
if (encType && settingStore.securityEncFileName) {
// 加密名称
const splitFolder = name.split('/')
const securityPassword = getEncPassword(user_id, encType, inputpassword)
const securityEncType = settingStore.securityEncType
if (!isDir) {
return splitFolder.map(name => {
let plainName = ''
let basename = path.basename(name)
let extname = path.extname(name)
if (settingStore.securityEncFileNameHideExt) {
plainName = basename
extname = ''
} else {
plainName = basename.replace(extname, '')
}
return encodeName(securityPassword, securityEncType, plainName) + extname
}).join('/')
} else {
return splitFolder.map(name => encodeName(securityPassword, securityEncType, name)).join('/')
}
} else {
return name
}
}
export function DecodeEncName(user_id: string, item: IAliFileItem | IAliGetFileModel, inputpassword: string = '') {
// 自动解密文件名
let settingStore = useSettingStore()
const securityFileNameAutoDecrypt = settingStore.securityFileNameAutoDecrypt
const securityEncType = settingStore.securityEncType
let ext = ''
let mine_type = item.mime_type
if ('file_extension' in item) {
ext = item.file_extension || ''
} else if ('ext' in item) {
ext = item.ext
}
let name = item.name
let description = item.description
let need_decode = description && description.includes('xbyEncrypt')
if (need_decode && securityFileNameAutoDecrypt) {
let encType = getEncType(item)
let filename = item.name.replace(ext ? '.' + ext : '', '')
let password = getEncPassword(user_id, encType, inputpassword)
let realName = decodeName(password, securityEncType, filename) || item.name
if (ext) {
name = realName + '.' + ext
} else if (path.extname(realName)) {
// 修复加密后的扩展
name = realName
ext = path.extname(realName).replace('.', '')
mine_type = mime.lookup(ext) || 'application/oct-stream'
} else {
name = realName
}
return { name: name, mine_type, ext }
}
return { name: item.name, mine_type, ext }
}
async function _ApiBatch(postData: string, user_id: string, share_token: string, result: IAliBatchResult): Promise<void> {
if (!user_id && !share_token) return
const url = 'v2/batch'
@@ -152,46 +82,24 @@ async function _ApiBatch(postData: string, user_id: string, share_token: string,
if (respi.body && respi.body.async_task_id) {
result.async_task.push({
drive_id: respi.body.drive_id || '',
file_id: respi.id,
task_id: respi.body.async_task_id,
newdrive_id: respi.body.drive_id || '',
newfile_id: respi.body.file_id || ''
})
result.async_task.push({ drive_id: respi.body.drive_id || '', file_id: respi.id, task_id: respi.body.async_task_id, newdrive_id: respi.body.drive_id || '', newfile_id: respi.body.file_id || '' })
} else if (respi.body && respi.body.share_id && respi.body.share_msg) {
result.reslut.push({
id: respi.id,
share_id: respi.body.share_id,
share_pwd: respi.body.share_pwd || '',
share_url: respi.body.share_url,
expiration: respi.body.expiration || '',
share_name: respi.body.share_name || ''
})
result.reslut.push({ id: respi.id, share_id: respi.body.share_id, share_pwd: respi.body.share_pwd || '', share_url: respi.body.share_url, expiration: respi.body.expiration || '', share_name: respi.body.share_name || '' })
} else if (respi.body) {
result.reslut.push({
id: respi.id,
file_id: respi.body.file_id,
name: respi.body.name || '',
body: respi.body
})
result.reslut.push({ id: respi.id, file_id: respi.body.file_id, name: respi.body.name || '', body: respi.body })
} else if (respi.id) {
result.reslut.push({ id: respi.id, file_id: respi.id })
}
} else {
const respi = responses[i]
const logmsg = (respi.body.code || '') + ' ' + (respi.body.message || '')
if (!logmsg.includes('File under sync control')) DebugLog.mSaveDanger(logmsg)
if (respi.body && respi.body.code) result.error.push({
id: respi.body.id || respi.id,
code: respi.body.code,
message: respi.body.message
})
if (logmsg.includes('File under sync control') == false) DebugLog.mSaveDanger(logmsg)
if (respi.body && respi.body.code) result.error.push({ id: respi.body.id || respi.id, code: respi.body.code, message: respi.body.message })
}
}
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('_ApiBatch err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('_ApiBatch err=' + (resp.code || ''))
}
}
@@ -219,7 +127,8 @@ export async function ApiBatch(title: string, batchList: string[], user_id: stri
}
if (allTask.length >= 3) {
await Promise.all(allTask).catch()
await Promise.all(allTask).catch(() => {})
allTask = []
if (title != '') message.loading(title + ' 执行中...(' + result.count.toString() + ')', 60, loadingKey)
}
@@ -228,8 +137,7 @@ export async function ApiBatch(title: string, batchList: string[], user_id: stri
postData += '],"resource":"file"}'
allTask.push(_ApiBatch(postData, user_id, share_token, result))
}
if (allTask.length > 0) await Promise.all(allTask).catch(() => {
})
if (allTask.length > 0) await Promise.all(allTask).catch(() => {})
if (result.async_task.length > 0) {
if (title != '' || share_token != '') message.warning(title + ' 异步执行中(' + result.async_task.length + ')', 2, loadingKey)
@@ -295,13 +203,7 @@ export function ApiBatchMaker(url: string, idList: string[], bodymake: (file_id:
const id = idList[i]
if (batchSet.has(id)) continue
batchSet.add(id)
batchList.push(JSON.stringify({
body: bodymake(id),
headers: { 'Content-Type': 'application/json' },
id: id,
method: 'POST',
url
}))
batchList.push(JSON.stringify({ body: bodymake(id), headers: { 'Content-Type': 'application/json' }, id: id, method: 'POST', url }))
}
batchSet.clear()
return batchList
@@ -315,13 +217,7 @@ export function ApiBatchMaker2(url: string, idList: string[], namelist: string[]
const id = idList[i]
if (batchSet.has(id)) continue
batchSet.add(id)
batchList.push(JSON.stringify({
body: bodymake(id, namelist[i]),
headers: { 'Content-Type': 'application/json' },
id: id,
method: 'POST',
url
}))
batchList.push(JSON.stringify({ body: bodymake(id, namelist[i]), headers: { 'Content-Type': 'application/json' }, id: id, method: 'POST', url }))
}
batchSet.clear()
return batchList
@@ -368,8 +264,8 @@ export async function ApiGetAsyncTask(user_id: string, async_task_id: string): P
message.warning('操作部分成功 ' + resp.body.message?.replace('ErrQuotaExhausted', '网盘空间已满') || '', 5)
return 'error'
}
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiGetAsyncTask err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiGetAsyncTask err=' + (resp.code || ''))
}
return 'error'
}
@@ -388,8 +284,8 @@ export async function ApiGetAsyncTaskUnzip(user_id: string, drive_id: string, fi
message.warning('操作部分成功 ' + resp.body.message?.replace('ErrQuotaExhausted', '网盘空间已满') || '', 5)
return 'error'
}
} else if (!AliHttp.HttpCodeBreak(resp.code)) {
DebugLog.mSaveWarning('ApiGetAsyncTaskUnzip err=' + (resp.code || ''), resp.body)
} else {
DebugLog.mSaveWarning('ApiGetAsyncTaskUnzip err=' + (resp.code || ''))
}
return 'error'
}

View File

@@ -285,8 +285,11 @@ body[arco-theme='dark'] .fileitem.focus {
.cff5722 {
color: #ff5722;
}
.ce74c3c {
color: #e74c3c;
.c5b89b8 {
color: #5b89b8;
}
.cvideo {
color: #5b89b8;
}
.griditemparent {
@@ -315,7 +318,7 @@ body[arco-theme='dark'] .fileitem.focus {
}
.griditem {
border-radius: 17.2px;
border-radius: 8px;
position: relative;
height: 100%;
border: 1px dotted transparent;
@@ -418,126 +421,6 @@ body[arco-theme='dark'] .griditem .select:hover {
overflow: hidden;
}
.griditem.movie {
width: 200px;
height: 320px;
flex-shrink: 0;
flex-grow: 0;
}
.movieicon {
left: 2px;
right: 2px;
top: -10px;
position: absolute;
text-align: center;
height:100% !important;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
flex-grow: 0;
}
.movieicon i {
font-size: 56px;
opacity: 0.75;
}
.movieicon .iconfile-folder {
color: #ffb74d !important;
}
.movieicon img {
display: block;
width: 170px !important;
height: 240px !important;
border-radius: 20px;
position: relative;
border: 1px solid rgba(132, 133, 141, 0.16);
}
.movieicon .playicon {
cursor: pointer;
font-size: 20px;
height: 28px;
width: 28px;
background: rgba(37, 38, 43, 0.36);
backdrop-filter: blur(10px);
border-radius: 50%;
color: rgb(255, 255, 255);
}
.movieicon .playicon svg {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
.movieprogress {
width: 100%;
text-align: center;
font-size: 14px;
line-height: 18px;
max-width: 100%;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
overflow-wrap: break-word;
/*margin-bottom: 2px;*/
color: var(--color-text-1);
padding: 0 8px;
position: absolute;
bottom: 45px;
font-weight: bold;
}
.moviename {
width: 100%;
text-align: center;
font-size: 14px;
line-height: 18px;
max-width: 100%;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/*overflow: hidden;*/
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
overflow-wrap: normal;
white-space: nowrap;
/* margin-bottom: 2px; */
color: var(--color-text-1);
padding: 0 8px;
position: absolute;
bottom: 25px;
font-weight: bold;
}
.moviename > div {
cursor: pointer;
text-align: center;
word-break: break-all;
width: 150px;
margin-left: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.moviename > div:hover {
color: #3482f0;
}
.movieinfo {
width: 100%;
text-align: center;
font-size: 12px;
color: var(--color-text-3);
position: absolute;
bottom: 0px;
}
.gridname {
width: 100%;
text-align: center;

View File

@@ -3,9 +3,8 @@ html {
min-height: 100vh;
overflow: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif, '黑体', '宋体', '方正黑体', '方正准圆';
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;
min-width: 100vw;
min-height: 100vh;
overflow: hidden;
@@ -13,27 +12,20 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
background: transparent;
width: 10px;
height: 10px;
cursor: pointer;
}
::-webkit-scrollbar-thumb {
background: var(--color-fill-3) content-box;
border: 2px solid transparent;
background: rgb(201, 201, 202);
border-radius: 5px;
cursor: default;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--color-fill-4);
}
::-webkit-scrollbar-thumb:vertical {
width: 9px;
}
::-webkit-scrollbar-thumb:horizontal {
height: 9px;
}
@@ -57,28 +49,23 @@ body {
::-webkit-scrollbar-button {
display: none;
}
.flex {
display: flex;
flex: 100% 1 1;
flex-direction: row;
}
.flexauto {
flex-grow: 1 !important;
}
.flexnoauto {
flex-grow: 0 !important;
flex-shrink: 0 !important;
flex-basis: unset !important;
}
.q-electron-drag {
user-select: none;
-webkit-app-region: drag;
}
.q-electron-drag--exception,
.q-electron-drag button,
.q-electron-drag ul,
@@ -87,7 +74,6 @@ body {
.q-electron-drag .arco-avatar {
-webkit-app-region: no-drag;
}
* {
user-select: none;
-webkit-user-drag: none;
@@ -97,7 +83,6 @@ body {
-webkit-font-smoothing: antialiased;
font-feature-settings: 'tnum', 'zero', 'liga' 0;
}
#app {
height: 100vh;
position: fixed;
@@ -106,7 +91,6 @@ body {
right: 0;
bottom: 0;
}
body {
--arcoblue-1: 232, 240, 255 !important;
--arcoblue-2: 205, 220, 255 !important;
@@ -129,7 +113,6 @@ body {
--color-text-1: var(--color-neutral-9) !important;
--color-mask-bg: rgba(29, 33, 41, 0.3) !important;
}
body[arco-theme='dark'] {
color-scheme: dark;
caret-color: rgba(255, 255, 255, 0.65);
@@ -157,7 +140,6 @@ body[arco-theme='dark'] {
background: #f0f5ff;
color: var(--color-text-2);
}
.arco-layout-sider .arco-menu-light .arco-menu-item .arco-menu-icon,
.arco-layout-sider .arco-menu-light .arco-menu-item:hover .arco-menu-icon,
.arco-layout-sider .arco-menu-light .arco-menu-item.arco-menu-selected .arco-menu-icon {
@@ -171,11 +153,9 @@ body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item,
body[arco-theme='dark'] .arco-card {
background: #23232e;
}
body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item:hover {
background: var(--color-fill-2);
}
body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item.arco-menu-selected {
background: #444457;
}
@@ -183,11 +163,9 @@ body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item.arco
.arco-alert-error {
background-color: rgb(253, 237, 237) !important;
}
.arco-alert-error .arco-alert-icon svg {
color: rgb(239, 83, 80) !important;
}
.arco-alert-error .arco-alert-content {
color: rgb(112, 40, 39) !important;
}
@@ -195,11 +173,9 @@ body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item.arco
.arco-alert-warning {
background-color: rgb(255, 244, 229) !important;
}
.arco-alert-warning .arco-alert-icon svg {
color: rgb(255, 152, 0) !important;
}
.arco-alert-warning .arco-alert-content {
color: rgb(117, 69, 1) !important;
}
@@ -207,11 +183,9 @@ body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item.arco
.arco-alert-success {
background-color: rgb(237, 247, 237) !important;
}
.arco-alert-success .arco-alert-icon svg {
color: rgb(76, 175, 80) !important;
}
.arco-alert-success .arco-alert-content {
color: rgb(38, 87, 40) !important;
}
@@ -223,11 +197,9 @@ body[arco-theme='dark'] .arco-layout-sider .arco-menu-light .arco-menu-item.arco
body[arco-theme='dark'] .arco-alert-error {
background-color: rgb(63, 32, 32) !important;
}
body[arco-theme='dark'] .arco-alert-error .arco-alert-icon svg {
color: rgb(244, 67, 54) !important;
}
body[arco-theme='dark'] .arco-alert-error .arco-alert-content {
color: rgb(244, 199, 199) !important;
}
@@ -235,11 +207,9 @@ body[arco-theme='dark'] .arco-alert-error .arco-alert-content {
body[arco-theme='dark'] .arco-alert-warning {
background-color: rgb(66, 49, 20) !important;
}
body[arco-theme='dark'] .arco-alert-warning .arco-alert-icon svg {
color: rgb(255, 167, 38) !important;
}
body[arco-theme='dark'] .arco-alert-warning .arco-alert-content {
color: rgb(255, 226, 183) !important;
}
@@ -247,11 +217,9 @@ body[arco-theme='dark'] .arco-alert-warning .arco-alert-content {
body[arco-theme='dark'] .arco-alert-success {
background-color: rgb(39, 61, 42) !important;
}
body[arco-theme='dark'] .arco-alert-success .arco-alert-icon svg {
color: rgb(102, 187, 106) !important;
}
body[arco-theme='dark'] .arco-alert-success .arco-alert-content {
color: rgb(204, 232, 205) !important;
}
@@ -283,13 +251,11 @@ button1:focus {
.arco-modal {
width: auto !important;
}
.arco-modal.arco-modal-fullscreen {
width: 100vw !important;
height: 100vh !important;
border-radius: 0 !important;
}
.arco-modal-wrapper {
overflow: hidden !important;
}
@@ -298,7 +264,6 @@ body[arco-theme='dark'] .arco-modal {
background: #2d2d3b !important;
box-shadow: 0 2px 20px 0 rgb(0 0 0 / 45%) !important;
}
body[arco-theme='dark'] .arco-dropdown,
body[arco-theme='dark'] .arco-select-dropdown,
body[arco-theme='dark'] .arco-popover-popup-content {
@@ -317,11 +282,9 @@ body[arco-theme='dark'] .arco-popover-popup-content {
margin: 0 4px !important;
border-radius: 3px !important;
}
.arco-dropdown-option.danger {
color: rgb(255, 77, 79) !important;
}
.arco-dropdown-option.danger:hover,
.arco-dropdown-option.danger:active {
color: #fff !important;
@@ -350,7 +313,6 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
.arco-modal {
z-index: 1001;
}
.arco-modal-close-btn {
-webkit-app-region: no-drag;
position: absolute;
@@ -363,15 +325,12 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
line-height: 24px;
border-radius: 50%;
}
.arco-modal-close-btn .arco-icon-hover .arco-icon {
left: 6px;
}
.arco-modal-close-btn .arco-icon-hover::before {
display: none;
}
.arco-modal-close-btn .arco-icon-hover:hover {
background-color: var(--color-fill-2);
}
@@ -386,7 +345,6 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
user-select: none;
padding-left: 6px;
}
.toppanbtn {
display: flex;
flex-direction: row;
@@ -397,7 +355,6 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
margin-right: 12px;
user-select: none;
}
.toppanbtn:last-child {
margin-right: 0;
}
@@ -432,24 +389,19 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
border-right: none !important;
border-radius: 4px !important;
}
.toppanbtn .searchpan .arco-btn.arco-input-search-btn {
border-radius: 4px !important;
}
.toppanbtn .arco-input-search .arco-input-wrapper {
background: var(--color-bg-2);
border: none !important;
}
.toppanbtn .arco-btn.arco-btn-primary {
border-radius: 0 !important;
}
.toppanbtn .arco-btn.arco-btn-primary:hover {
background: rgb(var(--primary-5)) !important;
}
.toppanbtn .arco-btn.arco-btn-primary:active {
background: rgb(var(--primary-7)) !important;
}
@@ -457,7 +409,6 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
.toppanbtn > .arco-btn + .arco-btn {
border-left: 0 !important;
}
.toppanbtn > .arco-btn:not(:last-child) {
border-right: 0 !important;
}
@@ -497,47 +448,39 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
width: 130px;
transition: width 0.3s;
}
@media only screen and (max-width: 859px) {
.arco-input-wrapper {
padding-right: 6px !important;
padding-left: 6px !important;
}
}
@media only screen and (min-width: 820px) {
.toppanbtn .arco-input-search {
width: 140px;
}
}
@media only screen and (min-width: 860px) {
.toppanbtn .arco-input-search {
width: 160px;
}
}
@media only screen and (min-width: 960px) {
.toppanbtn .arco-input-search {
width: 180px;
}
.toppanbtn .arco-input-search.arco-input-focus {
width: 200px;
}
}
@media only screen and (min-width: 1000px) {
.toppanbtn .arco-input-search.arco-input-focus {
width: 220px;
}
}
@media only screen and (min-width: 1200px) {
.toppanbtn .arco-input-search {
width: 200px;
}
.toppanbtn .arco-input-search.arco-input-focus {
width: 240px;
}
@@ -546,12 +489,10 @@ body[arco-theme='dark'] .toppanbtn .arco-btn.danger:active {
body[arco-theme='dark'] .toppanbtn .arco-btn {
background: none !important;
}
body[arco-theme='dark'] .toppanbtn .arco-btn:hover,
body[arco-theme='dark'] .toppanbtn .arco-btn:active {
background: #4c4c61 !important;
}
body[arco-theme='dark'] .toppanbtn .arco-btn {
color: #ffffffd9;
background: #353544;
@@ -559,97 +500,6 @@ body[arco-theme='dark'] .toppanbtn .arco-btn {
border-color: #444457 !important;
box-shadow: 0 1px 5px rgb(0 0 0 / 20%), 0 2px 2px rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%) !important;
}
body[arco-theme='dark'] .toppanarea .cell {
color: rgba(211, 216, 241, 0.45);
}
.cell {
color: var(--color-text-3);
overflow: hidden;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
display: inline-block;
line-height: 18px;
min-height: 18px;
padding: 0 4px;
justify-content: center;
}
.cell.tiquma {
width: 60px;
font-size: 12px;
}
.cell.filesize {
font-size: 16px;
width: 86px;
text-align: right;
flex-shrink: 0;
flex-grow: 0;
margin-right: 16px;
}
.cell.count {
width: 70px;
font-size: 12px;
line-height: 14px;
text-align: center;
word-wrap: break-word;
word-break: keep-all;
}
.cellcount {
align-items: center;
margin-right: 16px;
}
.cellcount .arco-badge .arco-badge-status-text {
margin-left: 4px;
color: var(--color-text-3);
line-height: 26px;
}
.cell.sharetime {
width: 80px;
font-size: 12px;
line-height: 14px;
text-align: right;
word-wrap: break-word;
word-break: keep-all;
}
.cell.sharetime.active {
color: rgb(217, 48, 37);
}
.cell.sharestate {
width: 60px;
font-size: 12px;
margin: 0 5px 0 5px;
}
.cell.sharestate.active {
color: rgb(var(--primary-6));
}
.cell.sharestate.forbidden {
color: rgb(217, 48, 37);
}
.cell.sharestate.deleted {
text-decoration: line-through;
}
.cell.p5 {
width: 5px;
}
.cell.pr {
width: 12px;
}
.toppanarea {
box-sizing: border-box;
display: flex;
@@ -661,7 +511,6 @@ body[arco-theme='dark'] .toppanarea .cell {
border-top: 1px solid #e5e8ed99;
border-bottom: 1px solid #e5e8ed99;
}
body[arco-theme='dark'] .toppanarea {
border-top: 1px solid #e5e8ed22;
border-bottom: 1px solid #e5e8ed22;
@@ -672,7 +521,6 @@ body[arco-theme='dark'] .toppanarea {
align-items: center;
height: 38px;
}
.toppanarea .selectInfo {
height: 38px;
line-height: 38px;
@@ -681,25 +529,14 @@ body[arco-theme='dark'] .toppanarea {
white-space: nowrap;
word-break: keep-all;
}
.toppanarea .cell {
font-size: 12px;
}
.toppanarea .cell.active,
.toppanarea .cell.active .iconxia {
color: rgb(var(--primary-6));
}
.toppanarea .cell.order {
cursor: pointer;
}
.toppanarea .cell.order:hover {
color: rgb(var(--primary-6));
}
.select,
.select > button {
min-width: 34px !important;
@@ -711,20 +548,17 @@ body[arco-theme='dark'] .toppanarea {
line-height: 34px !important;
border: none !important;
}
.select:hover,
.select:active,
.select.active {
background: rgba(99, 125, 255, 0.1) !important;
color: rgb(var(--primary-6)) !important;
}
.select .iconfont {
font-size: 24px;
line-height: 34px;
color: rgb(var(--primary-6)) !important;
}
.select .iconfont.iconrpic {
opacity: 0.8;
}
@@ -734,8 +568,8 @@ body[arco-theme='dark'] .toppanarea {
line-height: 48px;
}
.vermodal {
height: 50vh;
width: 520px;
height: 200px;
width: 400px;
flex-direction: column;
justify-content: center;
align-items: center;
@@ -762,11 +596,9 @@ body[arco-theme='dark'] .toppanarea {
.xbyleft {
box-shadow: 2px 0 8px 0 var(--leftshadow) !important;
}
.xbyright {
padding-left: 16px;
}
.headdesc {
box-sizing: border-box;
height: 40px;
@@ -783,7 +615,6 @@ body[arco-theme='dark'] .toppanarea {
.xbyleftmenu.arco-menu .arco-menu-inner {
padding: 0 2px 0 0 !important;
}
.xbyleftmenu.arco-menu .arco-menu-item {
padding: 0 8px;
margin-bottom: 2px;
@@ -801,14 +632,12 @@ body[arco-theme='dark'] .toppanarea {
transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1);
content: '';
}
.xbyleftmenu.arco-menu .arco-menu-item.arco-menu-selected::after {
transform: scaleY(1);
opacity: 1;
transition: transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
border-right: 2px solid #637dff;
}
.xbyleftmenu.arco-menu .arco-menu-item .arco-menu-icon {
width: 30px;
text-align: left;
@@ -818,7 +647,6 @@ body[arco-theme='dark'] .toppanarea {
flex-shrink: 0;
flex-grow: 0;
}
.xbyleftmenu.arco-menu .arco-menu-item .arco-menu-icon .iconfont {
font-size: 20px;
}
@@ -882,7 +710,6 @@ body[arco-theme='dark'] .toppanarea {
.arco-card {
border-radius: 6px !important;
}
.arco-divider-text {
border-radius: 4px !important;
}

View File

@@ -1,14 +1,12 @@
* {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-family: "PingFang SC", 'Microsoft YaHei', Arial, serif;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
/*font-size: 16px;*/
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@media screen and (max-width: 500px) {
* {
font-size: 17px;

View File

@@ -1,52 +0,0 @@
import axios from 'axios'
import { performance } from 'perf_hooks'
let QPS = 5
// 校准本地和服务端之间的时间差
let OFFSET = 250
// 间隔时间
let INTERVAL = 1000
const qpsMap = new Map()
const qpsController = () => async (config: any) => {
if (config.url.indexOf('aliyundrive') < 0 && config.url.indexOf('alipan') < 0) return config
const now = Math.trunc(performance.timeOrigin + performance.now())
let { count, ts } = qpsMap.get(config.url) || { count: 1, ts: now }
// 通过位运算实现取整,提高效率
if ((now / INTERVAL) >> 0 <= (ts / INTERVAL) >> 0) {
// 如果当前时间 ≤ Map中该接口的ts时间说明前面已经有超过并发后在等待的请求了
// 只比较秒忽略毫秒因为QPS是以秒为周期计算的即每秒多少个请求数
if (count < QPS) {
// 如果当前url的请求数没有达到QPS的限制则计数器+1
count++
} else {
// 否则重置计数器同时将时间戳设置为当前ts的下一整秒
// 这里需要将ts设置为当前ts的下一秒而不是当前时间因为当前ts可能已经远大于当前时间了
ts = INTERVAL * Math.ceil(ts / INTERVAL + 1)
count = 1
}
} else {
// 否则当前时间大于ts说明已经没有排队的请求了可能有未完成的但是都已经请求了
// 则将当前ts重置
ts = now
count = 1
}
qpsMap.set(config.url, { count, ts })
// 计算休眠时间:
// 由于本地服务器和远程服务器之间可能存在时间差会发生这种情况:
// 前5个请求在10:00:00.200时发送过去后此时本地时间可能到了10:00:00.900到来的第六请求由于超出了QPS=5的限制会休眠100ms
// 但是由于本地和服务端时间差的问题第六个休眠100ms后发送了请求服务端的时间可能才是10:00:00.950导致了QPS超限报错
// 所以这里添加一个OFFSET偏移值来纠正本地和服务端之间的时间差问题默认为50ms若出现QPS超限请酌情增大此值
let sleep = ts - now
sleep = sleep > 0 ? sleep + OFFSET : 0
// 让当前的请求睡一会儿再请求
if (sleep > 0) {
await new Promise<void>(resolve => setTimeout(() => resolve(), sleep))
}
return config
}
axios.interceptors.request.use(qpsController())
axios.defaults.withCredentials = false
export default axios

View File

@@ -1,10 +0,0 @@
export default class Config {
static referer = 'https://www.alipan.com/drive'
static downAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4577.63 Safari/537.36'
static userAgent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.12.0 Chrome/108.0.5359.215 Electron/22.3.24 Safari/537.36'
static loginUrl = 'https://auth.aliyundrive.com/v2/oauth/authorize?login_type=custom&response_type=code&redirect_uri=https%3A%2F%2Fwww.aliyundrive.com%2Fsign%2Fcallback&client_id=25dzX3vbYqktVxyX&state=%7B%22origin%22%3A%22https%3A%2F%2Fwww.aliyundrive.com%2F%22%7D'
static loginUrlAccount = 'https://passport.aliyundrive.com/mini_login.htm?lang=zh_cn&appName=aliyun_drive&appEntrance=web&styleType=auto&bizParams=&notLoadSsoView=false&notKeepLogin=false&isMobile=false&&rnd=0.1100330129139'
static tmdbProxyUrl = 'http://api.themoviedb.org'
static client_id = ''
static client_secret = ''
}

View File

@@ -1,24 +1,19 @@
import { IAliGetFileModel } from '../aliapi/alimodels'
import path from 'path'
import TreeStore from '../store/treestore'
import { useDownedStore, useDowningStore, useFootStore, useSettingStore, useUserStore } from '../store'
import { useUserStore, useSettingStore, useDownedStore, useDowningStore, useFootStore } from '../store'
import { ClearFileName } from '../utils/filehelper'
import {
AriaAddUrl,
AriaConnect,
AriaDeleteList,
AriaConnect, AriaDeleteList,
AriaGetDowningList,
AriaHashFile,
AriaStopList,
AriaHashFile, AriaStopList,
FormatAriaError,
IsAria2cRemote
} from '../utils/aria2c'
import { humanSize, humanSizeSpeed } from '../utils/format'
import { Howl } from 'howler'
import DBDown from '../utils/dbdown'
import fsPromises from 'fs/promises'
import { DecodeEncName } from '../aliapi/utils'
import { getEncType } from '../utils/proxyhelper'
export interface IStateDownFile {
DownID: string
@@ -61,16 +56,13 @@ export interface IStateDownInfo {
sizestr: string
icon: string
isDir: boolean
encType: string
sha1: string
crc64: string
m3u8_total_file_nums?:number
m3u8_parent_file_name?:string
}
export interface IAriaDownProgress {
gid: string
status: string
@@ -83,8 +75,13 @@ export interface IAriaDownProgress {
/** 存盘的时机:默认 10 时进行 */
let SaveTimeWait = 0
/** 下载正在执行中的数据 */
export let DownInExeMap = new Map<string, IStateDownFile>()
/** 下载正在队列中的数据 */
export let DownInQueues: IStateDownFile[] = []
const sound = new Howl({
src: ['./audio/download_finished.mp3'], // 音频文件路径
src: ['./audio/down_finished.mp3'], // 音频文件路径
autoplay: false, // 是否自动播放
volume: 1.0 // 音量,范围 0.0 ~ 1.0
})
@@ -147,9 +144,8 @@ export default class DownDAL {
const userID = useUserStore().user_id
const settingStore = useSettingStore()
if (savePath.endsWith('/') || savePath.endsWith('\\')) {
savePath = savePath.substr(0, savePath.length - 1)
}
if (savePath.endsWith('/')) savePath = savePath.substr(0, savePath.length - 1)
if (savePath.endsWith('\\')) savePath = savePath.substr(0, savePath.length - 1)
const downlist: IStateDownFile[] = []
const dTime = Date.now()
@@ -160,7 +156,7 @@ export default class DownDAL {
const sep = settingStore.ariaSavePath.indexOf('/') >= 0 ? '/' : '\\'
for (let f = 0; f < fileList.length; f++) {
const file = fileList[f]
const name = ClearFileName(DecodeEncName(userID, file).name)
const name = ClearFileName(file.name)
let fullPath = savePath
if (needPanPath) {
if (cPath != '' && cPid == file.parent_file_id) fullPath = cPath
@@ -169,7 +165,7 @@ export default class DownDAL {
const plist = TreeStore.GetDirPath(file.drive_id, file.parent_file_id)
for (let p = 0; p < plist.length; p++) {
const pName = ClearFileName(plist[p].name)
if (plist[p].file_id.includes('root')) continue
if (pName == '根目录') continue
if (path.join(cPath2, pName, name).length > 250) break
cPath2 = path.join(cPath2, pName)
}
@@ -189,6 +185,7 @@ export default class DownDAL {
let downloadurl = ''
let crc64 = ''
const downitem: IStateDownFile = {
DownID: userID + '|' + file.file_id,
Info: {
@@ -203,7 +200,6 @@ export default class DownDAL {
sizestr: file.sizeStr,
isDir: file.isDir,
icon: file.icon,
encType: getEncType(file),
sha1: '',
crc64: crc64
},
@@ -227,6 +223,7 @@ export default class DownDAL {
if (downitem.Info.ariaRemote && !downitem.Info.isDir) downitem.Info.icon = 'iconfont iconcloud-download'
downlist.push(downitem)
}
useDowningStore().mAddDownload({ downlist })
}
@@ -235,58 +232,109 @@ export default class DownDAL {
*/
static async aSpeedEvent() {
const downingStore = useDowningStore()
const downedStore = useDownedStore()
const settingStore = useSettingStore()
const isOnline = await AriaConnect()
if (isOnline && downingStore.ListDataRaw.length) {
await AriaGetDowningList()
if (isOnline) {
await AriaGetDowningList().catch(() => {
//
})
const DowningList = useDowningStore().ListDataRaw
let downingCount = 0
const ariaRemote = IsAria2cRemote()
const DowningList: IStateDownFile[] = downingStore.ListDataRaw
const timeThreshold = Date.now() - 60 * 1000
const downFileMax = settingStore.downFileMax
const shouldSkipDown = (Down: any) => {
return (
Down.IsCompleted ||
Down.IsStop ||
Down.IsDowning ||
(Down.IsFailed && timeThreshold <= Down.AutoTry)
)
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].Info.ariaRemote != ariaRemote) continue
if (DowningList[j].Down.IsDowning) {
downingCount++
}
let addDowningCount = 0
for (let i = 0; i < DowningList.length; i++) {
const DownItem = DowningList[i]
const { DownID, Info, Down } = DownItem
if (Info.ariaRemote !== ariaRemote) continue
if (Down.IsCompleted && Down.DownState === '已完成') {
// 将下载标记为已完成并添加到列表以供稍后处理
const completedDownId = `${Date.now()}_${Down.DownTime}`
// 删除已完成的下载并更新数据库
DowningList.splice(i, 1)
await DBDown.deleteDowning(DownID)
// 将已完成的下载添加到下载文件列表中
const downedData = JSON.parse(JSON.stringify({ DownID: completedDownId, Down, Info }))
downedStore.ListDataRaw.unshift({ DownID: completedDownId, Down, Info })
downedStore.mRefreshListDataShow(true)
await DBDown.saveDowned(completedDownId, downedData)
if (downedStore.ListSelected.has(completedDownId)) {
downedStore.ListSelected.delete(completedDownId)
}
// 移除Aria2已完成的任务
await AriaDeleteList([Info.GID])
i--
} else if ((addDowningCount + downingStore.ListDataDowningCount) < downFileMax && !shouldSkipDown(Down)) {
addDowningCount++
downingStore.mUpdateDownState(DownItem, 'start')
let state = await AriaAddUrl(DownItem)
downingStore.mUpdateDownState(DownItem, state)
if (DowningList[j].Down.IsCompleted && DowningList[j].Down.DownState === '已完成') {
downingStore.mSaveToDowned(DowningList[j].DownID)
j--
}
}
const time = Date.now() - 60 * 1000
for (let j = 0; j < DowningList.length; j++) {
if (downingCount >= settingStore.downFileMax) break
if (DowningList[j].Info.ariaRemote != ariaRemote) continue
const DownID = DowningList[j].DownID
const down = DowningList[j].Down
if (down.IsCompleted == false && down.IsStop == false && down.IsDowning == false) {
if (down.IsFailed == false || time > down.AutoTry) {
downingCount += 1
downingStore.mUpdateDownState({
DownID,
IsDowning: true,
DownSpeedStr: '',
DownState: '解析中',
DownTime: Date.now(),
FailedCode: 0,
FailedMessage: ''
})
AriaAddUrl(DowningList[j]).then((ret) => {
if (ret == 'downed') {
downingStore.mUpdateDownState({
DownID,
IsDowning: true,
IsCompleted: true,
DownProcess: 100,
DownSpeedStr: '',
DownState: '已完成',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: ''
})
} else if (ret == 'success') {
downingStore.mUpdateDownState({
DownID,
IsDowning: true,
IsCompleted: false,
DownSpeedStr: '',
DownState: '下载中',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: ''
})
} else if (ret == '已暂停') {
console.log('已暂停')
downingStore.mUpdateDownState({
DownID,
IsDowning: false,
IsCompleted: false,
DownSpeedStr: '',
DownState: '已暂停',
AutoTry: 0,
IsFailed: false,
IsStop: true,
FailedCode: 0,
FailedMessage: '已暂停'
})
} else {
useFootStore().mSaveDownTotalSpeedInfo('')
downingStore.mUpdateDownState({
DownID,
IsDowning: false,
IsCompleted: false,
DownSpeedStr: '',
DownState: '已出错',
AutoTry: Date.now(),
IsFailed: true,
IsStop: false,
FailedCode: 504,
FailedMessage: ret
})
}
})
}
}
}
}
downingStore.mRefreshListDataShow(true)
downedStore.mRefreshListDataShow(true)
useDownedStore().mRefreshListDataShow(true)
}
/**
@@ -295,134 +343,164 @@ export default class DownDAL {
static mSpeedEvent(list: IAriaDownProgress[]) {
const downingStore = useDowningStore()
const settingStore = useSettingStore()
const DowningList: IStateDownFile[] = downingStore.ListDataRaw
const DowningList = downingStore.ListDataRaw
if (list == undefined) list = []
const dellist: string[] = []
let hasSpeed = 0
for (let n = 0; n < DowningList.length; n++) {
if (DowningList[n].Down.DownSpeedStr != '') {
const gid = DowningList[n].Info.GID
let isFind = false
for (let m = 0; m < list.length; m++) {
if (list[m].gid != gid) continue
if (list[m].gid == gid && list[m].status == 'active') {
isFind = true
break
}
}
if (!isFind) {
if (DowningList[n].Down.DownState != '已暂停') DowningList[n].Down.DownState = '队列中'
DowningList[n].Down.DownSpeed = 0
DowningList[n].Down.DownSpeedStr = ''
}
}
}
const ariaRemote = !settingStore.AriaIsLocal
const dellist: string[] = []
const saveList: IStateDownFile[] = []
let hasSpeed = 0
for (const listItem of list) {
for (let i = 0; i < list.length; i++) {
try {
const { gid, status, totalLength, completedLength, downloadSpeed, errorCode, errorMessage } = listItem
const isComplete = status === 'complete'
const isDowning = isComplete || status === 'active' || status === 'waiting'
const isStop = status === 'paused' || status === 'removed'
const isError = status === 'error'
const downingItem: IStateDownFile | undefined = DowningList.find((item) => item.Info.ariaRemote === ariaRemote && item.Info.GID === gid)
if (!downingItem) continue
const { DownID, Down, Info } = downingItem
const totalLengthInt = parseInt(totalLength) || 0
Down.DownSize = parseInt(completedLength) || 0
Down.DownSpeed = parseInt(downloadSpeed) || 0
Down.DownSpeedStr = humanSize(Down.DownSpeed) + '/s'
Down.DownProcess = Math.floor((Down.DownSize * 100) / (totalLengthInt + 1)) % 100
Down.IsCompleted = isComplete
Down.IsDowning = isDowning
Down.IsFailed = isError
Down.IsStop = isStop
if (errorCode && errorCode !== '0') {
Down.FailedCode = parseInt(errorCode) || 0
Down.FailedMessage = FormatAriaError(errorCode, errorMessage)
const gid = list[i].gid
const isComplete = list[i].status === 'complete'
const isDowning = isComplete || list[i].status === 'active' || list[i].status === 'waiting'
const isStop = list[i].status === 'paused' || list[i].status === 'removed'
const isError = list[i].status === 'error'
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].Info.ariaRemote != ariaRemote) continue
if (DowningList[j].Info.GID == gid) {
const downItem = DowningList[j]
const down = downItem.Down
const totalLength = parseInt(list[i].totalLength) || 0
down.DownSize = parseInt(list[i].completedLength) || 0
down.DownSpeed = parseInt(list[i].downloadSpeed) || 0
down.DownSpeedStr = humanSize(down.DownSpeed) + '/s'
down.DownProcess = Math.floor((down.DownSize * 100) / (totalLength + 1)) % 100
down.IsCompleted = isComplete
down.IsDowning = isDowning
down.IsFailed = isError
down.IsStop = isStop
if (list[i].errorCode && list[i].errorCode != '0') {
down.FailedCode = parseInt(list[i].errorCode) || 0
down.FailedMessage = FormatAriaError(list[i].errorCode, list[i].errorMessage)
}
if (isComplete) {
downingStore.mUpdateDownState(downingItem, 'valid')
const check = AriaHashFile(downingItem)
down.DownSize = downItem.Info.size
down.DownSpeed = 0
down.DownSpeedStr = ''
down.DownProcess = 100
down.FailedCode = 0
down.FailedMessage = ''
down.DownState = '校验中'
const check = AriaHashFile(downItem)
if (check.Check) {
if (useSettingStore().downFinishAudio && !sound.playing()) {
sound.play()
}
downingStore.mUpdateDownState(downingItem, 'downed')
downingStore.mUpdateDownState({
DownID: check.DownID,
DownState: '已完成',
IsFailed: false,
IsDowning: true,
IsStop: false,
IsCompleted: true,
FailedMessage: ''
})
} else {
downingStore.mUpdateDownState(downingItem, 'error', '移动文件失败,请重新下载')
downingStore.mUpdateDownState({
DownID: check.DownID,
DownState: '已出错',
IsFailed: true,
IsDowning: false,
IsStop: true,
IsCompleted: false,
FailedMessage: '移动文件失败,请重新下载'
})
}
} else if (isStop) {
downingStore.mUpdateDownState(downingItem, 'stop')
dellist.push(gid)
} else if (isError) {
if (!Down.FailedMessage) {
Down.FailedMessage = '下载失败'
}
downingStore.mUpdateDownState(downingItem, 'error', Down.FailedMessage)
dellist.push(gid)
down.DownState = '已暂停'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.FailedCode = 0
down.FailedMessage = ''
} else if (isStop || isError) {
down.DownState = '已出错'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.AutoTry = Date.now()
if (down.FailedMessage == '') down.FailedMessage = '下载失败'
} else if (isDowning) {
hasSpeed += Down.DownSpeed
let lastTime = ((totalLengthInt - Down.DownSize) / (Down.DownSpeed + 1)) % 356400
if (lastTime < 1) lastTime = 1
// 进度条
Down.DownState =
`${Down.DownProcess}% ${(lastTime / 3600).toFixed(0).padStart(2, '0')}:${((lastTime % 3600) / 60)
.toFixed(0)
.padStart(2, '0')}:${(lastTime % 60).toFixed(0).padStart(2, '0')}`
if (SaveTimeWait > 10) {
saveList.push(downingItem)
hasSpeed += down.DownSpeed
let lasttime = ((totalLength - down.DownSize) / (down.DownSpeed + 1)) % 356400
if (lasttime < 1) lasttime = 1
down.DownState =
down.DownProcess.toString() +
'% ' +
(lasttime / 3600).toFixed(0).padStart(2, '0') +
':' +
((lasttime % 3600) / 60).toFixed(0).padStart(2, '0') +
':' +
(lasttime % 60).toFixed(0).padStart(2, '0')
if (SaveTimeWait > 10) saveList.push(downItem)
} else {
//console.log('update', DowningList[j]);
}
if (isStop || isError) {
dellist.push(gid)
}
downingStore.mRefreshListDataShow(true)
break
}
}
} catch {
// Ignore any errors
}
}
// 存盘时间
SaveTimeWait = (SaveTimeWait + 1) % 11
if (saveList.length) {
DBDown.saveDownings(JSON.parse(JSON.stringify(saveList)))
}
if (dellist.length) {
AriaDeleteList(dellist).then()
}
if (saveList.length > 0) DBDown.saveDownings(JSON.parse(JSON.stringify(saveList)))
if (dellist.length > 0) AriaDeleteList(dellist).then()
if (SaveTimeWait > 10) SaveTimeWait = 0
else SaveTimeWait++
useFootStore().mSaveDownTotalSpeedInfo(hasSpeed && humanSizeSpeed(hasSpeed) || '')
}
static async deleteDowning(isAll: boolean, deleteList: IStateDownFile[], gidList: string[]) {
// 处理待删除文件
if (!isAll) {
const downIDList = deleteList.map(item => item.DownID)
// console.log('deleteDowning', deleteList)
await DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList)))
} else {
await DBDown.deleteDowningAll()
}
// 停止aria2下载任务
await AriaStopList(gidList)
await AriaDeleteList(gidList)
// 删除临时文件
for (let downFile of deleteList) {
let downInfo = downFile.Info
if (downInfo.ariaRemote) continue
try {
if (!downInfo.isDir) {
let filePath = path.join(downInfo.DownSavePath, downInfo.name)
let tmpFilePath1 = filePath + '.td.aria2'
let tmpFilePath2 = filePath + '.td'
await fsPromises.rm(tmpFilePath1, { recursive: true })
await fsPromises.rm(tmpFilePath2, { recursive: true })
}
} catch (e) {
}
}
}
static async deleteDowned(isAll: boolean, deleteList: IStateDownFile[]) {
if (!isAll) {
static deleteDowning(isAll: boolean, downingList: IStateDownFile[], gidList: string[]) {
// 处理待删除状态
const downIDList = deleteList
const downIDList = downingList
.filter(list => list.Down.DownState === '待删除')
.map(item => item.DownID)
console.log('downedList', deleteList)
await DBDown.deleteDowneds(JSON.parse(JSON.stringify(downIDList)))
} else {
await DBDown.deleteDownedAll()
}
console.log('downIDList', downIDList)
DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList)))
AriaStopList(gidList).then(r => {})
AriaDeleteList(gidList).then(r => {})
}
static async stopDowning(downList: IStateDownFile[], gidList: string[]) {
await DBDown.saveDownings(JSON.parse(JSON.stringify(downList)))
await AriaStopList(gidList)
static stopDowning(downList: IStateDownFile[], gidList: string[]) {
DBDown.saveDownings(JSON.parse(JSON.stringify(downList)))
AriaStopList(gidList).then(r => {})
}
static QueryIsDowning() {
return useDowningStore().ListDataDowningCount > 0
const downingList = useDowningStore().ListDataRaw
for (let i = 0, maxi = downingList.length; i < maxi; i++) {
if(!downingList[i].Down.IsDowning) {
return true
}
}
return false
}
}

View File

@@ -1,31 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
KeyboardState,
MouseState,
useAppStore,
useDownedStore,
useKeyboardStore,
useMouseStore,
useWinStore
} from '../store'
import {
onHideRightMenuScroll,
onShowRightMenu,
RefreshScroll,
RefreshScrollTo,
TestCtrl,
TestKey,
TestKeyboardScroll,
TestKeyboardSelect
} from '../utils/keyboardhelper'
import { useAppStore, useKeyboardStore, KeyboardState, useUserStore, useWinStore, useDownedStore } from '../store'
import { onShowRightMenu, onHideRightMenuScroll, RefreshScroll, RefreshScrollTo, TestCtrl, TestKey,
TestKeyboardScroll, TestKeyboardSelect } from '../utils/keyboardhelper'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import 'ant-design-vue/es/tooltip/style/css'
import { IStateDownFile } from './DownDAL'
import { TestButton } from '../utils/mosehelper'
import { xorWith } from 'lodash'
const viewlist = ref()
const inputsearch = ref()
const appStore = useAppStore()
const winStore = useWinStore()
const downedStore = useDownedStore()
@@ -43,131 +27,14 @@ keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (TestKeyboardSelect(state.KeyDownEvent, viewlist.value, downedStore, null)) return
})
const rangIsSelecting = ref(false)
const rangSelectID = ref('')
const rangSelectStart = ref('')
const rangSelectEnd = ref('')
const rangSelectFiles = ref<{ [k: string]: any }>({})
const onSelectRangStart = () => {
onHideRightMenuScroll()
rangIsSelecting.value = !rangIsSelecting.value
rangSelectID.value = ''
rangSelectStart.value = ''
rangSelectEnd.value = ''
rangSelectFiles.value = {}
downedStore.mRefreshListDataShow(false)
}
const onSelectReverse = () => {
onHideRightMenuScroll()
const listData = downedStore.ListDataShow
const listSelected = downedStore.GetSelected()
const reverseSelect = xorWith(listData, listSelected, (a, b) => a.DownID === b.DownID)
downedStore.ListSelected.clear()
downedStore.ListFocusKey = ''
if (reverseSelect.length > 0) {
downedStore.mRangSelect(reverseSelect[0].DownID, reverseSelect.map(r => r.DownID))
}
downedStore.mRefreshListDataShow(false)
}
const onSelectCancel = () => {
onHideRightMenuScroll()
downedStore.ListSelected.clear()
downedStore.ListFocusKey = ''
downedStore.mRefreshListDataShow(false)
}
const onSelectRang = (file_id: string) => {
if (rangIsSelecting.value && rangSelectID.value != '') {
let startid = rangSelectID.value
let endid = ''
const s: { [k: string]: any } = {}
const children = downedStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].DownID == file_id) a = i
if (children[i].DownID == startid) b = i
if (a > 0 && b > 0) break
}
if (a >= 0 && b >= 0) {
if (a > b) {
;[a, b] = [b, a]
endid = file_id
} else {
endid = startid
startid = file_id
}
for (let n = a; n <= b; n++) {
s[children[n].DownID] = true
}
}
rangSelectStart.value = startid
rangSelectEnd.value = endid
rangSelectFiles.value = s
downedStore.mRefreshListDataShow(false)
}
}
const mouseStore = useMouseStore()
mouseStore.$subscribe((_m: any, state: MouseState) => {
if (appStore.appTab != 'down') return
const mouseEvent = state.MouseEvent
// console.log('MouseEvent', state.MouseEvent)
if (TestButton(0, mouseEvent, () => {
if (mouseEvent.srcElement) {
// @ts-ignore
const className = mouseEvent.srcElement.className
if (className && className.toString().startsWith('arco-virtual-list')) {
onSelectCancel()
}
}
})) return
})
const handleSelectAll = () => downedStore.mSelectAll()
const handleSelect = (file_id: string, event: any, isCtrl: boolean = false) => {
const handleOrder = (order: string) => downedStore.mOrderListData(order)
const handleSelect = (shareid: string, event: any) => {
onHideRightMenuScroll()
if (rangIsSelecting.value) {
if (!rangSelectID.value) {
if (!downedStore.ListSelected.has(file_id)) downedStore.mMouseSelect(file_id, true, false)
rangSelectID.value = file_id
rangSelectStart.value = file_id
rangSelectFiles.value = { [file_id]: true }
} else {
const start = rangSelectID.value
const children = downedStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].DownID == file_id) a = i
if (children[i].DownID == start) b = i
if (a > 0 && b > 0) break
}
const fileList: string[] = []
if (a >= 0 && b >= 0) {
if (a > b) [a, b] = [b, a]
for (let n = a; n <= b; n++) {
fileList.push(children[n].DownID)
}
}
downedStore.mRangSelect(file_id, fileList)
rangIsSelecting.value = false
rangSelectID.value = ''
rangSelectStart.value = ''
rangSelectEnd.value = ''
rangSelectFiles.value = {}
}
downedStore.mRefreshListDataShow(false)
} else {
downedStore.mMouseSelect(file_id, event.ctrlKey || isCtrl, event.shiftKey)
if (!downedStore.ListSelected.has(file_id)) downedStore.ListFocusKey = ''
}
downedStore.mMouseSelect(shareid, event.ctrlKey, event.shiftKey)
}
const handleDelete = async () => await downedStore.mDeleteDowned([...downedStore.ListSelected])
const handleDelete = () => downedStore.mDeleteUploaded([...downedStore.ListSelected])
const handleOpenFile = (file: IStateDownFile | null) =>
downedStore.mOpenUploadedFile(file, [...downedStore.ListSelected], false)
@@ -175,7 +42,7 @@ const handleOpenFile = (file: IStateDownFile | null) =>
const handleOpenDir = (file: IStateDownFile | null) =>
downedStore.mOpenUploadedFile(file, [...downedStore.ListSelected], true)
const handleDeleteAll = async () => await downedStore.mDeleteAllDowned()
const handleDeleteAll = () => downedStore.mDeleteAllUploaded()
const handleSearchInput = (value: string) => {
downedStore.mSearchListData(value)
@@ -194,15 +61,8 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<template>
<div style="height: 7px"></div>
<div class='toppanbtns' style='height: 26px'>
<div style="min-height: 26px; max-width: 100%; flex-shrink: 0; flex-grow: 0">
<div class="toppannav">
<div class="toppannavitem" title="已下载">
<span> 已下载 </span>
</div>
</div>
</div>
<div class='flex flexauto'></div>
<div class="toppanbtns" style="height: 26px">
<div class="flex flexauto"></div>
<div class="flex flexnoauto cellcount" title="总共已下载完记录数">
<a-badge color="#637dff" :text="'总记录数 ' + downedStore.ListStats.count" />
</div>
@@ -228,9 +88,7 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
size="small"
title="Ctrl+F / F3 / Space"
placeholder="快速筛选"
allow-clear
v-model="downedStore.ListSearchKey"
@clear='(e:any)=>handleSearchInput("")'
:model-value="downedStore.ListSearchKey"
@input="(val:any)=>handleSearchInput(val as string)"
@press-enter="handleSearchEnter"
@keydown.esc="($event.target as any).blur()"
@@ -247,36 +105,7 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</a-button>
</AntdTooltip>
</div>
<div class="selectInfo">{{ downedStore.ListDataSelectCountInfo }}</div>
<div style='margin: 0 2px'>
<AntdTooltip placement='rightTop' v-if="downedStore.ListDataShow.length > 0">
<a-button shape='square' type='text' tabindex='-1' class='qujian'
:status="rangIsSelecting ? 'danger' : 'normal'" title='Ctrl+Q' @click='onSelectRangStart'>
{{ rangIsSelecting ? '取消选择' : '区间选择' }}
</a-button>
<template #title>
<div>
第1步: 点击 区间选择 这个按钮
<br />
第2步: 鼠标点击一个文件
<br />
第3步: 移动鼠标点击另外一个文件
</div>
</template>
</AntdTooltip>
<a-button shape='square'
v-if='!rangIsSelecting && downedStore.ListSelected.size > 0 && downedStore.ListSelected.size < downedStore.ListDataShow.length'
type='text'
tabindex='-1'
class='qujian'
status='normal' @click='onSelectReverse'>
反向选择
</a-button>
<a-button shape='square' v-if='!rangIsSelecting && downedStore.ListSelected.size > 0' type='text' tabindex='-1' class='qujian'
status='normal' @click='onSelectCancel'>
取消已选
</a-button>
</div>
<div class="selectInfo" style="min-width: 266px">{{ downedStore.ListDataSelectCountInfo }}</div>
</div>
<div class="toppanlist" @keydown.space.prevent="() => true">
<a-list
@@ -302,13 +131,10 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<div
:class="'fileitem' + (downedStore.ListSelected.has(item.DownID) ? ' selected' : '') + (downedStore.ListFocusKey == item.DownID ? ' focus' : '')"
@click="handleSelect(item.DownID, $event)"
@mouseover='onSelectRang(item.DownID)'
@contextmenu="(event:MouseEvent)=>handleRightClick({event,node:{key:item.DownID}} )"
>
<div
:class="'rangselect ' + (rangSelectFiles[item.DownID] ? (rangSelectStart == item.DownID ? 'rangstart' : rangSelectEnd == item.DownID ? 'rangend' : 'rang') : '')">
<a-button shape='circle' type='text' tabindex='-1' class='select' :title='index'
@click.prevent.stop='handleSelect(item.DownID, $event, true)'>
<div style="margin: 2px">
<a-button shape="circle" type="text" tabindex="-1" class="select" :title="index" @click.prevent.stop="handleSelect(item.DownID, { ctrlKey: true, shiftKey: false })">
<i :class="downedStore.ListSelected.has(item.DownID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</div>
@@ -350,5 +176,167 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</template>
<style>
.cellcount {
align-items: center;
margin-right: 16px;
}
.cellcount .arco-badge .arco-badge-status-text {
margin-left: 4px;
color: var(--color-text-3);
line-height: 26px;
}
body[arco-theme='dark'] .toppanarea .cell {
color: rgba(211, 216, 241, 0.45);
}
.cell {
color: var(--color-text-3);
overflow: hidden;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
display: inline-block;
line-height: 18px;
min-height: 18px;
padding: 0 4px;
justify-content: center;
}
.cell.filesize {
font-size: 16px;
width: 86px;
text-align: right;
flex-shrink: 0;
flex-grow: 0;
margin-right: 16px;
}
.cell.tiquma {
width: 60px;
font-size: 12px;
}
.cell.count {
width: 60px;
font-size: 12px;
}
.cell.sharetime {
width: 80px;
font-size: 12px;
line-height: 14px;
text-align: right;
word-wrap: break-word;
word-break: keep-all;
}
.cell.sharetime.active {
color: rgb(217, 48, 37);
}
.cell.sharestate {
width: 70px;
font-size: 12px;
}
.cell.sharestate.active {
color: rgb(var(--primary-6));
}
.cell.sharestate.forbidden {
color: rgb(217, 48, 37);
}
.cell.sharestate.deleted {
text-decoration: line-through;
}
.cell.p5 {
width: 5px;
}
.cell.pr {
width: 12px;
}
.toppanarea .cell.order {
cursor: pointer;
}
.toppanarea .cell.order:hover {
color: rgb(var(--primary-6));
}
.downprogress {
width: 90px;
flex-shrink: 0;
flex-grow: 0;
margin-right: 8px;
}
.downspeed {
width: 126px;
font-size: 22px;
color: var(--color-text-4);
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
flex-shrink: 0;
flex-grow: 0;
text-align: right;
}
.transfering-state {
display: block;
width: 100%;
overflow: visible;
}
.text-state {
font-size: 12px;
line-height: 16px;
color: var(--color-text-3);
max-width: 100%;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
height: 16px;
margin: 0;
}
.text-error {
color: #f35b51;
font-size: 12px;
line-height: 16px;
width: 100%;
overflow: visible;
white-space: nowrap;
height: 16px;
margin: 0;
}
.progress-total {
width: 100%;
height: 3px;
background: var(--color-text-4);
border-radius: 1.5px;
margin-top: 2px;
position: relative;
}
.progress-total .progress-current {
position: absolute;
top: 0;
left: 0;
max-width: 100%;
min-width: 6px;
height: 100%;
border-radius: 1.5px;
background: var(--color-primary-light-4);
-webkit-transition: width 0.3s ease, opacity 0.3s ease;
-o-transition: width 0.3s ease, opacity 0.3s ease;
transition: width 0.3s ease, opacity 0.3s ease;
}
.progress-total .progress-current.succeed {
background: #099970;
}
.progress-total .progress-current.error {
background: #f35b51;
}
.progress-total .progress-current.active {
background: linear-gradient(270deg, #ffba7a 0%, #ff74c7 8.56%, #637dff 26.04%, rgba(99, 125, 255, 0.2) 100%);
}
.downHideTip {
text-align: center;
padding: 8px;
opacity: 0.5;
}
</style>

View File

@@ -1,28 +1,10 @@
<script setup lang='ts'>
import { computed, ref, watch } from 'vue'
import {
KeyboardState,
MouseState,
useAppStore,
useDowningStore,
useKeyboardStore,
useMouseStore,
useUploadingStore,
useWinStore
} from '../store'
import {
onHideRightMenuScroll,
onShowRightMenu,
RefreshScroll,
RefreshScrollTo,
TestCtrl,
TestKey,
TestKeyboardScroll,
TestKeyboardSelect
} from '../utils/keyboardhelper'
<script setup lang="ts">
import { ref } from 'vue'
import { useAppStore, useKeyboardStore, KeyboardState, useWinStore, useDowningStore } from '../store'
import { onShowRightMenu, onHideRightMenuScroll, RefreshScroll, RefreshScrollTo, TestCtrl, TestKey,
TestKeyboardScroll, TestKeyboardSelect } from '../utils/keyboardhelper'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import { TestButton } from '../utils/mosehelper'
import { xorWith } from 'lodash'
import 'ant-design-vue/es/tooltip/style/css'
const viewlist = ref()
const inputsearch = ref()
@@ -31,17 +13,6 @@ const appStore = useAppStore()
const winStore = useWinStore()
const downingStore = useDowningStore()
const isDowning = computed(() => downingStore.ListDataDowningCount > 0)
watch(isDowning, (value, oldValue) => {
if (value !== oldValue && window.WebToElectron) {
if (value) {
window.WebToElectron({ cmd: 'preventSleep', flag: 1 })
} else if (useUploadingStore().ListDataUploadingCount == 0) {
window.WebToElectron({ cmd: 'preventSleep', flag: 0 })
}
}
})
const keyboardStore = useKeyboardStore()
keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (appStore.appTab != 'down' || appStore.GetAppTabMenu != 'DowningRight') return
@@ -55,140 +26,24 @@ keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (TestKeyboardSelect(state.KeyDownEvent, viewlist.value, downingStore, null)) return
})
const rangIsSelecting = ref(false)
const rangSelectID = ref('')
const rangSelectStart = ref('')
const rangSelectEnd = ref('')
const rangSelectFiles = ref<{ [k: string]: any }>({})
const onSelectRangStart = () => {
onHideRightMenuScroll()
rangIsSelecting.value = !rangIsSelecting.value
rangSelectID.value = ''
rangSelectStart.value = ''
rangSelectEnd.value = ''
rangSelectFiles.value = {}
downingStore.mRefreshListDataShow(false)
}
const onSelectReverse = () => {
onHideRightMenuScroll()
const listData = downingStore.ListDataShow
const listSelected = downingStore.GetSelected()
const reverseSelect = xorWith(listData, listSelected, (a, b) => a.DownID === b.DownID)
downingStore.ListSelected.clear()
downingStore.ListFocusKey = ''
if (reverseSelect.length > 0) {
downingStore.mRangSelect(reverseSelect[0].DownID, reverseSelect.map(r => r.DownID))
}
downingStore.mRefreshListDataShow(false)
}
const onSelectCancel = () => {
onHideRightMenuScroll()
downingStore.ListSelected.clear()
downingStore.ListFocusKey = ''
downingStore.mRefreshListDataShow(false)
}
const onSelectRang = (file_id: string) => {
if (rangIsSelecting.value && rangSelectID.value != '') {
let startid = rangSelectID.value
let endid = ''
const s: { [k: string]: any } = {}
const children = downingStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].DownID == file_id) a = i
if (children[i].DownID == startid) b = i
if (a > 0 && b > 0) break
}
if (a >= 0 && b >= 0) {
if (a > b) {
;[a, b] = [b, a]
endid = file_id
} else {
endid = startid
startid = file_id
}
for (let n = a; n <= b; n++) {
s[children[n].DownID] = true
}
}
rangSelectStart.value = startid
rangSelectEnd.value = endid
rangSelectFiles.value = s
downingStore.mRefreshListDataShow(false)
}
}
const handleSelectAll = () => downingStore.mSelectAll()
const handleSelect = (file_id: string, event: any, isCtrl: boolean = false) => {
const handleOrder = (order: string) => downingStore.mOrderListData(order)
const handleSelect = (shareid: string, event: any) => {
onHideRightMenuScroll()
if (rangIsSelecting.value) {
if (!rangSelectID.value) {
if (!downingStore.ListSelected.has(file_id)) downingStore.mMouseSelect(file_id, true, false)
rangSelectID.value = file_id
rangSelectStart.value = file_id
rangSelectFiles.value = { [file_id]: true }
} else {
const start = rangSelectID.value
const children = downingStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].DownID == file_id) a = i
if (children[i].DownID == start) b = i
if (a > 0 && b > 0) break
}
const fileList: string[] = []
if (a >= 0 && b >= 0) {
if (a > b) [a, b] = [b, a]
for (let n = a; n <= b; n++) {
fileList.push(children[n].DownID)
}
}
downingStore.mRangSelect(file_id, fileList)
rangIsSelecting.value = false
rangSelectID.value = ''
rangSelectStart.value = ''
rangSelectEnd.value = ''
rangSelectFiles.value = {}
}
downingStore.mRefreshListDataShow(false)
} else {
downingStore.mMouseSelect(file_id, event.ctrlKey || isCtrl, event.shiftKey)
if (!downingStore.ListSelected.has(file_id)) downingStore.ListFocusKey = ''
}
downingStore.mMouseSelect(shareid, event.ctrlKey, event.shiftKey)
}
const mouseStore = useMouseStore()
mouseStore.$subscribe((_m: any, state: MouseState) => {
if (appStore.appTab != 'down') return
const mouseEvent = state.MouseEvent
// console.log('MouseEvent', state.MouseEvent)
if (TestButton(0, mouseEvent, () => {
if (mouseEvent.srcElement) {
// @ts-ignore
const className = mouseEvent.srcElement.className
if (className && className.toString().startsWith('arco-virtual-list')) {
onSelectCancel()
}
}
})) return
})
const handleStart = () => downingStore.mStartDowning()
const handleStartAll = () => downingStore.mStartAllDowning()
const handleStop = () => downingStore.mStopDowning()
const handleStopAll = async () => await downingStore.mStopAllDowning()
const handleStopAll = () => downingStore.mStopAllDowning()
const handleDelete = async () => await downingStore.mDeleteDowning([...downingStore.ListSelected])
const handleDelete = () => downingStore.mDeleteDowning([...downingStore.ListSelected])
const handleDeleteAll = async () => await downingStore.mDeleteAllDowning()
const handleDeleteAll = () => downingStore.mDeleteAllDowning()
const handleTop = () => downingStore.mOrderDowning([...downingStore.ListSelected])
@@ -209,118 +64,69 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<template>
<div style="height: 7px"></div>
<div class='toppanbtns' style='height: 26px'>
<div style="min-height: 26px; max-width: 100%; flex-shrink: 0; flex-grow: 0">
<div class="toppannav">
<div class="toppannavitem" title="下载中">
<span> 下载中 </span>
<div class="toppanbtns" style="height: 26px">
<div class="flex flexauto"></div>
<div class="flex flexnoauto cellcount" title="总共文件数量">
<a-badge color="#637dff" :text="'总数 ' + downingStore.ListStats.count" />
</div>
<div class="flex flexnoauto cellcount" title="正在执行下载数">
<a-badge color="#637dff" :text="'下载 ' + downingStore.ListStats.runningCount" />
</div>
<div class="flex flexnoauto cellcount" title="总共文件大小">
<a-badge color="#637dff" :text="'大小 ' + downingStore.ListStats.totalSizeStr" />
</div>
</div>
</div>
<div class='flex flexauto'></div>
<div class='flex flexnoauto cellcount' title='总共文件数量'>
<a-badge color='#637dff' :text="'总数 ' + downingStore.ListStats.count" />
</div>
<div class='flex flexnoauto cellcount' title='正在执行下载数'>
<a-badge color='#637dff' :text="'下载 ' + downingStore.ListStats.runningCount" />
</div>
<div class='flex flexnoauto cellcount' title='总共文件大小'>
<a-badge color='#637dff' :text="'大小 ' + downingStore.ListStats.totalSizeStr" />
</div>
</div>
<div style='height: 14px'></div>
<div class='toppanbtns' style='height: 26px'>
<div class='toppanbtn' v-show='downingStore.IsListSelected'>
<a-button type='text' size='small' tabindex='-1' @click='handleStart'><i class='iconfont iconstart' />开始
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleStop'><i class='iconfont iconpause' />暂停
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleTop'><i class='iconfont iconyouxian' />优先传输
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleDelete'><i class='iconfont icondelete' />删除
</a-button>
<a-button type='text' size='small' tabindex='-1'><i class='iconfont icondian' /></a-button>
<div style="height: 14px"></div>
<div class="toppanbtns" style="height: 26px">
<div class="toppanbtn" v-show="downingStore.IsListSelected">
<a-button type="text" size="small" tabindex="-1" @click="handleStart"><i class="iconfont iconstart" />开始</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleStop"><i class="iconfont iconpause" />暂停</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleTop"><i class="iconfont iconyouxian" />优先传输</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleDelete"><i class="iconfont icondelete" />删除</a-button>
<a-button type="text" size="small" tabindex="-1"><i class="iconfont icondian" /></a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleStartAll'><i class='iconfont iconstart' />开始全部
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleStopAll'><i class='iconfont iconpause' />暂停全部
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleDeleteAll'><i class='iconfont icondelete' />删除全部
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleStartAll"><i class="iconfont iconstart" />开始全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleStopAll"><i class="iconfont iconpause" />暂停全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleDeleteAll"><i class="iconfont icondelete" />删除全部</a-button>
</div>
<div class='toppanbtn' v-show='!downingStore.IsListSelected'>
<a-button type='text' size='small' tabindex='-1' @click='handleStartAll'><i class='iconfont iconstart' />开始全部
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleStopAll'><i class='iconfont iconpause' />暂停全部
</a-button>
<a-button type='text' size='small' tabindex='-1' @click='handleDeleteAll'><i class='iconfont icondelete' />删除全部
</a-button>
<div class="toppanbtn" v-show="!downingStore.IsListSelected">
<a-button type="text" size="small" tabindex="-1" @click="handleStartAll"><i class="iconfont iconstart" />开始全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleStopAll"><i class="iconfont iconpause" />暂停全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="handleDeleteAll"><i class="iconfont icondelete" />删除全部</a-button>
</div>
<div style='flex-grow: 1'></div>
<div class='toppanbtn'>
<div style="flex-grow: 1"></div>
<div class="toppanbtn">
<a-input-search
tabindex='-1'
ref='inputsearch'
size='small'
title='Ctrl+F / F3 / Space'
placeholder='快速筛选'
allow-clear
v-model='downingStore.ListSearchKey'
@clear='(e:any)=>handleSearchInput("")'
@input='(val:any)=>handleSearchInput(val as string)'
@press-enter='handleSearchEnter'
@keydown.esc='($event.target as any).blur()'
tabindex="-1"
ref="inputsearch"
size="small"
title="Ctrl+F / F3 / Space"
placeholder="快速筛选"
:model-value="downingStore.ListSearchKey"
@input="(val:any)=>handleSearchInput(val as string)"
@press-enter="handleSearchEnter"
@keydown.esc="($event.target as any).blur()"
/>
</div>
<div></div>
</div>
<div style='height: 9px'></div>
<div class='toppanarea'>
<div style='margin: 0 3px'>
<AntdTooltip title='点击全选' placement='left'>
<a-button shape='circle' type='text' tabindex='-1' class='select all' @click='handleSelectAll' title='Ctrl+A'>
<div style="height: 9px"></div>
<div class="toppanarea">
<div style="margin: 0 3px">
<AntdTooltip title="点击全选" placement="left">
<a-button shape="circle" type="text" tabindex="-1" class="select all" @click="handleSelectAll" title="Ctrl+A">
<i :class="downingStore.IsListSelectedAll ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</AntdTooltip>
</div>
<div class='selectInfo'>{{ downingStore.ListDataSelectCountInfo }}</div>
<div style='margin: 0 2px'>
<AntdTooltip placement='rightTop' v-if="downingStore.ListDataShow.length > 0">
<a-button shape='square' type='text' tabindex='-1' class='qujian'
:status="rangIsSelecting ? 'danger' : 'normal'" title='Ctrl+Q' @click='onSelectRangStart'>
{{ rangIsSelecting ? '取消选择' : '区间选择' }}
</a-button>
<template #title>
<div>
第1步: 点击 区间选择 这个按钮
<br />
第2步: 鼠标点击一个文件
<br />
第3步: 移动鼠标点击另外一个文件
<div class="selectInfo" style="min-width: 266px">{{ downingStore.ListDataSelectCountInfo }}</div>
</div>
</template>
</AntdTooltip>
<a-button shape='square'
v-if='!rangIsSelecting && downingStore.ListSelected.size > 0 && downingStore.ListSelected.size < downingStore.ListDataShow.length'
type='text'
tabindex='-1'
class='qujian'
status='normal' @click='onSelectReverse'>
反向选择
</a-button>
<a-button shape='square' v-if='!rangIsSelecting && downingStore.ListSelected.size > 0' type='text' tabindex='-1'
class='qujian'
status='normal' @click='onSelectCancel'>
取消已选
</a-button>
</div>
</div>
<div class='toppanlist' @keydown.space.prevent='() => true'>
<div class="toppanlist" @keydown.space.prevent="() => true">
<a-list
ref='viewlist'
:bordered='false'
:split='false'
:max-height='winStore.GetListHeightNumber'
ref="viewlist"
:bordered="false"
:split="false"
:max-height="winStore.GetListHeightNumber"
:virtualListProps="{
height: winStore.GetListHeightNumber,
isStaticItemHeight: true,
@@ -328,71 +134,66 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
threshold: 1,
itemKey: 'DownID'
}"
style='width: 100%'
:data='downingStore.ListDataShow'
:loading='downingStore.ListLoading'
tabindex='-1'
@scroll='onHideRightMenuScroll'
style="width: 100%"
:data="downingStore.ListDataShow"
:loading="downingStore.ListLoading"
tabindex="-1"
@scroll="onHideRightMenuScroll"
>
<template #item='{ item, index }'>
<div :key='item.DownID' class='listitemdiv' :data-id='item.DownID'>
<template #item="{ item, index }">
<div :key="item.DownID" class="listitemdiv" :data-id="item.DownID">
<div
:class="'fileitem' + (downingStore.ListSelected.has(item.DownID) ? ' selected' : '') + (downingStore.ListFocusKey == item.DownID ? ' focus' : '')"
@click='handleSelect(item.DownID, $event)'
@mouseover='onSelectRang(item.DownID)'
@contextmenu='(event:MouseEvent)=>handleRightClick({event,node:{key:item.DownID}} )'
@click="handleSelect(item.DownID, $event)"
@contextmenu="(event:MouseEvent)=>handleRightClick({event,node:{key:item.DownID}} )"
>
<div
:class="'rangselect ' + (rangSelectFiles[item.DownID] ? (rangSelectStart == item.DownID ? 'rangstart' : rangSelectEnd == item.DownID ? 'rangend' : 'rang') : '')">
<a-button shape='circle' type='text' tabindex='-1' class='select' :title='index'
@click.prevent.stop='handleSelect(item.DownID, $event, true)'>
<i
:class="downingStore.ListSelected.has(item.DownID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
<div style="margin: 2px">
<a-button shape="circle" type="text" tabindex="-1" class="select" :title="index" @click.prevent.stop="handleSelect(item.DownID, { ctrlKey: true, shiftKey: false })">
<i :class="downingStore.ListSelected.has(item.DownID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</div>
<div class='fileicon'>
<i :class="'iconfont ' + item.Info.icon" aria-hidden='true'></i>
<div class="fileicon">
<i :class="'iconfont ' + item.Info.icon" aria-hidden="true"></i>
</div>
<div class='filename'>
<div :title='item.Info.localFilePath'>
<div class="filename">
<div :title="item.Info.localFilePath">
{{ item.Info.name }}
</div>
</div>
<div class='cell filesize'>{{ item.Info.sizestr }}</div>
<div class='downprogress'>
<div class='transfering-state'>
<p class='text-state'>{{ item.Down.DownState }}</p>
<div class='progress-total'>
<div class="cell filesize">{{ item.Info.sizestr }}</div>
<div class="downprogress">
<div class="transfering-state">
<p class="text-state">{{ item.Down.DownState }}</p>
<div class="progress-total">
<div
:class="item.Down.IsDowning ? 'progress-current active' : item.Down.IsCompleted ? 'progress-current succeed' : item.Down.IsFailed ? 'progress-current error' : 'progress-current'"
:style="'width: ' + item.Down.DownProcess.toString() + '%'"
></div>
</div>
<p class='text-error'>{{ item.Down.FailedMessage }}</p>
<p class="text-error">{{ item.Down.FailedMessage }}</p>
</div>
</div>
<div class='downspeed'>{{ item.Down.DownSpeedStr }}</div>
<div class="downspeed">{{ item.Down.DownSpeedStr }}</div>
</div>
</div>
</template>
</a-list>
<a-dropdown id='downingrightmenu' class='rightmenu' :popup-visible='true' tabindex='-1' :draggable='false'
style='z-index: -1; left: -200px; opacity: 0'>
<a-dropdown id="downingrightmenu" class="rightmenu" :popup-visible="true" tabindex="-1" :draggable="false" style="z-index: -1; left: -200px; opacity: 0">
<template #content>
<a-doption @click='handleStart'>
<template #icon><i class='iconfont iconstart' /></template>
<a-doption @click="handleStart">
<template #icon> <i class="iconfont iconstart" /> </template>
<template #default>开始</template>
</a-doption>
<a-doption @click='handleStop'>
<template #icon><i class='iconfont iconpause' /></template>
<a-doption @click="handleStop">
<template #icon> <i class="iconfont iconpause" /> </template>
<template #default>暂停</template>
</a-doption>
<a-doption @click='handleTop'>
<template #icon><i class='iconfont iconyouxian' /></template>
<a-doption @click="handleTop">
<template #icon> <i class="iconfont iconyouxian" /> </template>
<template #default>优先传输</template>
</a-doption>
<a-doption @click='handleDelete' class='danger'>
<template #icon><i class='iconfont icondelete' /></template>
<a-doption @click="handleDelete" class="danger">
<template #icon> <i class="iconfont icondelete" /> </template>
<template #default>删除</template>
</a-doption>
</template>
@@ -400,47 +201,105 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</div>
</template>
<style scoped>
.downprogress {
<style>
.cellcount {
align-items: center;
margin-right: 16px;
}
.cellcount .arco-badge .arco-badge-status-text {
margin-left: 4px;
color: var(--color-text-3);
line-height: 26px;
}
body[arco-theme='dark'] .toppanarea .cell {
color: rgba(211, 216, 241, 0.45);
}
.cell {
color: var(--color-text-3);
overflow: hidden;
text-align: center;
flex-grow: 0;
flex-shrink: 0;
width: 110px;
display: inline-block;
line-height: 18px;
min-height: 18px;
padding: 0 4px;
justify-content: center;
}
.cell.filesize {
font-size: 16px;
width: 86px;
text-align: right;
flex-shrink: 0;
flex-grow: 0;
margin-right: 16px;
}
.cell.tiquma {
width: 60px;
font-size: 12px;
}
.cell.count {
width: 60px;
font-size: 12px;
}
.cell.sharetime {
width: 80px;
font-size: 12px;
line-height: 14px;
text-align: right;
word-wrap: break-word;
word-break: keep-all;
}
.cell.sharetime.active {
color: rgb(217, 48, 37);
}
.cell.sharestate {
width: 70px;
font-size: 12px;
}
.cell.sharestate.active {
color: rgb(var(--primary-6));
}
.cell.sharestate.forbidden {
color: rgb(217, 48, 37);
}
.cell.sharestate.deleted {
text-decoration: line-through;
}
.cell.p5 {
width: 5px;
}
.cell.pr {
width: 12px;
}
.toppanarea .cell.order {
cursor: pointer;
}
.toppanarea .cell.order:hover {
color: rgb(var(--primary-6));
}
.downprogress {
width: 90px;
flex-shrink: 0;
flex-grow: 0;
margin-right: 8px;
}
.downspeed {
flex-grow: 0;
flex-shrink: 0;
width: 130px;
overflow: hidden;
width: 126px;
font-size: 22px;
color: var(--color-text-4);
font-size: 25px;
text-align: right;
text-overflow: clip;
white-space: nowrap;
word-break: keep-all;
}
@media only screen and (min-width: 900px) {
.downprogress {
width: 150px;
}
}
@media only screen and (min-width: 960px) {
.downprogress {
width: 170px;
}
}
@media only screen and (min-width: 1000px) {
.downprogress {
width: 180px;
}
.downspeed {
width: 150px;
}
overflow: hidden;
text-overflow: clip;
flex-shrink: 0;
flex-grow: 0;
text-align: right;
}
.transfering-state {
@@ -448,68 +307,62 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
width: 100%;
overflow: visible;
}
.text-state {
max-width: 100%;
height: 16px;
margin: 0;
overflow: hidden;
color: var(--color-text-3);
font-size: 12px;
line-height: 16px;
color: var(--color-text-3);
max-width: 100%;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.text-error {
width: 100%;
height: 16px;
margin: 0;
overflow: visible;
}
.text-error {
color: #f35b51;
font-size: 12px;
line-height: 16px;
width: 100%;
overflow: visible;
white-space: nowrap;
height: 16px;
margin: 0;
}
.progress-total {
position: relative;
width: 100%;
height: 3px;
margin-top: 2px;
background: #84858d14;
background: var(--color-text-4);
border-radius: 1.5px;
margin-top: 2px;
position: relative;
}
body[arco-theme='dark'] .progress-total {
background: #84858d;
}
.progress-total .progress-current {
position: absolute;
top: 0;
left: 0;
min-width: 6px;
max-width: 100%;
min-width: 6px;
height: 100%;
background: #00000033;
border-radius: 1.5px;
background: var(--color-primary-light-4);
-webkit-transition: width 0.3s ease, opacity 0.3s ease;
-o-transition: width 0.3s ease, opacity 0.3s ease;
transition: width 0.3s ease, opacity 0.3s ease;
}
.progress-total .progress-current.succeed {
background: #099970;
}
.progress-total .progress-current.error {
background: #f35b51;
}
.progress-total .progress-current.active {
background: linear-gradient(270deg, #ffba7a 0%, #ff74c7 8.56%, #637dff 26.04%, rgba(99, 125, 255, 0.2) 100%);
}
.downHideTip {
text-align: center;
padding: 8px;
opacity: 0.5;
}
</style>

View File

@@ -1,17 +1,8 @@
<script setup lang="ts">
import {
KeyboardState,
MouseState,
useAppStore,
useKeyboardStore,
useMouseStore,
usePanFileStore,
useWinStore
} from '../store'
import { KeyboardState, useAppStore, useKeyboardStore, usePanFileStore, useWinStore } from '../store'
import {
onHideRightMenuScroll,
onShowRightMenu,
RefreshScroll,
onShowRightMenu, RefreshScroll,
RefreshScrollTo,
TestCtrl,
TestKey,
@@ -22,16 +13,15 @@ import { ref } from 'vue'
import UploadDAL from '../transfer/uploaddal'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import 'ant-design-vue/es/tooltip/style/css'
import useUploadedStore from './UploadedStore'
import { IStateUploadTask } from '../utils/dbupload'
import message from '../utils/message'
import AliFile from '../aliapi/file'
import PanDAL from '../pan/pandal'
import { humanSize } from '../utils/format'
import { TestButton } from '../utils/mosehelper'
import fs from 'node:fs'
import { xorWith } from 'lodash'
const fs = window.require('fs')
const viewlist = ref()
const inputsearch = ref()
const appStore = useAppStore()
@@ -52,130 +42,12 @@ keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (TestKeyboardScroll(state.KeyDownEvent, viewlist.value, uploadedStore)) return
})
const rangIsSelecting = ref(false)
const rangSelectID = ref(-1)
const rangSelectStart = ref(-1)
const rangSelectEnd = ref(-1)
const rangSelectFiles = ref<{ [k: number]: any }>({})
const onSelectRangStart = () => {
onHideRightMenuScroll()
rangIsSelecting.value = !rangIsSelecting.value
rangSelectID.value = -1
rangSelectStart.value = -1
rangSelectEnd.value = -1
rangSelectFiles.value = {}
uploadedStore.mRefreshListDataShow(false)
}
const onSelectReverse = () => {
onHideRightMenuScroll()
const listData = uploadedStore.ListDataShow
const listSelected = uploadedStore.GetSelected()
const reverseSelect = xorWith(listData, listSelected, (a, b) => a.TaskID === b.TaskID)
uploadedStore.ListSelected.clear()
uploadedStore.ListFocusKey = -1
if (reverseSelect.length > 0) {
uploadedStore.mRangSelect(reverseSelect[0].TaskID, reverseSelect.map(r => r.TaskID))
}
uploadedStore.mRefreshListDataShow(false)
}
const onSelectCancel = () => {
onHideRightMenuScroll()
uploadedStore.ListSelected.clear()
uploadedStore.ListFocusKey = 0
uploadedStore.mRefreshListDataShow(false)
}
const onSelectRang = (file_id: number) => {
if (rangIsSelecting.value && rangSelectID.value != -1) {
let startid = rangSelectID.value
let endid = -1
const s: { [k: string]: any } = {}
const children = uploadedStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].TaskID == file_id) a = i
if (children[i].TaskID == startid) b = i
if (a > 0 && b > 0) break
}
if (a >= 0 && b >= 0) {
if (a > b) {
;[a, b] = [b, a]
endid = file_id
} else {
endid = startid
startid = file_id
}
for (let n = a; n <= b; n++) {
s[children[n].TaskID] = true
}
}
rangSelectStart.value = startid
rangSelectEnd.value = endid
rangSelectFiles.value = s
uploadedStore.mRefreshListDataShow(false)
}
}
const mouseStore = useMouseStore()
mouseStore.$subscribe((_m: any, state: MouseState) => {
if (appStore.appTab != 'down') return
const mouseEvent = state.MouseEvent
// console.log('MouseEvent', state.MouseEvent)
if (TestButton(0, mouseEvent, () => {
if (mouseEvent.srcElement) {
// @ts-ignore
const className = mouseEvent.srcElement.className
if (className && className.toString().startsWith('arco-virtual-list')) {
onSelectCancel()
}
}
})) return
})
const handleRefresh = () => UploadDAL.aReloadUploaded()
const handleSelectAll = () => uploadedStore.mSelectAll()
const handleSelect = (TaskID: number, event: any, isCtrl: boolean = false) => {
onHideRightMenuScroll()
if (rangIsSelecting.value) {
if (!rangSelectID.value) {
if (!uploadedStore.ListSelected.has(TaskID)) uploadedStore.mMouseSelect(TaskID, true, false)
rangSelectID.value = TaskID
rangSelectStart.value = TaskID
rangSelectFiles.value = { [TaskID]: true }
} else {
const start = rangSelectID.value
const children = uploadedStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].TaskID == TaskID) a = i
if (children[i].TaskID == start) b = i
if (a > 0 && b > 0) break
}
const fileList: number[] = []
if (a >= 0 && b >= 0) {
if (a > b) [a, b] = [b, a]
for (let n = a; n <= b; n++) {
fileList.push(children[n].TaskID)
}
}
uploadedStore.mRangSelect(TaskID, fileList)
rangIsSelecting.value = false
rangSelectID.value = -1
rangSelectStart.value = -1
rangSelectEnd.value = -1
rangSelectFiles.value = {}
}
uploadedStore.mRefreshListDataShow(false)
} else {
uploadedStore.mMouseSelect(TaskID, event.ctrlKey || isCtrl, event.shiftKey)
if (!uploadedStore.ListSelected.has(TaskID)) uploadedStore.ListFocusKey = -1
}
}
const handleDbClick = (TaskID: number) => {
onSelectFile(undefined, 'pan')
@@ -212,8 +84,7 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
} else {
message.error('文件可能已经被删除')
}
} catch {
}
} catch {}
}
if (cmd == 'dir') {
const full = item.localFilePath
@@ -224,8 +95,7 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
} else {
message.error('文件夹可能已经被删除')
}
} catch {
}
} catch {}
}
if (cmd == 'delete') {
UploadDAL.UploadedDelete(false)
@@ -251,22 +121,13 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
<template>
<div style="height: 7px"></div>
<div class='toppanbtns' style='height: 26px'>
<div style="min-height: 26px; max-width: 100%; flex-shrink: 0; flex-grow: 0">
<div class="toppannav">
<div class="toppannavitem" title="已上传">
<span> 已上传 </span>
</div>
</div>
</div>
<div class='flex flexauto'></div>
<div class="toppanbtns" style="height: 26px">
<div style="flex-grow: 1"></div>
</div>
<div style="height: 14px"></div>
<div class="toppanbtns" style="height: 26px">
<div class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" :loading="uploadedStore.ListLoading" title="F5"
@click="handleRefresh">
<a-button type="text" size="small" tabindex="-1" :loading="uploadedStore.ListLoading" title="F5" @click="handleRefresh">
<template #icon>
<i class="iconfont iconreload-1-icon" />
</template>
@@ -274,15 +135,11 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
</div>
<div v-if="uploadedStore.IsListSelected" class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" @click="() => UploadDAL.UploadedDelete(false)"><i
class="iconfont icondelete" />清除选中
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadDAL.UploadedDelete(false)"><i class="iconfont icondelete" />清除选中</a-button>
</div>
<div class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" @click="() => UploadDAL.UploadedDelete(true)"><i
class="iconfont iconrest" />清空全部已上传
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadDAL.UploadedDelete(true)"><i class="iconfont iconrest" />清空全部已上传</a-button>
</div>
<div style="flex-grow: 1"></div>
@@ -293,9 +150,7 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
size="small"
title="Ctrl+F / F3 / Space"
placeholder="快速筛选"
allow-clear
v-model="uploadedStore.ListSearchKey"
@clear='(e:any)=>handleSearchInput("")'
:model-value="uploadedStore.ListSearchKey"
@input="(val:any)=>handleSearchInput(val as string)"
@press-enter="handleSearchEnter"
@keydown.esc="($event.target as any).blur()"
@@ -312,37 +167,9 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
</AntdTooltip>
</div>
<div class="selectInfo">{{ uploadedStore.ListDataSelectCountInfo }}</div>
<div style='margin: 0 2px'>
<AntdTooltip placement='rightTop' v-if="uploadedStore.ListDataShow.length > 0">
<a-button shape='square' type='text' tabindex='-1' class='qujian'
:status="rangIsSelecting ? 'danger' : 'normal'" title='Ctrl+Q' @click='onSelectRangStart'>
{{ rangIsSelecting ? '取消选择' : '区间选择' }}
</a-button>
<template #title>
<div>
第1步: 点击 区间选择 这个按钮
<br />
第2步: 鼠标点击一个文件
<br />
第3步: 移动鼠标点击另外一个文件
</div>
</template>
</AntdTooltip>
<a-button shape='square'
v-if='!rangIsSelecting && uploadedStore.ListSelected.size > 0 && uploadedStore.ListSelected.size < uploadedStore.ListDataShow.length'
type='text'
tabindex='-1'
class='qujian'
status='normal' @click='onSelectReverse'>
反向选择
</a-button>
<a-button shape='square' v-if='!rangIsSelecting && uploadedStore.ListSelected.size > 0' type='text' tabindex='-1'
class='qujian'
status='normal' @click='onSelectCancel'>
取消已选
</a-button>
</div>
<div style="flex-grow: 1"></div>
<div class="cell pr"></div>
</div>
<div class="toppanlist" :style="{ height: winStore.GetListHeight }" @keydown.space.prevent="() => true">
@@ -363,23 +190,17 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
:loading="uploadedStore.ListLoading"
tabindex="-1"
@scroll="onHideRightMenuScroll">
<template #empty>
<a-empty description="没有 已上传 的任务" />
</template>
<template #empty><a-empty description="没有 已上传 的任务" /></template>
<template #item="{ item, index }">
<div :key="item.TaskID" class="listitemdiv">
<div
:class="'fileitem ' + (uploadedStore.ListSelected.has(item.TaskID) ? ' selected' : '') + (uploadedStore.ListFocusKey == item.TaskID ? ' focus' : '')"
@click="handleSelect(item.TaskID, $event)"
@mouseover='onSelectRang(item.TaskID)'
@dblclick="() => handleDbClick(item.TaskID)"
@contextmenu="(event:MouseEvent)=>handleRightClick({event,node:{key:item.TaskID}} )">
<div
:class="'rangselect ' + (rangSelectFiles[item.TaskID] ? (rangSelectStart == item.TaskID ? 'rangstart' : rangSelectEnd == item.TaskID ? 'rangend' : 'rang') : '')">
<a-button shape='circle' type='text' tabindex='-1' class='select' :title='index'
@click.prevent.stop='handleSelect(item.TaskID, $event, true)'>
<i
:class="uploadedStore.ListSelected.has(item.TaskID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
<div style="margin: 2px">
<a-button shape="circle" type="text" tabindex="-1" class="select" :title="index" @click.prevent.stop="handleSelect(item.TaskID, $event, true)">
<i :class="uploadedStore.ListSelected.has(item.TaskID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</div>
<div class="fileicon">
@@ -401,8 +222,7 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
<a-button type="text" tabindex="-1" title="打开文件夹" @click.prevent.stop="onSelectFile(item, 'dir')">
<i class="iconfont iconfile-folder" />
</a-button>
<a-button type="text" tabindex="-1" title="删除上传记录"
@click.prevent.stop="onSelectFile(item, 'delete')">
<a-button type="text" tabindex="-1" title="删除上传记录" @click.prevent.stop="onSelectFile(item, 'delete')">
<i class="iconfont icondelete" />
</a-button>
</div>
@@ -410,23 +230,22 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
</div>
</template>
</a-list>
<a-dropdown id="rightuploadedmenu" class="rightmenu" :popup-visible="true" tabindex="-1" :draggable="false"
style="z-index: -1; left: -200px; opacity: 0">
<a-dropdown id="rightuploadedmenu" class="rightmenu" :popup-visible="true" tabindex="-1" :draggable="false" style="z-index: -1; left: -200px; opacity: 0">
<template #content>
<a-doption @click="() => onSelectFile(undefined, 'pan')">
<template #icon><i class="iconfont iconcloud" /></template>
<template #icon> <i class="iconfont iconcloud" /> </template>
<template #default>定位到网盘</template>
</a-doption>
<a-doption @click="() => onSelectFile(undefined, 'file')">
<template #icon><i class="iconfont iconwenjian" /></template>
<template #icon> <i class="iconfont iconwenjian" /> </template>
<template #default>打开文件</template>
</a-doption>
<a-doption @click="() => onSelectFile(undefined, 'dir')">
<template #icon><i class="iconfont iconfile-folder" /></template>
<template #icon> <i class="iconfont iconfile-folder" /> </template>
<template #default>打开文件夹</template>
</a-doption>
<a-doption @click="() => onSelectFile(undefined, 'delete')">
<template #icon><i class="iconfont icondelete" /></template>
<template #icon> <i class="iconfont icondelete" /> </template>
<template #default>删除上传记录</template>
</a-doption>
</template>
@@ -453,7 +272,6 @@ const onSelectFile = (item: IStateUploadTask | undefined, cmd: string) => {
border: none !important;
margin: 0 1px;
}
.downedbtn > button .iconfont {
font-size: 24px;
line-height: 30px;

View File

@@ -1,13 +1,5 @@
<script setup lang="ts">
import {
KeyboardState,
MouseState,
useAppStore,
useDowningStore,
useKeyboardStore,
useMouseStore,
useWinStore
} from '../store'
import { KeyboardState, useAppStore, useKeyboardStore, useWinStore } from '../store'
import {
onHideRightMenuScroll,
onShowRightMenu,
@@ -16,29 +8,18 @@ import {
TestKeyboardScroll,
TestKeyboardSelect
} from '../utils/keyboardhelper'
import { computed, ref, watch } from 'vue'
import { ref } from 'vue'
import useUploadingStore from './UploadingStore'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import 'ant-design-vue/es/tooltip/style/css'
import UploadingDAL from '../transfer/uploadingdal'
import { TestButton } from '../utils/mosehelper'
import { xorWith } from 'lodash'
const viewlist = ref()
const appStore = useAppStore()
const winStore = useWinStore()
const uploadingStore = useUploadingStore()
const isUpload = computed(() => uploadingStore.ListDataUploadingCount > 0)
watch(isUpload, (value, oldValue) => {
if (value !== oldValue && window.WebToElectron) {
if (value) {
window.WebToElectron({ cmd: 'preventSleep', flag: 1 })
} else if (useDowningStore().ListDataDowningCount == 0) {
window.WebToElectron({ cmd: 'preventSleep', flag: 0 })
}
}
})
const menuShowDir = ref(false)
const menuShowTask = ref(false)
uploadingStore.$subscribe((_m: any, state: any) => {
@@ -59,132 +40,13 @@ keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (TestKeyboardScroll(state.KeyDownEvent, viewlist.value, uploadingStore)) return
})
const rangIsSelecting = ref(false)
const rangSelectID = ref(-1)
const rangSelectStart = ref(-1)
const rangSelectEnd = ref(-1)
const rangSelectFiles = ref<{ [k: number]: any }>({})
const onSelectRangStart = () => {
onHideRightMenuScroll()
rangIsSelecting.value = !rangIsSelecting.value
rangSelectID.value = -1
rangSelectStart.value = -1
rangSelectEnd.value = -1
rangSelectFiles.value = {}
uploadingStore.mRefreshListDataShow(false)
}
const onSelectReverse = () => {
onHideRightMenuScroll()
const listData = uploadingStore.ListDataShow
const listSelected = uploadingStore.GetSelected()
const reverseSelect = xorWith(listData, listSelected, (a, b) => a.UploadID === b.UploadID)
uploadingStore.ListSelected.clear()
uploadingStore.ListFocusKey = -1
if (reverseSelect.length > 0) {
uploadingStore.mRangSelect(reverseSelect[0].UploadID, reverseSelect.map(r => r.UploadID))
}
uploadingStore.mRefreshListDataShow(false)
}
const onSelectCancel = () => {
onHideRightMenuScroll()
uploadingStore.ListSelected.clear()
uploadingStore.ListFocusKey = 0
uploadingStore.mRefreshListDataShow(false)
}
const onSelectRang = (file_id: number) => {
if (rangIsSelecting.value && rangSelectID.value != -1) {
let startid = rangSelectID.value
let endid = -1
const s: { [k: string]: any } = {}
const children = uploadingStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].UploadID == file_id) a = i
if (children[i].UploadID == startid) b = i
if (a > 0 && b > 0) break
}
if (a >= 0 && b >= 0) {
if (a > b) {
;[a, b] = [b, a]
endid = file_id
} else {
endid = startid
startid = file_id
}
for (let n = a; n <= b; n++) {
s[children[n].UploadID] = true
}
}
rangSelectStart.value = startid
rangSelectEnd.value = endid
rangSelectFiles.value = s
uploadingStore.mRefreshListDataShow(false)
}
}
const mouseStore = useMouseStore()
mouseStore.$subscribe((_m: any, state: MouseState) => {
if (appStore.appTab != 'down') return
const mouseEvent = state.MouseEvent
// console.log('MouseEvent', state.MouseEvent)
if (TestButton(0, mouseEvent, () => {
if (mouseEvent.srcElement) {
// @ts-ignore
const className = mouseEvent.srcElement.className
if (className && className.toString().startsWith('arco-virtual-list')) {
onSelectCancel()
}
}
})) return
})
const handleRefresh = () => UploadingDAL.aReloadUploading()
const handleBack = () => UploadingDAL.mUploadingShowTaskBack()
const handleSelectAll = () => uploadingStore.mSelectAll()
const handleSelect = (UploadID: number, event: any, isCtrl: boolean = false) => {
onHideRightMenuScroll()
if (rangIsSelecting.value) {
if (rangSelectID.value == -1) {
if (!uploadingStore.ListSelected.has(UploadID)) {
uploadingStore.mMouseSelect(UploadID, true, false)
}
rangSelectID.value = UploadID
rangSelectStart.value = UploadID
rangSelectFiles.value = { [UploadID]: true }
} else {
const start = rangSelectID.value
const children = uploadingStore.ListDataShow
let a = -1
let b = -1
for (let i = 0, maxi = children.length; i < maxi; i++) {
if (children[i].UploadID == UploadID) a = i
if (children[i].UploadID == start) b = i
if (a > 0 && b > 0) break
}
const fileList: number[] = []
if (a >= 0 && b >= 0) {
if (a > b) [a, b] = [b, a]
for (let n = a; n <= b; n++) {
fileList.push(children[n].UploadID)
}
}
uploadingStore.mRangSelect(UploadID, fileList)
rangIsSelecting.value = false
rangSelectID.value = -1
rangSelectStart.value = -1
rangSelectEnd.value = -1
rangSelectFiles.value = {}
}
uploadingStore.mRefreshListDataShow(false)
} else {
uploadingStore.mMouseSelect(UploadID, event.ctrlKey || isCtrl, event.shiftKey)
if (!uploadingStore.ListSelected.has(UploadID)) uploadingStore.ListFocusKey = -1
}
}
const handleRightClick = (e: { event: MouseEvent; node: any }) => {
@@ -211,14 +73,12 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<div style="height: 14px"></div>
<div class="toppanbtns" style="height: 26px">
<div class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" :disabled="uploadingStore.ListLoading" title="后退 Back Space"
@click="handleBack">
<a-button type="text" size="small" tabindex="-1" :disabled="uploadingStore.ListLoading" title="后退 Back Space" @click="handleBack">
<template #icon>
<i class="iconfont iconarrow-left-2-icon" />
</template>
</a-button>
<a-button type="text" size="small" tabindex="-1" :loading="uploadingStore.ListLoading" title="F5"
@click="handleRefresh">
<a-button type="text" size="small" tabindex="-1" :loading="uploadingStore.ListLoading" title="F5" @click="handleRefresh">
<template #icon>
<i class="iconfont iconreload-1-icon" />
</template>
@@ -226,29 +86,15 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</div>
<div v-if="uploadingStore.IsListSelected" class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(false, true)"><i
class="iconfont iconstart" />开始
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(false, false)"><i
class="iconfont iconpause" />暂停
</a-button>
<a-button v-show="menuShowDir" type="text" size="small" tabindex="-1"
@click="() => UploadingDAL.mUploadingShowTask()"><i class="iconfont icongengduo1" />查看
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingDelete(false)"><i
class="iconfont icondelete" />清除
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(false, true)"><i class="iconfont iconstart" />开始</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(false, false)"><i class="iconfont iconpause" />暂停</a-button>
<a-button v-show="menuShowDir" type="text" size="small" tabindex="-1" @click="() => UploadingDAL.mUploadingShowTask()"><i class="iconfont icongengduo1" />查看</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingDelete(false)"><i class="iconfont icondelete" />清除</a-button>
</div>
<div class="toppanbtn">
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(true, true)"><i
class="iconfont iconstart" />开始全部
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(true, false)"><i
class="iconfont iconpause" />暂停全部
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingDelete(true)"><i
class="iconfont iconrest" />清空全部
</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(true, true)"><i class="iconfont iconstart" />开始全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingStart(true, false)"><i class="iconfont iconpause" />暂停全部</a-button>
<a-button type="text" size="small" tabindex="-1" @click="() => UploadingDAL.aUploadingDelete(true)"><i class="iconfont iconrest" />清空全部</a-button>
</div>
</div>
<div style="height: 9px"></div>
@@ -259,44 +105,14 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<i :class="uploadingStore.IsListSelectedAll ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</AntdTooltip>
<div class='selectInfo'>{{ uploadingStore.ListDataSelectCountInfo }}</div>
<div style='margin: 0 2px'>
<AntdTooltip placement='rightTop' v-if="uploadingStore.ListDataShow.length > 0">
<a-button shape='square' type='text' tabindex='-1' class='qujian'
:status="rangIsSelecting ? 'danger' : 'normal'" title='Ctrl+Q' @click='onSelectRangStart'>
{{ rangIsSelecting ? '取消选择' : '区间选择' }}
</a-button>
<template #title>
<div>
第1步: 点击 区间选择 这个按钮
<br />
第2步: 鼠标点击一个文件
<br />
第3步: 移动鼠标点击另外一个文件
</div>
</template>
</AntdTooltip>
<a-button shape='square'
v-if='!rangIsSelecting && uploadingStore.ListSelected.size > 0 && uploadingStore.ListSelected.size < uploadingStore.ListDataShow.length'
type='text'
tabindex='-1'
class='qujian'
status='normal' @click='onSelectReverse'>
反向选择
</a-button>
<a-button shape='square' v-if='!rangIsSelecting && uploadingStore.ListSelected.size > 0' type='text'
tabindex='-1' class='qujian'
status='normal' @click='onSelectCancel'>
取消已选
</a-button>
</div>
</div>
<div class="selectInfo">{{ uploadingStore.ListDataSelectCountInfo }}</div>
<div style="flex-grow: 1"></div>
<div class="cell tiquma">瞬时速度</div>
<div class="cell pr"></div>
</div>
<div class="toppanlist" style="position: relative" :style="{ height: winStore.GetListHeight }"
@keydown.space.prevent="() => true">
<div class="toppanlist" style="position: relative" :style="{ height: winStore.GetListHeight }" @keydown.space.prevent="() => true">
<a-list
ref="viewlist"
:bordered="false"
@@ -313,31 +129,24 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
:data="uploadingStore.ListDataShow"
tabindex="-1"
@scroll="onHideRightMenuScroll">
<template #empty>
<a-empty description="没有 需要上传 的任务" />
</template>
<template #empty><a-empty description="没有 需要上传 的任务" /></template>
<template #item="{ item, index }">
<div :key="item.UploadID" class="listitemdiv">
<div
:class="'fileitem ' + (uploadingStore.ListSelected.has(item.UploadID) ? ' selected' : '') + (uploadingStore.ListFocusKey == item.UploadID ? ' focus' : '') + (item.uploadState == 'hashing' || item.uploadState == 'running' ? ' running' : '')"
@click="handleSelect(item.UploadID, $event)"
@mouseover='onSelectRang(item.UploadID)'
@dblclick="UploadingDAL.aUploadingStartOne(item.UploadID)"
@contextmenu="(event:MouseEvent)=>handleRightClick({event,node:{key:item.UploadID}} )">
<div
:class="'rangselect ' + (rangSelectFiles[item.UploadID] ? (rangSelectStart == item.UploadID ? 'rangstart' : rangSelectEnd == item.UploadID ? 'rangend' : 'rang') : '')">
<a-button shape='circle' type='text' tabindex='-1' class='select' :title='index'
@click.prevent.stop='handleSelect(item.UploadID, $event, true)'>
<i
:class="uploadingStore.ListSelected.has(item.UploadID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
<div style="margin: 2px">
<a-button shape="circle" type="text" tabindex="-1" class="select" :title="index" @click.prevent.stop="handleSelect(item.UploadID, $event, true)">
<i :class="uploadingStore.ListSelected.has(item.UploadID) ? 'iconfont iconrsuccess' : 'iconfont iconpic2'" />
</a-button>
</div>
<div class="fileicon">
<i :class="'iconfont ' + item.icon" aria-hidden="true"></i>
</div>
<div class="filename">
<div v-if="item.isDir && menuShowTask" :title="item.localFilePath"
@click.stop="UploadingDAL.mUploadingShowTask(item.TaskID)">
<div v-if="item.isDir && menuShowTask" :title="item.localFilePath" @click.stop="UploadingDAL.mUploadingShowTask(item.TaskID)">
{{ item.name }}
</div>
<div v-else class="nopoint" :title="item.localFilePath">
@@ -349,12 +158,9 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
<div class="transfering-state">
<p class="text-state">{{ item.uploadState }} {{ item.ProgressStr }}</p>
<div class="progress-total">
<div
:class="'progress-current ' + (item.uploadState == 'success' ? ' succeed' : item.uploadState == 'error' ? ' error' : item.uploadState == '已暂停' ? '' : ' active')"
:style="{ width: item.Progress + '%' }"></div>
<div :class="'progress-current ' + (item.uploadState == 'success' ? ' succeed' : item.uploadState == 'error' ? ' error' : item.uploadState == '已暂停' ? '' : ' active')" :style="{ width: item.Progress + '%' }"></div>
</div>
<p v-if="item.isDir && menuShowTask" class="text-error pointer" :title="item.errorMessage"
@click.stop="UploadingDAL.mUploadingShowTask(item.TaskID)">{{ item.errorMessage }}</p>
<p v-if="item.isDir && menuShowTask" class="text-error pointer" :title="item.errorMessage" @click.stop="UploadingDAL.mUploadingShowTask(item.TaskID)">{{ item.errorMessage }}</p>
<p v-else class="text-error" :title="item.errorMessage">{{ item.errorMessage }}</p>
</div>
</div>
@@ -363,26 +169,25 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</div>
</template>
</a-list>
<a-dropdown id="rightuploadingmenu" class="rightmenu" :popup-visible="true" tabindex="-1" :draggable="false"
style="z-index: -1; left: -200px; opacity: 0">
<a-dropdown id="rightuploadingmenu" class="rightmenu" :popup-visible="true" tabindex="-1" :draggable="false" style="z-index: -1; left: -200px; opacity: 0">
<template #content>
<a-doption @click="() => UploadingDAL.aUploadingStart(false, true)">
<template #icon><i class="iconfont iconstart" /></template>
<template #icon> <i class="iconfont iconstart" /> </template>
<template #default>开始上传</template>
</a-doption>
<a-doption @click="() => UploadingDAL.aUploadingStart(false, false)">
<template #icon><i class="iconfont iconpause" /></template>
<template #icon> <i class="iconfont iconpause" /> </template>
<template #default>暂停上传</template>
</a-doption>
<a-doption v-show="menuShowDir" @click="() => UploadingDAL.mUploadingShowTask()">
<template #icon><i class="iconfont icongengduo1" /></template>
<template #icon> <i class="iconfont icongengduo1" /> </template>
<template #default>查看详情</template>
</a-doption>
<a-doption @click="() => UploadingDAL.aUploadingDelete(false)">
<template #icon><i class="iconfont icondelete" /></template>
<template #icon> <i class="iconfont icondelete" /> </template>
<template #default>清除上传</template>
</a-doption>
</template>
@@ -390,11 +195,32 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
</div>
</template>
<style scoped>
<style>
.rightBottomTip {
display: inline-block;
position: absolute;
bottom: 0;
right: 20px;
padding: 0;
opacity: 0.8;
font-size: 14px;
line-height: 16px;
color: var(--color-text-3);
}
.downHideTip {
padding: 8px;
text-align: center;
opacity: 0.5;
}
.downHideTip .iconfont {
color: #ccc;
font-size: 80px;
}
.fileitem .icondownload {
color: #bcb3b399;
}
.fileitem .running .icondownload {
color: #2196f3;
}
@@ -405,14 +231,14 @@ const handleRightClick = (e: { event: MouseEvent; node: any }) => {
width: 110px;
margin-right: 8px;
}
.downspeed {
flex-grow: 0;
flex-shrink: 0;
width: 130px;
overflow: hidden;
color: var(--color-text-4);
color: #00000033;
font-size: 25px;
text-align: right;
text-overflow: clip;
white-space: nowrap;
@@ -428,18 +254,15 @@ body[arco-theme='dark'] .downspeed {
width: 150px;
}
}
@media only screen and (min-width: 960px) {
.downprogress {
width: 170px;
}
}
@media only screen and (min-width: 1000px) {
.downprogress {
width: 180px;
}
.downspeed {
width: 150px;
}
@@ -463,7 +286,6 @@ body[arco-theme='dark'] .downspeed {
width: 100%;
overflow: visible;
}
.text-state {
max-width: 100%;
height: 16px;
@@ -476,7 +298,6 @@ body[arco-theme='dark'] .downspeed {
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.text-error {
width: 100%;
height: 16px;
@@ -487,7 +308,6 @@ body[arco-theme='dark'] .downspeed {
line-height: 16px;
white-space: nowrap;
}
.progress-total {
position: relative;
width: 100%;
@@ -496,11 +316,9 @@ body[arco-theme='dark'] .downspeed {
background: #84858d14;
border-radius: 1.5px;
}
body[arco-theme='dark'] .progress-total {
background: #84858d;
}
.progress-total .progress-current {
position: absolute;
top: 0;
@@ -514,15 +332,12 @@ body[arco-theme='dark'] .progress-total {
-o-transition: width 0.3s ease, opacity 0.3s ease;
transition: width 0.3s ease, opacity 0.3s ease;
}
.progress-total .progress-current.succeed {
background: #099970;
}
.progress-total .progress-current.error {
background: #f35b51;
}
.progress-total .progress-current.active {
background: linear-gradient(270deg, #ffba7a 0%, #ff74c7 8.56%, #637dff 26.04%, rgba(99, 125, 255, 0.2) 100%);
}

View File

@@ -1,11 +1,12 @@
import fuzzysort from 'fuzzysort'
import { defineStore } from 'pinia'
import DownDAL, { IStateDownFile } from './DownDAL'
import { GetFocusNext, GetSelectedList, KeyboardSelectOne, MouseSelectOne, SelectAll } from '../utils/selecthelper'
import { IStateDownFile } from './DownDAL'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../utils/selecthelper'
import { humanSize } from '../utils/format'
import message from '../utils/message'
import fs from 'fs'
import path from 'path'
import DBDown from '../utils/dbdown'
type Item = IStateDownFile
type State = DownState
@@ -176,16 +177,6 @@ const useDownStore = defineStore('down', {
this.mRefreshListDataShow(false)
},
mRangSelect(lastkey: string, file_idList: string[]) {
if (this.ListDataShow.length == 0) return
const selectedNew = new Set<string>(this.ListSelected)
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
selectedNew.add(file_idList[i])
}
this.$patch({ ListSelected: selectedNew, ListFocusKey: lastkey, ListSelectKey: lastkey })
this.mRefreshListDataShow(false)
},
GetSelected() {
return GetSelectedList(this.ListDataShow, KEY, this.ListSelected)
},
@@ -212,36 +203,34 @@ const useDownStore = defineStore('down', {
/**
* 删除下载完成,修改为“待删除”状态,并从列表中删除 <br/>
* @param downedIDList
* @param uploadIDList
*/
async mDeleteDowned(downedIDList: string[]) {
mDeleteUploaded(uploadIDList: string[]) {
const UploadedList = this.ListDataRaw
const newListSelected = new Set(this.ListSelected)
const newList: Item[] = []
const downedList: Item[] = this.ListDataRaw
const deleteList: Item[] = []
for (let j = 0; j < downedList.length; j++) {
const downID = downedList[j].DownID
if (downedIDList.includes(downID)) {
downedList[j].Down.DownState = '待删除'
deleteList.push(downedList[j])
for (let j = 0; j < UploadedList.length; j++) {
const downID = UploadedList[j].DownID
if (uploadIDList.includes(downID)) {
UploadedList[j].Down.DownState = '待删除'
if (newListSelected.has(downID)) newListSelected.delete(downID)
} else {
newList.push(downedList[j])
newList.push(UploadedList[j])
}
}
this.ListDataRaw = newList
this.ListSelected = newListSelected
await DownDAL.deleteDowned(false, deleteList)
DBDown.deleteDowneds(uploadIDList)
this.mRefreshListDataShow(true)
},
/**
* 删除全部
*/
async mDeleteAllDowned() {
await DownDAL.deleteDowned(true, this.ListDataRaw)
mDeleteAllUploaded() {
this.ListSelected = new Set<string>()
this.ListDataRaw.splice(0, this.ListDataRaw.length)
DBDown.deleteDownedAll()
this.mRefreshListDataShow(true)
},
@@ -254,6 +243,7 @@ const useDownStore = defineStore('down', {
*/
mOpenUploadedFile(file: Item | null, downIDList: string[], isDir: boolean) {
const DownedList = this.ListDataRaw
const openDir = (localFilePath: string) => {
try {
if (fs.existsSync(localFilePath)) {

View File

@@ -1,10 +1,12 @@
import fuzzysort from 'fuzzysort'
import { defineStore } from 'pinia'
import DownDAL, { IStateDownFile } from './DownDAL'
import { GetFocusNext, GetSelectedList, KeyboardSelectOne, MouseSelectOne, SelectAll } from '../utils/selecthelper'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../utils/selecthelper'
import { humanSize } from '../utils/format'
import message from '../utils/message'
import { useDownedStore } from '../store'
import DBDown from '../utils/dbdown'
import { AriaDeleteList, AriaStopList } from '../utils/aria2c'
type Item = IStateDownFile
type State = DowningState
@@ -46,10 +48,6 @@ const useDowningStore = defineStore('downing', {
return state.ListDataShow.length
},
ListDataDowningCount(state: State): number {
return state.ListDataRaw.filter((down: any) => down.Down.IsDowning).length
},
IsListSelected(state: State): boolean {
return state.ListSelected.size > 0
},
@@ -175,16 +173,6 @@ const useDowningStore = defineStore('downing', {
this.mRefreshListDataShow(false)
},
mRangSelect(lastkey: string, file_idList: string[]) {
if (this.ListDataShow.length == 0) return
const selectedNew = new Set<string>(this.ListSelected)
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
selectedNew.add(file_idList[i])
}
this.$patch({ ListSelected: selectedNew, ListFocusKey: lastkey, ListSelectKey: lastkey })
this.mRefreshListDataShow(false)
},
GetSelected() {
return GetSelectedList(this.ListDataShow, KEY, this.ListSelected)
},
@@ -226,22 +214,26 @@ const useDowningStore = defineStore('downing', {
},
mAddDownload({ downlist }: { downlist: Item[] }) {
const savelist = []
const DowningList = this.ListDataRaw
const haslist = new Set(DowningList.map(item => item.DownID))
for (const downitem of downlist) {
const savelist = []
const haslist = new Map<string, boolean>()
for (let i = 0; i < DowningList.length; i++) {
haslist.set(DowningList[i].DownID, true)
}
for (let d = 0; d < downlist.length; d++) {
const downitem = downlist[d]
if (!haslist.has(downitem.DownID)) {
Object.freeze(downitem.Info)
savelist.push(downitem)
haslist.add(downitem.DownID)
}
}
if (savelist.length === 0) {
message.info('下载任务已存在,请勿重复创建任务')
} else {
DBDown.saveDownings(JSON.parse(JSON.stringify(savelist)))
DowningList.push(...savelist)
this.mRefreshListDataShow(true)
if (savelist.length == 0) {
message.info('下载任务已存在,请勿重复创建任务')
} else {
message.success('成功创建 ' + savelist.length.toString() + '个下载任务')
}
},
@@ -250,10 +242,21 @@ const useDowningStore = defineStore('downing', {
*/
mStartDowning() {
const DowningList = this.ListDataRaw
for (const downID of this.ListSelected) {
const selectedDown: IStateDownFile | undefined = DowningList.find(down => down.DownID === downID)
if (selectedDown && !selectedDown.Down.IsDowning && !selectedDown.Down.IsCompleted) {
this.mUpdateDownState(selectedDown, 'queue')
for (const DownID of this.ListSelected) {
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID) {
const down = DowningList[j].Down
if (down.IsDowning || down.IsCompleted) continue
down.IsStop = false
down.DownState = '队列中'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
break
}
}
}
},
@@ -266,15 +269,23 @@ const useDowningStore = defineStore('downing', {
for (let j = 0; j < DowningList.length; j++) {
const down = DowningList[j].Down
if (down.IsDowning || down.IsCompleted) continue
this.mUpdateDownState(DowningList[j], 'queue')
down.IsStop = false
down.DownState = '队列中'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
}
},
/**
* 暂停下载,只改变状态,待定时任务处理
*/
async mStopDowning() {
mStopDowning() {
const gidList: string[] = []
const downIDList: string[] = []
const downList: Item[] = []
const DowningList = this.ListDataRaw
for (const DownID of this.ListSelected) {
@@ -283,29 +294,49 @@ const useDowningStore = defineStore('downing', {
const down = DowningList[j].Down
if (down.IsCompleted) continue
gidList.push(DowningList[j].Info.GID)
downIDList.push(DowningList[j].DownID)
downList.push(DowningList[j])
this.mUpdateDownState(DowningList[j], 'stop')
down.IsDowning = false
down.IsCompleted = false
down.IsStop = true
down.DownState = '已暂停'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
break
}
}
}
await DownDAL.stopDowning(downList, gidList)
DownDAL.stopDowning(downList, gidList)
this.mRefreshListDataShow(true)
},
/**
* 暂停全部
*/
async mStopAllDowning() {
mStopAllDowning() {
const gidList: string[] = []
const downIDList: string[] = []
const DowningList = this.ListDataRaw
for (let j = 0; j < DowningList.length; j++) {
const down = DowningList[j].Down
if (down.IsCompleted) continue
downIDList.push(DowningList[j].DownID)
gidList.push(DowningList[j].Info.GID)
this.mUpdateDownState(DowningList[j], 'stop')
down.IsDowning = false
down.IsStop = true
down.DownState = '已暂停'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
}
await DownDAL.stopDowning(DowningList, gidList)
DownDAL.stopDowning(DowningList, gidList)
this.mRefreshListDataShow(true)
},
@@ -314,45 +345,44 @@ const useDowningStore = defineStore('downing', {
* 注:下载服务中的执行列表,请根据状态做进一步处理
* @param downIDList
*/
async mDeleteDowning(downIDList: string[]) {
mDeleteDowning(downIDList: string[]) {
const gidList: string[] = []
const DowningList = this.ListDataRaw
const newListSelected = new Set(this.ListSelected)
const newList: Item[] = []
const DowningList: Item[] = this.ListDataRaw
const deleteList: Item[] = []
for (let j = 0; j < DowningList.length; j++) {
const DownID = DowningList[j].DownID
if (downIDList.includes(DownID)) {
DowningList[j].Down.DownState = '待删除'
gidList.push(DowningList[j].Info.GID)
deleteList.push(DowningList[j])
if (newListSelected.has(DownID)) {
newListSelected.delete(DownID)
}
DowningList[j].Down.DownState = '待删除'
if (newListSelected.has(DownID)) newListSelected.delete(DownID)
} else {
newList.push(DowningList[j])
}
}
this.ListDataRaw = newList
this.ListSelected = newListSelected
await DownDAL.deleteDowning(false, deleteList, gidList)
DownDAL.deleteDowning(false, DowningList, gidList)
this.mRefreshListDataShow(true)
AriaStopList(gidList).then(r => {})
AriaDeleteList(gidList).then(r => {})
// DownDAL.deleteDowning(false, downIDList) // TODO
},
/**
* 删除全部,修改为“待删除”状态,并从列表中删除 <br/>
* 注:下载服务中的执行列表,请根据状态做进一步处理
*/
async mDeleteAllDowning() {
mDeleteAllDowning() {
const gidList: string[] = []
const DowningList = this.ListDataRaw
this.ListSelected = new Set<string>()
for (let j = 0; j < DowningList.length; j++) {
DowningList[j].Down.DownState = '待删除'
gidList.push(DowningList[j].Info.GID)
}
await DownDAL.deleteDowning(true, DowningList, gidList)
DowningList.splice(0, DowningList.length)
this.ListSelected = new Set<string>()
DownDAL.deleteDowning(true, DowningList, gidList)
this.mRefreshListDataShow(true)
},
@@ -383,72 +413,35 @@ const useDowningStore = defineStore('downing', {
this.mRefreshListDataShow(true)
},
mUpdateDownState(DownItem: IStateDownFile, state: string, msg?: string) {
const { DownID, Down } = DownItem
const updateState: any = {
DownID: DownID,
IsDowning: false,
IsCompleted: false,
DownProcess: 0,
DownSpeedStr: '',
DownState: '',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: ''
}
switch (state) {
case 'start':
updateState.DownState = '解析中'
updateState.IsDowning = true
updateState.DownTime = Date.now()
break
case 'queue':
updateState.IsDowning = false
updateState.DownState = '队列中'
break
case 'success':
updateState.IsDowning = true
updateState.DownState = '下载中'
break
case 'downed':
updateState.IsDowning = true
updateState.IsCompleted = true
updateState.DownState = '已完成'
updateState.DownProcess = 100
break
case 'valid':
updateState.IsDowning = true
updateState.IsCompleted = true
updateState.DownState = '校验中'
updateState.DownProcess = 100
break
case 'stop':
updateState.IsDowning = false
updateState.DownState = '已暂停'
updateState.DownSpeed = 0
updateState.DownSpeedStr = ''
updateState.IsStop = true
break
case 'error':
updateState.DownState = '已出错'
updateState.DownSpeed = 0
updateState.AutoTry = Date.now()
updateState.IsFailed = true
updateState.FailedMessage = msg || state
break
default:
updateState.DownState = '已出错'
updateState.DownSpeed = 0
updateState.AutoTry = Date.now()
updateState.IsFailed = true
updateState.FailedCode = 504
updateState.FailedMessage = msg || state
mSaveToDowned(DownID: string) {
const DowningList = this.ListDataRaw
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID && DowningList[j].Down.DownState === '已完成') {
const item = DowningList[j]
DowningList.splice(j, 1)
DBDown.deleteDowning(item.DownID)
item.Down.DownTime = Date.now()
item.DownID = item.Down.DownTime.toString() + '_' + item.DownID
useDownedStore().ListDataRaw.splice(0, 0, item)
useDownedStore().mRefreshListDataShow(true)
DBDown.saveDowned(item.DownID, JSON.parse(JSON.stringify(item)))
break
}
DownItem.Down = { ...DownItem.Down, ...updateState }
}
if (this.ListSelected.has(DownID)) this.ListSelected.delete(DownID)
},
mUpdateDownState(data: any) {
const DowningList = this.ListDataRaw
const DownID = data.DownID
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID) {
DowningList[j].Down = { ...DowningList[j].Down, ...data }
break
}
}
}
}
})

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'
import { GetFocusNext, GetSelectedList, KeyboardSelectOne, MouseSelectOne, SelectAll } from '../utils/selecthelper'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../utils/selecthelper'
import { IStateUploadTask } from '../utils/dbupload'
import fuzzysort from 'fuzzysort'
import fuzzysort from "fuzzysort"
type Item = IStateUploadTask
@@ -138,16 +138,6 @@ const useUploadedStore = defineStore('uploaded', {
this.mRefreshListDataShow(false)
},
mRangSelect(lastkey: number, file_idList: number[]) {
if (this.ListDataShow.length == 0) return
const selectedNew = new Set<number>(this.ListSelected)
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
selectedNew.add(file_idList[i])
}
this.$patch({ ListSelected: selectedNew, ListFocusKey: lastkey, ListSelectKey: lastkey })
this.mRefreshListDataShow(false)
},
GetSelected() {
return GetSelectedList(this.ListDataShow, KEY, this.ListSelected)
},

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { GetFocusNext, GetSelectedList, KeyboardSelectOne, MouseSelectOne, SelectAll } from '../utils/selecthelper'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../utils/selecthelper'
export interface IUploadingModel {
@@ -64,9 +64,6 @@ const useUploadingStore = defineStore('uploading', {
}),
getters: {
ListDataUploadingCount(state: State): number {
return state.ListDataShow.length
},
IsListSelected(state: State): boolean {
return state.ListSelected.size > 0
@@ -157,16 +154,6 @@ const useUploadingStore = defineStore('uploading', {
this.mRefreshListDataShow(false)
},
mRangSelect(lastkey: number, file_idList: number[]) {
if (this.ListDataShow.length == 0) return
const selectedNew = new Set<number>(this.ListSelected)
for (let i = 0, maxi = file_idList.length; i < maxi; i++) {
selectedNew.add(file_idList[i])
}
this.$patch({ ListSelected: selectedNew, ListFocusKey: lastkey, ListSelectKey: lastkey })
this.mRefreshListDataShow(false)
},
GetSelected() {
return GetSelectedList(this.ListDataShow, KEY, this.ListSelected)
},

View File

@@ -15,7 +15,7 @@ const appStore = useAppStore()
<template>
<a-layout style="height: 100%">
<a-layout-sider hide-trigger :width="158" class="xbyleft">
<div class="headdesc">传输文件</div>
<div class="headdesc">上传下载文件</div>
<a-menu :style="{ width: '100%' }" class="xbyleftmenu" :selected-keys="[appStore.GetAppTabMenu]" @update:selected-keys="appStore.toggleTabMenu('down', $event[0])">
<a-menu-item key="DowningRight">
<template #icon><i class="iconfont icondownload" /></template>
@@ -23,7 +23,7 @@ const appStore = useAppStore()
</a-menu-item>
<a-menu-item key="DownedRight">
<template #icon><i class="iconfont icondesktop" /></template>
已下载
已下载
</a-menu-item>
<a-menu-item key="UploadingRight">
<template #icon><i class="iconfont iconcloud-upload" /></template>
@@ -31,19 +31,19 @@ const appStore = useAppStore()
</a-menu-item>
<a-menu-item key="UploadedRight">
<template #icon><i class="iconfont iconcloud_success" /></template>
已上传
已上传
</a-menu-item>
<a-menu-item key="SyncRight">
<template #icon><i class="iconfont iconcloud-sync" /></template>
文件夹同步 x
</a-menu-item>
<!-- <a-menu-item key="SyncRight">-->
<!-- <template #icon><i class="iconfont iconcloud-sync" /></template>-->
<!-- 文件夹同步 x-->
<!-- </a-menu-item>-->
<a-menu-item key="M3U8DowingRight">
<template #icon><i class="iconfont iconluxiang" /></template>
M3U8下载中
</a-menu-item>
<a-menu-item key="M3U8DowedRight">
<template #icon><i class="iconfont iconluxiang" /></template>
M3U8下载完
M3U8下载完
</a-menu-item>
</a-menu>
</a-layout-sider>

View File

@@ -5,7 +5,7 @@ import {
useSettingStore,
useM3u8DownloadedStore,
useM3u8DownloadingStore,
useFootStore, useDowningStore, useDownedStore
useFootStore,
} from '../../store'
import {
AriaAddUrl,
@@ -22,9 +22,66 @@ import TreeStore from "../../store/treestore";
import fs from "fs";
import { execFile } from 'child_process'
import DBDown from '../../utils/dbdown'
import { IAriaDownProgress, IStateDownFile } from '../DownDAL'
import { getEncType } from '../../utils/proxyhelper'
import fsPromises from 'fs/promises'
export interface IStateDownFile {
DownID: string
Info: IStateDownInfo
Down: {
DownState: string
DownTime: number
DownSize: number
DownSpeed: number
DownSpeedStr: string
DownProcess: number
IsStop: boolean
IsDowning: boolean
IsCompleted: boolean
IsFailed: boolean
FailedCode: number
FailedMessage: string
AutoTry: number
DownUrl: string
}
}
export interface IStateDownInfo {
GID: string
user_id: string
DownSavePath: string
ariaRemote: boolean
file_id: string
drive_id: string
name: string
size: number
sizestr: string
icon: string
isDir: boolean
sha1: string
crc64: string
m3u8_total_file_nums?:number
m3u8_parent_file_name?:string
}
export interface IAriaDownProgress {
gid: string
status: string
totalLength: string
completedLength: string
downloadSpeed: string
errorCode: string
errorMessage: string
}
/** 存盘的时机:默认 10 时进行 */
let SaveTimeWait = 0
@@ -44,9 +101,9 @@ export default class M3u8DownloadDAL {
/**
* 从DB中加载数据
*/
static async aReloadDowning() {
static async aReloadDowning () {
const downingStore = useM3u8DownloadingStore()
if (downingStore.ListLoading) return
if(downingStore.ListLoading) return
downingStore.ListLoading = true
const stateDownFiles = await DBDown.getDowningAll()
// 首次从DB中加载数据如果上次意外停止则重新开始如果手动暂停则保持
@@ -57,23 +114,23 @@ export default class M3u8DownloadDAL {
down.IsCompleted = false
down.IsStop = false
down.DownState = '队列中'
down.DownSpeed = 0
down.DownSpeed = 0;
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedCode = 0;
down.FailedMessage = ''
down.AutoTry = 0
down.AutoTry = 0;
down.IsDowning = false
}
}
downingStore.ListDataRaw = stateDownFiles
downingStore.ListLoading = false
downingStore.mRefreshListDataShow(true)
downingStore.ListLoading = false
}
static async aReloadDowned() {
static async aReloadDowned () {
const downedStore = useM3u8DownloadedStore()
if (downedStore.ListLoading) return
if(downedStore.ListLoading) return
downedStore.ListLoading = true
const max = useSettingStore().debugDownedListMax
const showlist = await DBDown.getDownedByTop(max)
@@ -153,7 +210,6 @@ export default class M3u8DownloadDAL {
sizestr: file.sizeStr,
isDir: file.isDir,
icon: file.icon,
encType: getEncType(file),
sha1: '',
crc64: crc64,
m3u8_total_file_nums: file.m3u8_total_file_nums,
@@ -182,7 +238,7 @@ export default class M3u8DownloadDAL {
downlist.push(downitem);
}
useM3u8DownloadingStore().mAddDownload({ downlist});
useM3u8DownloadingStore().mAddDownload({ downlist, tip});
}
/**
@@ -190,58 +246,100 @@ export default class M3u8DownloadDAL {
*/
static async aSpeedEvent() {
const downingStore = useM3u8DownloadingStore()
const downedStore = useM3u8DownloadedStore()
const settingStore = useSettingStore()
const isOnline = await AriaConnect()
if (isOnline && downingStore.ListDataRaw.length) {
await AriaGetM3u8DowningList()
if (isOnline) {
await AriaGetM3u8DowningList().catch(() => {
//
});
const DowningList = useM3u8DownloadingStore().ListDataRaw
let downingCount = 0;
const ariaRemote = IsAria2cRemote()
const DowningList: IStateDownFile[] = downingStore.ListDataRaw
const timeThreshold = Date.now() - 60 * 1000
const downFileMax = settingStore.downFileMax
const shouldSkipDown = (Down: any) => {
return (
Down.IsCompleted ||
Down.IsStop ||
Down.IsDowning ||
(Down.IsFailed && timeThreshold <= Down.AutoTry)
)
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].Info.ariaRemote != ariaRemote) continue;
if (DowningList[j].Down.IsDowning) {
downingCount++;
}
let addDowningCount = 0
for (let i = 0; i < DowningList.length; i++) {
const DownItem = DowningList[i]
const { DownID, Info, Down } = DownItem
if (Info.ariaRemote !== ariaRemote) continue
if (Down.IsCompleted && Down.DownState === '已完成') {
// 将下载标记为已完成并添加到列表以供稍后处理
const completedDownId = `${Date.now()}_${Down.DownTime}`
// 删除已完成的下载并更新数据库
DowningList.splice(i, 1)
await DBDown.deleteDowning(DownID)
// 将已完成的下载添加到下载文件列表中
const downedData = JSON.parse(JSON.stringify({ DownID: completedDownId, Down, Info }))
downedStore.ListDataRaw.unshift({ DownID: completedDownId, Down, Info })
downedStore.mRefreshListDataShow(true)
await DBDown.saveDowned(completedDownId, downedData)
if (downedStore.ListSelected.has(completedDownId)) {
downedStore.ListSelected.delete(completedDownId)
}
// 移除Aria2已完成的任务
await AriaDeleteList([Info.GID])
i--
} else if ((addDowningCount + downingStore.ListDataDowningCount) < downFileMax && !shouldSkipDown(Down)) {
addDowningCount++
downingStore.mUpdateDownState(DownItem, 'start')
let state = await AriaAddUrl(DownItem)
downingStore.mUpdateDownState(DownItem, state)
if (DowningList[j].Down.IsCompleted && DowningList[j].Down.DownState === '已完成') {
downingStore.mSaveToDowned(DowningList[j].DownID);
j--;
}
}
const time = Date.now() - 60 * 1000;
for (let j = 0; j < DowningList.length; j++) {
if (downingCount >= settingStore.downFileMax) break;
if (DowningList[j].Info.ariaRemote != ariaRemote) continue;
const DownID = DowningList[j].DownID;
const down = DowningList[j].Down;
if (down.IsCompleted == false && down.IsStop == false && down.IsDowning == false) {
if (down.IsFailed == false || time > down.AutoTry) {
downingCount += 1;
downingStore.mUpdateDownState({ DownID, IsDowning: true, DownSpeedStr: '', DownState: '解析中', DownTime: Date.now(), FailedCode: 0, FailedMessage: '' });
AriaAddUrl(DowningList[j]).then((ret) => {
if (ret == 'downed') {
downingStore.mUpdateDownState({
DownID,
IsDowning: true,
IsCompleted: true,
DownProcess: 100,
DownSpeedStr: '',
DownState: '已完成',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: '',
});
} else if (ret == 'success') {
downingStore.mUpdateDownState({
DownID,
IsDowning: true,
IsCompleted: false,
DownSpeedStr: '',
DownState: '下载中',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: '',
});
} else if (ret == '已暂停') {
downingStore.mUpdateDownState({
DownID,
IsDowning: false,
IsCompleted: false,
DownSpeedStr: '',
DownState: '已暂停',
AutoTry: 0,
IsFailed: false,
IsStop: true,
FailedCode: 0,
FailedMessage: '已暂停',
});
} else {
useFootStore().mSaveDownTotalSpeedInfo('')
downingStore.mUpdateDownState({
DownID,
IsDowning: false,
IsCompleted: false,
DownSpeedStr: '',
DownState: '已出错',
AutoTry: Date.now(),
IsFailed: true,
IsStop: false,
FailedCode: 504,
FailedMessage: ret,
});
}
});
}
}
}
}
downingStore.mRefreshListDataShow(true)
downedStore.mRefreshListDataShow(true)
useM3u8DownloadedStore().mRefreshListDataShow(true)
}
static compareTsFiles(a: string, b: string): number {
@@ -309,138 +407,158 @@ export default class M3u8DownloadDAL {
}
}
/**
* 速度事件方法
*/
static mSpeedEvent(list: IAriaDownProgress[]) {
const downingStore = useM3u8DownloadingStore()
const settingStore = useSettingStore()
const DowningList: IStateDownFile[] = downingStore.ListDataRaw
const DowningList = downingStore.ListDataRaw
if (list == undefined) list = [];
const dellist: string[] = [];
let hasSpeed = 0;
for (let n = 0; n < DowningList.length; n++) {
if (DowningList[n].Down.DownSpeedStr != '') {
const gid = DowningList[n].Info.GID;
let isFind = false;
for (let m = 0; m < list.length; m++) {
if (list[m].gid != gid) continue
if (list[m].gid == gid && list[m].status == 'active') {
isFind = true;
break;
}
}
if (!isFind) {
if (DowningList[n].Down.DownState != '已暂停') DowningList[n].Down.DownState = '队列中'
DowningList[n].Down.DownSpeed = 0;
DowningList[n].Down.DownSpeedStr = '';
}
}
}
const ariaRemote = !settingStore.AriaIsLocal
const dellist: string[] = []
const saveList: IStateDownFile[] = []
let hasSpeed = 0
for (const listItem of list) {
const saveList: IStateDownFile[] = [];
for (let i = 0; i < list.length; i++) {
try {
const { gid, status, totalLength, completedLength, downloadSpeed, errorCode, errorMessage } = listItem
const isComplete = status === 'complete'
const isDowning = isComplete || status === 'active' || status === 'waiting'
const isStop = status === 'paused' || status === 'removed'
const isError = status === 'error'
const downingItem: IStateDownFile | undefined = DowningList.find((item) => item.Info.ariaRemote === ariaRemote && item.Info.GID === gid)
if (!downingItem) continue
const { DownID, Down, Info } = downingItem
const totalLengthInt = parseInt(totalLength) || 0
Down.DownSize = parseInt(completedLength) || 0
Down.DownSpeed = parseInt(downloadSpeed) || 0
Down.DownSpeedStr = humanSize(Down.DownSpeed) + '/s'
Down.DownProcess = Math.floor((Down.DownSize * 100) / (totalLengthInt + 1)) % 100
Down.IsCompleted = isComplete
Down.IsDowning = isDowning
Down.IsFailed = isError
Down.IsStop = isStop
if (errorCode && errorCode !== '0') {
Down.FailedCode = parseInt(errorCode) || 0
Down.FailedMessage = FormatAriaError(errorCode, errorMessage)
const gid = list[i].gid
const isComplete = list[i].status === 'complete'
const isDowning = isComplete || list[i].status === 'active' || list[i].status === 'waiting'
const isStop = list[i].status === 'paused' || list[i].status === 'removed'
const isError = list[i].status === 'error'
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].Info.ariaRemote != ariaRemote) continue;
if (DowningList[j].Info.GID == gid) {
const downItem = DowningList[j];
const down = downItem.Down;
const totalLength = parseInt(list[i].totalLength) || 0;
down.DownSize = parseInt(list[i].completedLength) || 0;
down.DownSpeed = parseInt(list[i].downloadSpeed) || 0;
down.DownSpeedStr = humanSize(down.DownSpeed) + '/s';
down.DownProcess = Math.floor((down.DownSize * 100) / (totalLength + 1)) % 100;
down.IsCompleted = isComplete;
down.IsDowning = isDowning;
down.IsFailed = isError;
down.IsStop = isStop;
if (list[i].errorCode && list[i].errorCode != '0') {
down.FailedCode = parseInt(list[i].errorCode) || 0;
down.FailedMessage = FormatAriaError(list[i].errorCode, list[i].errorMessage);
}
if (isComplete) {
downingStore.mUpdateDownState(downingItem, 'valid')
const check = AriaHashFile(downingItem)
down.DownSize = downItem.Info.size;
down.DownSpeed = 0;
down.DownSpeedStr = '';
down.DownProcess = 100;
down.FailedCode = 0;
down.FailedMessage = '';
down.DownState = '校验中';
const check = AriaHashFile(downItem);
if (check.Check) {
if (useSettingStore().downFinishAudio && !sound.playing()) {
sound.play()
}
downingStore.mUpdateDownState(downingItem, 'downed')
this.tryConcatM3u8Video(downingItem)
downingStore.mUpdateDownState({
DownID: check.DownID,
DownState: '已完成',
IsFailed: false,
IsDowning: true,
IsStop: false,
IsCompleted: true,
FailedMessage: '',
});
this.tryConcatM3u8Video(downItem)
} else {
downingStore.mUpdateDownState(downingItem, 'error', '移动文件失败,请重新下载')
downingStore.mUpdateDownState({
DownID: check.DownID,
DownState: '已出错',
IsFailed: true,
IsDowning: false,
IsStop: true,
IsCompleted: false,
FailedMessage: '移动文件失败,请重新下载',
});
}
} else if (isStop) {
downingStore.mUpdateDownState(downingItem, 'stop')
dellist.push(gid)
} else if (isError) {
if (!Down.FailedMessage) {
Down.FailedMessage = '下载失败'
}
downingStore.mUpdateDownState(downingItem, 'error', Down.FailedMessage)
dellist.push(gid)
down.DownState = '已暂停';
down.DownSpeed = 0;
down.DownSpeedStr = '';
down.FailedCode = 0;
down.FailedMessage = '';
} else if (isStop || isError) {
down.DownState = '已出错';
down.DownSpeed = 0;
down.DownSpeedStr = '';
down.AutoTry = Date.now();
if (down.FailedMessage == '') down.FailedMessage = '下载失败';
} else if (isDowning) {
hasSpeed += Down.DownSpeed
let lastTime = ((totalLengthInt - Down.DownSize) / (Down.DownSpeed + 1)) % 356400
if (lastTime < 1) lastTime = 1
// 进度条
Down.DownState =
`${Down.DownProcess}% ${(lastTime / 3600).toFixed(0).padStart(2, '0')}:${((lastTime % 3600) / 60)
.toFixed(0)
.padStart(2, '0')}:${(lastTime % 60).toFixed(0).padStart(2, '0')}`
if (SaveTimeWait > 10) {
saveList.push(downingItem)
hasSpeed += down.DownSpeed;
let lasttime = ((totalLength - down.DownSize) / (down.DownSpeed + 1)) % 356400;
if (lasttime < 1) lasttime = 1;
down.DownState =
down.DownProcess.toString() +
'% ' +
(lasttime / 3600).toFixed(0).padStart(2, '0') +
':' +
((lasttime % 3600) / 60).toFixed(0).padStart(2, '0') +
':' +
(lasttime % 60).toFixed(0).padStart(2, '0');
if (SaveTimeWait > 10) saveList.push(downItem);
} else {
//console.log('update', DowningList[j]);
}
if (isStop || isError) {
dellist.push(gid);
}
downingStore.mRefreshListDataShow(true)
} catch {
// Ignore any errors
break;
}
}
// 存盘时间
SaveTimeWait = (SaveTimeWait + 1) % 11
if (saveList.length) {
DBDown.saveDownings(JSON.parse(JSON.stringify(saveList)))
}
if (dellist.length) {
AriaDeleteList(dellist).then()
} catch {}
}
if (saveList.length > 0) DBDown.saveDownings(JSON.parse(JSON.stringify(saveList)))
if (dellist.length > 0) AriaDeleteList(dellist).then()
if (SaveTimeWait > 10) SaveTimeWait = 0;
else SaveTimeWait++;
useFootStore().mSaveDownTotalSpeedInfo(hasSpeed && humanSizeSpeed(hasSpeed) || '')
}
static async deleteDowning(isAll: boolean, deleteList: IStateDownFile[], gidList: string[]) {
// 处理待删除文件
if (!isAll) {
const downIDList = deleteList.map(item => item.DownID)
// console.log('deleteDowning', deleteList)
await DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList)))
} else {
await DBDown.deleteDowningAll()
}
// 停止aria2下载任务
await AriaStopList(gidList)
await AriaDeleteList(gidList)
// 删除临时文件
for (let downFile of deleteList) {
let downInfo = downFile.Info
if (downInfo.ariaRemote) continue
try {
if (!downInfo.isDir) {
let filePath = path.join(downInfo.DownSavePath, downInfo.name)
let tmpFilePath1 = filePath + '.td.aria2'
let tmpFilePath2 = filePath + '.td'
await fsPromises.rm(tmpFilePath1, { recursive: true })
await fsPromises.rm(tmpFilePath2, { recursive: true })
}
} catch (e) {
}
}
}
static async deleteDowned(isAll: boolean, deleteList: IStateDownFile[]) {
if (!isAll) {
// 处理待删除状态
const downIDList = deleteList
.filter(list => list.Down.DownState === '待删除')
.map(item => item.DownID)
console.log('downedList', deleteList)
await DBDown.deleteDowneds(JSON.parse(JSON.stringify(downIDList)))
} else {
await DBDown.deleteDownedAll()
}
}
static async stopDowning(downList: IStateDownFile[], gidList: string[]) {
await DBDown.saveDownings(JSON.parse(JSON.stringify(downList)))
await AriaStopList(gidList)
}
/**
* 查询是否下载中
*/
static QueryIsDowning() {
return useM3u8DownloadingStore().ListDataDowningCount > 0
return false
}
/**
* ?查询选择的是否下载中?
*/
static QuerySelectedIsDowning() {
return false
}
}

View File

@@ -4,7 +4,8 @@ import { useAppStore, useKeyboardStore, KeyboardState, useWinStore, useM3u8Downl
import { onShowRightMenu, onHideRightMenuScroll, RefreshScroll, RefreshScrollTo, TestCtrl, TestKey,
TestKeyboardScroll, TestKeyboardSelect } from '../../utils/keyboardhelper'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import { IStateDownFile } from '../DownDAL'
import 'ant-design-vue/es/tooltip/style/css'
import { IStateDownFile } from './M3u8DownloadDAL'
const viewlist = ref()
const inputsearch = ref()
@@ -33,7 +34,7 @@ const handleSelect = (shareid: string, event: any) => {
downedStore.mMouseSelect(shareid, event.ctrlKey, event.shiftKey)
}
const handleDelete = () => downedStore.mDeleteDowned([...downedStore.ListSelected])
const handleDelete = () => downedStore.mDeleteUploaded([...downedStore.ListSelected])
const handleOpenFile = (file: IStateDownFile | null) =>
downedStore.mOpenUploadedFile(file, [...downedStore.ListSelected], false)
@@ -41,7 +42,7 @@ const handleOpenFile = (file: IStateDownFile | null) =>
const handleOpenDir = (file: IStateDownFile | null) =>
downedStore.mOpenUploadedFile(file, [...downedStore.ListSelected], true)
const handleDeleteAll = () => downedStore.mDeleteAllDowned()
const handleDeleteAll = () => downedStore.mDeleteAllUploaded()
const handleSearchInput = (value: string) => {
downedStore.mSearchListData(value)

View File

@@ -1,13 +1,12 @@
import fuzzysort from 'fuzzysort'
import { defineStore } from 'pinia'
import { IStateDownFile } from '../DownDAL'
import { IStateDownFile } from './M3u8DownloadDAL'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../../utils/selecthelper'
import { humanSize } from '../../utils/format'
import message from '../../utils/message'
import fs from 'fs'
import path from 'path'
import DBDown from '../../utils/dbdown'
import M3u8DownloadDAL from './M3u8DownloadDAL'
type Item = IStateDownFile
type State = DownState
@@ -202,36 +201,34 @@ const useM3u8DownloadedStore = defineStore('m3u8downloaded', {
/**
* 删除下载完成,修改为“待删除”状态,并从列表中删除 <br/>
* @param downedIDList
* @param uploadIDList
*/
async mDeleteDowned(downedIDList: string[]) {
const newListSelected = new Set(this.ListSelected)
const newList: Item[] = []
const downedList: Item[] = this.ListDataRaw
const deleteList: Item[] = []
for (let j = 0; j < downedList.length; j++) {
const downID = downedList[j].DownID
if (downedIDList.includes(downID)) {
downedList[j].Down.DownState = '待删除'
deleteList.push(downedList[j])
if (newListSelected.has(downID)) newListSelected.delete(downID)
mDeleteUploaded(uploadIDList: string[]) {
const UploadedList = this.ListDataRaw
const newListSelected = new Set(this.ListSelected);
const newList: Item[] = [];
for (let j = 0; j < UploadedList.length; j++) {
const downID = UploadedList[j].DownID;
if (uploadIDList.includes(downID)) {
UploadedList[j].Down.DownState = '待删除'
if (newListSelected.has(downID)) newListSelected.delete(downID);
} else {
newList.push(downedList[j])
newList.push(UploadedList[j]);
}
}
this.ListDataRaw = newList
this.ListSelected = newListSelected
await M3u8DownloadDAL.deleteDowned(false, deleteList)
this.ListDataRaw = newList;
this.ListSelected = newListSelected;
DBDown.deleteDowneds(uploadIDList)
this.mRefreshListDataShow(true)
},
/**
* 删除全部
*/
async mDeleteAllDowned() {
await M3u8DownloadDAL.deleteDowned(true, this.ListDataRaw)
mDeleteAllUploaded() {
this.ListSelected = new Set<string>()
this.ListDataRaw.splice(0, this.ListDataRaw.length)
DBDown.deleteDownedAll()
this.mRefreshListDataShow(true)
},

View File

@@ -4,6 +4,7 @@ import { useAppStore, useKeyboardStore, KeyboardState, useWinStore, useM3u8Downl
import { onShowRightMenu, onHideRightMenuScroll, RefreshScroll, RefreshScrollTo, TestCtrl, TestKey,
TestKeyboardScroll, TestKeyboardSelect } from '../../utils/keyboardhelper'
import { Tooltip as AntdTooltip } from 'ant-design-vue'
import 'ant-design-vue/es/tooltip/style/css'
const viewlist = ref()
const inputsearch = ref()

View File

@@ -1,13 +1,12 @@
import fuzzysort from 'fuzzysort'
import { defineStore } from 'pinia'
import DownDAL, { IStateDownFile } from '../DownDAL'
import { IStateDownFile } from './M3u8DownloadDAL'
import { GetSelectedList, GetFocusNext, SelectAll, MouseSelectOne, KeyboardSelectOne } from '../../utils/selecthelper'
import { humanSize } from '../../utils/format'
import message from '../../utils/message'
import { useM3u8DownloadedStore } from '../../store'
import { AriaDeleteList, AriaStopList } from '../../utils/aria2c'
import DBDown from '../../utils/dbdown'
import M3u8DownloadDAL from './M3u8DownloadDAL'
type Item = IStateDownFile
type State = DowningState
@@ -49,10 +48,6 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
return state.ListDataShow.length
},
ListDataDowningCount(state: State): number {
return state.ListDataRaw.filter((down: any) => down.Down.IsDowning).length
},
IsListSelected(state: State): boolean {
return state.ListSelected.size > 0
},
@@ -222,23 +217,29 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
}
},
mAddDownload({ downlist }: { downlist: Item[] }) {
const savelist = []
mAddDownload({ downlist, tip}: { downlist: Item[]; tip: boolean}) {
const DowningList = this.ListDataRaw
const haslist = new Set(DowningList.map(item => item.DownID))
for (const downitem of downlist) {
const savelist = []
const haslist = new Map<string, boolean>()
for (let i = 0; i < DowningList.length; i++) {
haslist.set(DowningList[i].DownID, true)
}
for (let d = 0; d < downlist.length; d++) {
const downitem = downlist[d]
if (!haslist.has(downitem.DownID)) {
Object.freeze(downitem.Info)
savelist.push(downitem)
haslist.add(downitem.DownID)
}
}
if (savelist.length === 0) {
DBDown.saveDownings(JSON.parse(JSON.stringify(savelist)))
DowningList.push(...savelist);
this.mRefreshListDataShow(true)
if (tip) {
if (savelist.length == 0) {
message.info('下载任务已存在,请勿重复创建任务')
} else {
DBDown.saveDownings(JSON.parse(JSON.stringify(savelist)))
DowningList.push(...savelist)
this.mRefreshListDataShow(true)
message.success('成功创建 ' + savelist.length.toString() + '个下载任务')
}
}
},
@@ -247,10 +248,21 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
*/
mStartDowning() {
const DowningList = this.ListDataRaw
for (const downID of this.ListSelected) {
const selectedDown: IStateDownFile | undefined = DowningList.find(down => down.DownID === downID)
if (selectedDown && !selectedDown.Down.IsDowning && !selectedDown.Down.IsCompleted) {
this.mUpdateDownState(selectedDown, 'queue')
for (const DownID of this.ListSelected) {
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID) {
const down = DowningList[j].Down;
if (down.IsDowning || down.IsCompleted) continue
down.IsStop = false
down.DownState = '队列中'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
break
}
}
}
},
@@ -263,15 +275,23 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
for (let j = 0; j < DowningList.length; j++) {
const down = DowningList[j].Down
if (down.IsDowning || down.IsCompleted) continue
this.mUpdateDownState(DowningList[j], 'queue')
down.IsStop = false
down.DownState = '队列中'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0;
down.FailedMessage = ''
down.AutoTry = 0
}
},
/**
* 暂停下载,只改变状态,待定时任务处理
*/
async mStopDowning() {
mStopDowning() {
const gidList: string[] = []
const downIDList: string[] = []
const downList: Item[] = []
const DowningList = this.ListDataRaw
for (const DownID of this.ListSelected) {
@@ -280,30 +300,54 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
const down = DowningList[j].Down
if (down.IsCompleted) continue
gidList.push(DowningList[j].Info.GID)
downIDList.push(DowningList[j].DownID)
downList.push(DowningList[j])
this.mUpdateDownState(DowningList[j], 'stop')
down.IsDowning = false
down.IsCompleted = false
down.IsStop = true
down.DownState = '已暂停'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
break
}
}
}
await M3u8DownloadDAL.stopDowning(downList, gidList)
AriaStopList(gidList).then(r => {})
// DownDAL.stopDowning(false, downIDList) // TODO
this.mRefreshListDataShow(true)
DBDown.saveDownings(JSON.parse(JSON.stringify(downList)))
},
/**
* 暂停全部
*/
async mStopAllDowning() {
mStopAllDowning() {
const gidList: string[] = []
const downIDList: string[] = []
const DowningList = this.ListDataRaw
for (let j = 0; j < DowningList.length; j++) {
const down = DowningList[j].Down
if (down.IsCompleted) continue
downIDList.push(DowningList[j].DownID)
gidList.push(DowningList[j].Info.GID)
this.mUpdateDownState(DowningList[j], 'stop')
down.IsDowning = false
down.IsStop = true
down.DownState = '已暂停'
down.DownSpeed = 0
down.DownSpeedStr = ''
down.IsFailed = false
down.FailedCode = 0
down.FailedMessage = ''
down.AutoTry = 0
}
await M3u8DownloadDAL.stopDowning(DowningList, gidList)
AriaStopList(gidList).then(r => {})
// DownDAL.stopDowning(false, downIDList) // TODO
this.mRefreshListDataShow(true)
DBDown.saveDownings(JSON.parse(JSON.stringify(DowningList)))
},
/**
@@ -311,46 +355,51 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
* 注:下载服务中的执行列表,请根据状态做进一步处理
* @param downIDList
*/
async mDeleteDowning(downIDList: string[]) {
mDeleteDowning(downIDList: string[]) {
const gidList: string[] = []
const newListSelected = new Set(this.ListSelected)
const newList: Item[] = []
const DowningList: Item[] = this.ListDataRaw
const deleteList: Item[] = []
const DowningList = this.ListDataRaw
const newListSelected = new Set(this.ListSelected);
const newList: Item[] = [];
for (let j = 0; j < DowningList.length; j++) {
const DownID = DowningList[j].DownID
const DownID = DowningList[j].DownID;
if (downIDList.includes(DownID)) {
DowningList[j].Down.DownState = '待删除'
gidList.push(DowningList[j].Info.GID)
deleteList.push(DowningList[j])
if (newListSelected.has(DownID)) {
newListSelected.delete(DownID)
}
DowningList[j].Down.DownState = '待删除'
if (newListSelected.has(DownID)) newListSelected.delete(DownID);
} else {
newList.push(DowningList[j])
newList.push(DowningList[j]);
}
}
this.ListDataRaw = newList
this.ListSelected = newListSelected
await M3u8DownloadDAL.deleteDowning(false, deleteList, gidList)
this.ListDataRaw = newList;
this.ListSelected = newListSelected;
DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList)))
this.mRefreshListDataShow(true)
AriaStopList(gidList).then(r => {})
AriaDeleteList(gidList).then(r => {})
// DownDAL.deleteDowning(false, downIDList) // TODO
},
/**
* 删除全部,修改为“待删除”状态,并从列表中删除 <br/>
* 注:下载服务中的执行列表,请根据状态做进一步处理
*/
async mDeleteAllDowning() {
mDeleteAllDowning() {
const gidList: string[] = []
const DowningList = this.ListDataRaw
this.ListSelected = new Set<string>()
const downIDList: string[] = []
for (let j = 0; j < DowningList.length; j++) {
const DownID = DowningList[j].DownID
DowningList[j].Down.DownState = '待删除'
downIDList.push(DownID)
gidList.push(DowningList[j].Info.GID)
}
await M3u8DownloadDAL.deleteDowning(true, DowningList, gidList)
DowningList.splice(0, DowningList.length)
this.ListSelected = new Set<string>()
DBDown.deleteDowningAll()
this.mRefreshListDataShow(true)
AriaStopList(gidList).then(r => {})
AriaDeleteList(gidList).then(r => {})
// DownDAL.deleteDowning(false, downIDList) // TODO
},
/**
@@ -359,92 +408,54 @@ const useM3u8DownloadingStore = defineStore('m3u8downloading', {
*/
mOrderDowning(downIDList: string[]) {
const DowningList = this.ListDataRaw
const newlist: Item[] = []
const lastlist: Item[] = []
const newlist: Item[] = [];
const lastlist: Item[] = [];
for (let j = 0; j < DowningList.length; j++) {
const DownID = DowningList[j].DownID
let find = false
const DownID = DowningList[j].DownID;
let find = false;
for (let i = 0; i < downIDList.length; i++) {
if (downIDList[i] == DownID) {
newlist.push(DowningList[j])
find = true
break
newlist.push(DowningList[j]);
find = true;
break;
}
}
if (!find) {
lastlist.push(DowningList[j])
lastlist.push(DowningList[j]);
}
}
DowningList.splice(0, DowningList.length, ...newlist, ...lastlist)
DowningList.splice(0, DowningList.length, ...newlist, ...lastlist);
this.mRefreshListDataShow(true)
},
mUpdateDownState(DownItem: IStateDownFile, state: string, msg?: string) {
const { DownID, Down } = DownItem
const updateState: any = {
DownID: DownID,
IsDowning: false,
IsCompleted: false,
DownProcess: 0,
DownSpeedStr: '',
DownState: '',
AutoTry: 0,
IsFailed: false,
IsStop: false,
FailedCode: 0,
FailedMessage: ''
mSaveToDowned(DownID: string) {
const DowningList = this.ListDataRaw
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID && DowningList[j].Down.DownState === '已完成') {
const item = DowningList[j]
DowningList.splice(j, 1)
DBDown.deleteDowning(item.DownID)
item.Down.DownTime = Date.now()
item.DownID = item.Down.DownTime.toString() + '_' + item.DownID
useM3u8DownloadedStore().ListDataRaw.splice(0, 0, item)
useM3u8DownloadedStore().mRefreshListDataShow(true)
DBDown.saveDowned(item.DownID, JSON.parse(JSON.stringify(item)))
break;
}
}
if (this.ListSelected.has(DownID)) this.ListSelected.delete(DownID)
},
mUpdateDownState(data: any) {
const DowningList = this.ListDataRaw
const DownID = data.DownID;
for (let j = 0; j < DowningList.length; j++) {
if (DowningList[j].DownID == DownID) {
DowningList[j].Down = { ...DowningList[j].Down, ...data };
break;
}
switch (state) {
case 'start':
updateState.DownState = '解析中'
updateState.IsDowning = true
updateState.DownTime = Date.now()
break
case 'queue':
updateState.IsDowning = false
updateState.DownState = '队列中'
break
case 'success':
updateState.IsDowning = true
updateState.DownState = '下载中'
break
case 'downed':
updateState.IsDowning = true
updateState.IsCompleted = true
updateState.DownState = '已完成'
updateState.DownProcess = 100
break
case 'valid':
updateState.IsDowning = true
updateState.IsCompleted = true
updateState.DownState = '校验中'
updateState.DownProcess = 100
break
case 'stop':
updateState.IsDowning = false
updateState.DownState = '已暂停'
updateState.DownSpeed = 0
updateState.DownSpeedStr = ''
updateState.IsStop = true
break
case 'error':
updateState.DownState = '已出错'
updateState.DownSpeed = 0
updateState.AutoTry = Date.now()
updateState.IsFailed = true
updateState.FailedMessage = msg || state
break
default:
updateState.DownState = '已出错'
updateState.DownSpeed = 0
updateState.AutoTry = Date.now()
updateState.IsFailed = true
updateState.FailedCode = 504
updateState.FailedMessage = msg || state
break
}
DownItem.Down = { ...DownItem.Down, ...updateState }
}
}

View File

@@ -18,7 +18,6 @@ declare enum TaskState {
}
declare type CheckNameMode =
| 'overwrite' // overwrite (直接覆盖,以后多版本有用)
| 'auto_rename' // auto_rename (自动换一个随机名称)
| 'refuse' // refuse (不会创建,告诉你已经存在)
| 'ignore' // ignore (会创建重名的)

View File

@@ -8,19 +8,14 @@ declare global {
Electron: any
openDatabase: any
WebRelaunchAria: () => Promise<number>
WebRelaunchAlist: () => Promise<number>
WebResetAlistPwd:any
platform: string
WinMsg: any
postdataFunc: any
Prism: any
WebUserToken: any
WebToElectron: any
WebToWindow: any
WebClearCache: any
WebRelaunch: any
WebGetCookies: any
WebSetCookies: any
WebClearCookies: any
WebShutDown: any
WebOpenWindow: any
@@ -32,15 +27,16 @@ declare global {
UploadPort: any
DownloadPort: any
MainPort: any
MainProxyServer: any
MainProxyHost: any
MainProxyPort: any
WinMsgToUpload: any
WinMsgToDownload: any
WinMsgToMain: any
AutoLanuchAtStartup: any
CheckUpdate: any
IsMainPage: boolean
WebSetProxy: any
speedLimte: number
WebSetProgressBar: any
Aria2cDownloadSpeed: any
Aria2cUploadSpeed: any
}
}

View File

@@ -1,67 +1,58 @@
<script lang='ts'>
<script lang="ts">
import { defineComponent } from 'vue'
import { useModalStore } from '../store'
import UserSpaceModal from '../user/UserSpaceModal.vue'
import CreatNewFileModal from '../pan/topbtns/CreatNewFileModal.vue'
import CreatNewAlbumModal from '../pan/topbtns/CreatNewAlbumModal.vue'
import RenameModal from '../pan/topbtns/RenameModal.vue'
import RenameMultiModal from '../pan/topbtns/RenameMultiModal.vue'
import CreatNewDirModal from '../pan/topbtns/CreatNewDirModal.vue'
import DaoRuShareLinkModal from '../share/share/DaoRuShareLinkModal.vue'
import DaoRuShareLinkModal from '../pan/topbtns/DaoRuShareLinkModal.vue'
import EditShareLinkModal from '../share/share/EditShareLinkModal.vue'
import DaoRuShareLinkMultiModal from '../share/share/DaoRuShareLinkMultiModal.vue'
import DaoRuShareLinkMultiModal from '../pan/topbtns/DaoRuShareLinkMultiModal.vue'
import ShowShareLinkModal from '../share/share/ShowShareLinkModal.vue'
import SelectPanDirModal from '../pan/topbtns/SelectPanDirModal.vue'
import CreatNewShareLinkModal from '../share/share/CreatNewShareLinkModal.vue'
import CreatNewShareLinkModal from '../pan/topbtns/CreatNewShareLinkModal.vue'
import ShuXingModal from '../pan/topbtns/ShuXingModal.vue'
import ShuXingMultiModal from '../pan/topbtns/ShuXingMultiModal.vue'
import SearchPanModal from '../pan/topbtns/SearchPanModal.vue'
import DLNAPlayerModal from '../pan/topbtns/DLNAPlayerModal.vue'
import M3U8DownloadModal from '../pan/topbtns/M3U8DownloadModal.vue'
import CopyFileTreeModal from '../pan/topbtns/CopyFileTreeModal.vue'
import ArchiveModal from '../pan/topbtns/ArchiveModal.vue'
import ArchivePasswordModal from '../pan/topbtns/ArchivePasswordModal.vue'
import AlphaModal from '../pan/topbtns/AlphaModal.vue'
import UploadModal from '../pan/topbtns/UploadModal.vue'
import DownloadModal from '../pan/topbtns/DownloadModal.vue'
import SelectSubTitleModal from '../pan/topbtns/SelectSubTitleModal.vue'
import CreatNewAlbumModal from "../pan/topbtns/CreatNewAlbumModal.vue";
import MoveToAlbumModal from '../pan/topbtns/MoveToAlbumModal.vue'
import ShowUpdateLog from '../pan/topbtns/ShowUpdateLog.vue'
import PostModal from '../pan/topbtns/PostModal.vue'
import UserRewardSpace from '../user/UserRewardSpace.vue'
import PasswordModal from '../pan/topbtns/PasswordModal.vue'
import UpdateModal from '../pan/topbtns/ShowUpdateModal.vue'
import ShowUpdateModal from '../pan/topbtns/ShowUpdateModal.vue'
import SelectVideoQualityModal from '../pan/topbtns/SelectVideoQualityModal.vue'
export default defineComponent({
components: {
SelectVideoQualityModal,
ShowUpdateModal,
UpdateModal,
PasswordModal,
UserRewardSpace,
MoveToAlbumModal,
CreatNewAlbumModal,
UserSpaceModal,
CreatNewFileModal,
RenameModal,
RenameMultiModal,
MoveToAlbumModal,
CreatNewFileModal,
CreatNewAlbumModal,
CreatNewDirModal,
CreatNewShareLinkModal,
DaoRuShareLinkModal,
EditShareLinkModal,
DaoRuShareLinkMultiModal,
ShowShareLinkModal,
SelectPanDirModal,
CreatNewShareLinkModal,
ShuXingModal,
ShuXingMultiModal,
SearchPanModal,
DLNAPlayerModal,
M3U8DownloadModal,
AlphaModal,
CopyFileTreeModal,
ArchiveModal,
ArchivePasswordModal,
UploadModal,
DownloadModal,
ShowUpdateLog,
PostModal
DownloadModal
},
setup() {
const modalStore = useModalStore()
@@ -71,41 +62,27 @@ export default defineComponent({
</script>
<template>
<UserSpaceModal :visible="modalStore.modalName == 'userspace'" />
<UserRewardSpace :visible="modalStore.modalName == 'userrewardspace'" :user_id="modalStore.modalData.user_id || ''" />
<CreatNewFileModal :visible="modalStore.modalName == 'creatfile'" :encType="modalStore.modalData.encType || ''" />
<CreatNewAlbumModal :visible="modalStore.modalName == 'creatalbum'" />
<MoveToAlbumModal :visible="modalStore.modalName == 'movetoalbum'" :istree='modalStore.modalData.istree || false' />
<CreatNewDirModal :visible="modalStore.modalName == 'creatdir'"
:dirtype="modalStore.modalData.dirtype || ''"
:encType="modalStore.modalData.encType || ''"
:parentdirid="modalStore.modalData.parentdirid || ''"
:callback='modalStore.modalData.callback' />
<CreatNewShareLinkModal :visible="modalStore.modalName == 'creatshare'"
:sharetype="modalStore.modalData.sharetype || ''"
:driveType="modalStore.modalData.driveType || ''"
:filelist='modalStore.modalData.filelist || []' />
<AlphaModal />
<DaoRuShareLinkModal :visible="modalStore.modalName == 'daorushare'"
:shareUrl="modalStore.modalData.shareUrl || ''"
:sharePwd="modalStore.modalData.sharePwd || ''" />
<UserSpaceModal :visible="modalStore.modalName == 'userspace'" />
<CreatNewFileModal :visible="modalStore.modalName == 'creatfile'" />
<CreatNewAlbumModal :visible="modalStore.modalName == 'createalbum'" />
<MoveToAlbumModal :visible="modalStore.modalName == 'movetoalubm'" :photos_file_id='modalStore.modalData.photos_file_id' :album_id='modalStore.modalData.album_id' />
<CreatNewDirModal :visible="modalStore.modalName == 'creatdir'" :dirtype="modalStore.modalData.dirtype || ''" :parentdirid="modalStore.modalData.parentdirid || ''" :callback="modalStore.modalData.callback" />
<CreatNewShareLinkModal :visible="modalStore.modalName == 'creatshare'" :sharetype="modalStore.modalData.sharetype || ''" :filelist="modalStore.modalData.filelist || []" />
<DaoRuShareLinkModal :visible="modalStore.modalName == 'daorushare'" />
<DaoRuShareLinkMultiModal :visible="modalStore.modalName == 'daorusharemulti'" />
<RenameModal :visible="modalStore.modalName == 'rename'"
:istree='modalStore.modalData.istree || false'
:ispic='modalStore.modalData.ispic || false' />
<RenameMultiModal :visible="modalStore.modalName == 'renamemulti'" :istree='modalStore.modalData.istree || false' />
<ShuXingModal :visible="modalStore.modalName == 'shuxing'"
:istree='modalStore.modalData.istree || false'
:inputselectType="modalStore.modalData.inputselectType || ''"
:ispic='modalStore.modalData.ispic || false' />
<SearchPanModal :visible="modalStore.modalName == 'searchpan'"
:inputsearchType="modalStore.modalData.inputsearchType || []" />
<RenameModal :visible="modalStore.modalName == 'rename'" :istree="modalStore.modalData.istree || false" />
<RenameMultiModal :visible="modalStore.modalName == 'renamemulti'" :istree="modalStore.modalData.istree || false" />
<ShuXingModal :visible="modalStore.modalName == 'shuxing'" :istree="modalStore.modalData.istree || false" />
<ShuXingMultiModal :visible="modalStore.modalName == 'shuxingmulti'" :istree="modalStore.modalData.istree || false" />
<SearchPanModal :visible="modalStore.modalName == 'searchpan'" />
<DLNAPlayerModal :visible="modalStore.modalName == 'dlna'" />
<M3U8DownloadModal :visible="modalStore.modalName == 'm3u8download'" />
<CopyFileTreeModal :visible="modalStore.modalName == 'copyfiletree'"
:filelist='modalStore.modalData.filelist || []' />
<CopyFileTreeModal :visible="modalStore.modalName == 'copyfiletree'" :filelist="modalStore.modalData.filelist || []" />
<ArchiveModal
:visible="modalStore.modalName == 'archive'"
:user_id="modalStore.modalData.user_id || ''"
@@ -125,47 +102,24 @@ export default defineComponent({
:domain_id="modalStore.modalData.domain_id || ''"
:ext="modalStore.modalData.ext || ''" />
<EditShareLinkModal :visible="modalStore.modalName == 'editshare'"
:sharelist='modalStore.modalData.sharelist || []' />
<EditShareLinkModal :visible="modalStore.modalName == 'editshare'" :sharelist="modalStore.modalData.sharelist || []" />
<ShowShareLinkModal
:visible="modalStore.modalName == 'showshare'"
:share_id="modalStore.modalData.share_id || ''"
:share_pwd="modalStore.modalData.share_pwd || ''"
:share_token="modalStore.modalData.share_token || ''"
:withsave='modalStore.modalData.withsave || false'
:save_db='modalStore.modalData.save_db || false'
:file_id_list='modalStore.modalData.file_id_list || []' />
:withsave="modalStore.modalData.withsave || false"
:file_id_list="modalStore.modalData.file_id_list || []" />
<UploadModal :visible="modalStore.modalName == 'upload'"
:file_id="modalStore.modalData.file_id || ''"
:filelist='modalStore.modalData.filelist || []'
:ispic='modalStore.modalData.ispic || false'
:encType="modalStore.modalData.encType || ''" />
<DownloadModal :visible="modalStore.modalName == 'download'" :istree='modalStore.modalData.istree || false' />
<UploadModal :visible="modalStore.modalName == 'upload'" :file_id="modalStore.modalData.file_id || ''" :filelist="modalStore.modalData.filelist || []" />
<DownloadModal :visible="modalStore.modalName == 'download'" :istree="modalStore.modalData.istree || false" />
<SelectPanDirModal :visible="modalStore.modalName == 'selectpandir'"
:selecttype="modalStore.modalData.selecttype || ''"
:selectid="modalStore.modalData.selectid || ''"
:category='modalStore.modalData.category'
:extFilter='modalStore.modalData.extFilter'
:category="modalStore.modalData.category"
:extFilter="modalStore.modalData.extFilter"
:callback='modalStore.modalData.callback' />
<ShowUpdateLog :visible="modalStore.modalName == 'showupdatelog'" />
<ShowUpdateModal :visible="modalStore.modalName == 'showupdate'"
:verData='modalStore.modalData.verData || {}' />
<SelectVideoQualityModal :visible="modalStore.modalName == 'selectvideoquality'"
:file-info="modalStore.modalData.fileInfo || {}"
:quality-data="modalStore.modalData.qualityData || {}"
:callback="modalStore.modalData.callback" />
<PostModal :visible="modalStore.modalName == 'showpostmodal'"
:msg='modalStore.modalData.msg || ""'
:msgid='modalStore.modalData.msgid || ""' />
<PasswordModal :visible="modalStore.modalName == 'showpassword'"
:optType="modalStore.modalData.optType || 'new'"
:callback="modalStore.modalData.callback" />
</template>
<style>
.modalclass .arco-modal-body {

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue'
import {defineComponent, ref, watchEffect} from 'vue'
import { useWinStore, WinState } from '../store'
export default defineComponent({
@@ -13,11 +13,11 @@ export default defineComponent({
emits: ['splitSize'],
setup(props) {
const leftMinWidth = 0
const rightMinWidth = 220
const rightMinWidth = 280
const winStore = useWinStore()
const bodyWidth = ref(Math.max(winStore.width, 900))
const bodyWidth = ref(Math.max(winStore.width, 800))
const splitMoveing = ref(false)
const splitSize = ref(bodyWidth.value < 900 ? '220px' : '240px')
const splitSize = ref(bodyWidth.value < 900 ? '280px' : '320px')
const splitSizeMax = ref(bodyWidth.value - rightMinWidth)
winStore.$subscribe((_m: any, state: WinState) => {
@@ -35,7 +35,7 @@ export default defineComponent({
})
watchEffect(() => {
if(props.visible){
splitSize.value = bodyWidth.value < 900 ? '220px' : '240px'
splitSize.value = bodyWidth.value < 900 ? '280px' : '320px'
}else {
splitSize.value = '0px'
}
@@ -46,9 +46,7 @@ export default defineComponent({
</script>
<template>
<a-split v-model:size="splitSize" class="MySplit" style="height: 100%; width: 100%;"
:min="leftMinWidth" :max="splitSizeMax" tabindex="-1"
@move-start="splitMoveing = true" @move-end="splitMoveing = false">
<a-split v-model:size="splitSize" class="MySplit" style="height: 100%; width: 100%" :min="leftMinWidth" :max="splitSizeMax" tabindex="-1" @move-start="splitMoveing = true" @move-end="splitMoveing = false">
<template #first>
<slot name="first">first</slot>
</template>

View File

@@ -1,59 +1,45 @@
<script setup lang='ts'>
import { PropType } from 'vue'
<script lang="ts">
import { defineComponent } from 'vue'
const props = defineProps({
value: { type: Boolean },
disabled: { type: Boolean },
beforeChange: {
type: Function as PropType<(newValue: string | number | boolean) => Promise<boolean | void> | boolean | void>
}
export default defineComponent({
props: { value: Boolean },
emits: ['update:value'],
setup() {}
})
const emits = defineEmits(['update:value', 'change'])
</script>
<template>
<div class='myswitch'>
<a-switch type="round" :model-value='value' tabindex='-1'
:before-change="beforeChange"
@change="$emit('change', $event)"
:disabled="disabled"
@update:model-value="$emit('update:value', $event)">
<div class="myswitch">
<a-switch type="round" :model-value="value" tabindex="-1" @update:model-value="$emit('update:value', $event)">
<template #checked></template>
<template #unchecked></template>
</a-switch>
<span class='myswitchspan' @click="$emit('update:value', !value)">
<slot></slot>
</span>
<span class="myswitchspan" @click="$emit('update:value', !value)"><slot></slot></span>
</div>
</template>
<style>
.myswitch {
height: 30px;
display: flex;
align-items: center;
user-select: none;
display: inline-flex;
color: var(--color-text-2);
}
.myswitch .arco-switch {
min-width: 36px;
height: 18px;
line-height: 18px;
}
.myswitch .arco-switch-handle {
width: 12px;
height: 12px;
top: 3px;
}
.myswitch .arco-switch-checked .arco-switch-handle {
left: calc(100% - 16px);
}
.myswitch .arco-icon-loading, .arco-icon-spin {
width: 12px;
height: 12px;
color: rgb(var(--primary-6));
}
.myswitch .arco-switch-text-holder {
margin: 0 4px 0 22px;
}
@@ -65,7 +51,6 @@ const emits = defineEmits(['update:value', 'change'])
.myswitch .arco-switch-text {
left: 22px;
}
.myswitch .arco-switch-checked .arco-switch-text {
left: 6px;
}

View File

@@ -52,13 +52,13 @@ export default defineComponent({
user-select: none;
}
.mytags .arco-input-search {
height: 30px;
height: 33px;
margin-right: 8px;
margin-bottom: 4px;
}
.mytags .arco-input-search .arco-input-wrapper {
padding-left: 4px;
padding-right: 4px;
padding-left: 30px;
padding-right: 30px;
font-size: 12px;
}
.mytags .arco-input-search .arco-input-wrapper .arco-input {
@@ -70,8 +70,8 @@ export default defineComponent({
font-size: 12px;
}
.mytags .arco-input-search .arco-input-append .arco-input-search-btn {
height: 28px;
padding: 0 5px;
font-size: 12px;
height: 30px;
padding: 0 15px;
font-size: 15px;
}
</style>

View File

@@ -1,679 +0,0 @@
<template>
<a-layout draggable="false">
<a-layout-header id="xbyhead" draggable="false">
<div id="xbyhead2" class="q-electron-drag">
<div class="flexauto"></div>
<a-button type="text" tabindex="-1" title="最小化" @click="handleMinClick">
<i class="iconfont iconzuixiaohua"></i>
</a-button>
<a-button type="text" tabindex="-1" title="最大化" @click="handleMaxClick">
<i class="iconfont iconfullscreen"></i>
</a-button>
<a-button type="text" tabindex="-1" @click="closeByHide()">
<i class="iconfont iconclose"></i>
</a-button>
</div>
</a-layout-header>
<div
class="w-full h-screen bg-gray-100 overflow-hidden"
id="demo_wrap"
>
<div
class="flex flex-row justify-center w-full m-auto relative z-10"
style="height: 90%;"
>
<div class="flex1 flex-col w-full md:w-5/12 bg-gray-100 rounded-lg mt-8">
<div class="m-auto mt-4 mb-0">
<div
v-for="(audio, indexo) in audios.slice(index, index + 1)"
:key="indexo"
class="mb-4"
>
<h3 class="text-xl text-grey-darkest font-semibold">
{{ audio.name }}
</h3>
</div>
<div class="mt-15 m-auto relative" style="width: 400px">
<img
class="mt-20 rounded-3xl block shadow-lg"
src="/public/images/music_bg.jpg"
alt="Album Pic"
/>
</div>
</div>
<div class="flex w-3/5 m-auto justify-between items-center ">
<div class="text-grey-darker hover:bg-gray-300 rounded-full p-1">
<svg
@click="random = !random"
:class="random ? 'text-blue-500' : ''"
class="w-8 h-8 cursor-pointer"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path
d="M6.59 12.83L4.4 15c-.58.58-1.59 1-2.4 1H0v-2h2c.29 0 .8-.2 1-.41l2.17-2.18 1.42 1.42zM16 4V1l4 4-4 4V6h-2c-.29 0-.8.2-1 .41l-2.17 2.18L9.4 7.17 11.6 5c.58-.58 1.59-1 2.41-1h2zm0 10v-3l4 4-4 4v-3h-2c-.82 0-1.83-.42-2.41-1l-8.6-8.59C2.8 6.21 2.3 6 2 6H0V4h2c.82 0 1.83.42 2.41 1l8.6 8.59c.2.2.7.41.99.41h2z"
/>
</svg>
</div>
<div class="text-grey-darker hover:bg-gray-300 rounded-full p-1">
<svg
@click="prevButton ? previous() : ''"
class="w-8 h-8 cursor-pointer"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M4 5h3v10H4V5zm12 0v10l-9-5 9-5z" />
</svg>
</div>
<div
class="text-white p-4 rounded-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 shadow-lg"
>
<svg
v-if="!pauseTrack"
@click="play()"
class="w-8 h-8 cursor-pointer"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<svg
v-else
@click="pause()"
class="w-8 h-8 cursor-pointer"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M5 4h3v12H5V4zm7 0h3v12h-3V4z" />
</svg>
</div>
<div class="text-grey-darker hover:bg-gray-300 rounded-full p-1">
<svg
@click="nextButton ? next() : ''"
class="w-8 h-8 cursor-pointer"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M13 5h3v10h-3V5zM4 5l9 5-9 5V5z" />
</svg>
</div>
<div class="text-grey-darker hover:bg-gray-300 rounded-full p-1">
<svg
@click="repeat = !repeat"
:class="repeat ? 'text-blue-500' : ''"
class="w-8 h-8 cursor-pointer"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path
d="M5 4a2 2 0 0 0-2 2v6H0l4 4 4-4H5V6h7l2-2H5zm10 4h-3l4-4 4 4h-3v6a2 2 0 0 1-2 2H6l2-2h7V8z"
/>
</svg>
</div>
</div>
</div>
<div class="w-7/12 hidden md:block mt-1 ml-2">
<ul
class="w-full overflow-auto m-auto mb-2 bg-gray-100 pt-2"
style="max-height: 100%"
id="journal-scroll"
>
<li
@click="selectSound(indexo)"
:style="indexo == index ? '' : ''"
:class="
indexo == index
? 'bg-gradient-to-r from-blue-500 via-blue-600 to-blue-700 text-white'
: ''
"
class="flex py-1 rounded cursor-pointer w-11/12 m-auto"
v-for="(audio, indexo) in audios"
:key="indexo"
>
<div class="font-semibold m-auto">
{{ indexo + 1 }}
</div>
<div class="w-3/5 font-semibold text-left m-auto">
<div class="font-semibold text-sm">
<p>{{ audio.name }}</p>
<p class="text-xs text-gray-500">{{ audio.artist }}</p>
</div>
</div>
<div class="w-1/5 m-auto">
<svg
v-if="state.audioPlaying[indexo]"
class="w-6 h-6 m-auto"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M5 4h3v12H5V4zm7 0h3v12h-3V4z" />
</svg>
<svg
v-else
class="w-6 h-6 m-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
</li>
</ul>
</div>
</div>
<div
class="w-full bg-gradient-to-r from-gray-300 to-gray-400 relative z-10"
style="height: 30%; bottom: 60px;"
>
<div class="flex1 w-11/12 h-20 items-center justify-around ml-10">
<div
class="w-2/12 md:flex items-center hidden"
v-for="(audio, indexo) in audios.slice(index, index + 1)"
:key="indexo"
>
<div class="flex flex-col mr-2 font-semibold">
<p>{{ audio.name }}</p>
</div>
</div>
<div class="flex md:w-8/12 items-center">
<div class="text-sm text-grey-darker mr-4 font-semibold">
<p>{{ timer }}</p>
</div>
<div class="mt-1 relative w-8/12 md:w-10/12">
<div
@click="seek($event)"
ref="progress"
class="h-1 bg-grey-dark cursor-pointer rounded-full bg-gray-500"
>
<div
class="flex w-full justify-end h-1 bg-gradient-to-r from-blue-500 to-blue-700 rounded-full relative"
:style="{ width: step + '%' }"
></div>
</div>
<div
class="flex w-full justify-end h-1 rounded-full relative"
:style="{ width: step + '%' }"
>
<span
id="progressButtonTimer"
class="w-3 h-3 md:w-4 md:h-4 bg-gradient-to-r from-blue-500 to-blue-700 absolute pin-r pin-b -mb-1 rounded-full shadow"
></span>
</div>
</div>
<div class="text-sm text-grey-darker w-2/12 md:w-1/12 font-semibold ml-4">
<p>{{ duration }}</p>
</div>
</div>
<div class="ml-4 w-1/5 flex m-auto items-center">
<div
class="w-3/12 hover:bg-gray-500 rounded-full"
@click="mute()"
>
<svg
v-if="mutePlayer"
class="w-6 h-6 m-auto text-blue-500 cursor-pointer ml-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 30 30"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
clip-rule="evenodd"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2"
/>
</svg>
<svg
v-else
class="w-6 h-6 m-auto cursor-pointer ml-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
/>
</svg>
</div>
<div class="w-9/12 md:w-10/12 m-auto relative" style='left: -40px'>
<div
@click="volume($event)"
ref="volBar"
class="h-1 bg-grey-dark cursor-pointer rounded-full bg-gray-500 m-auto relative"
style="width: 100%"
>
<div
class="flex justify-end h-1 bg-gradient-to-r from-teal-400 to-blue-500 rounded-full relative"
:style="{ width: volumeProgress + '%' }"
></div>
</div>
<div
class="flex justify-end h-1 rounded-full relative"
:style="{ width: volumeProgress + '%' }"
>
<span
id="progressButtonVolume"
class="w-3 h-3 md:w-4 md:h-4 bg-gradient-to-r from-teal-400 to-blue-500 absolute pin-r pin-b -mb-1 rounded-full shadow"
></span>
</div>
</div>
</div>
</div>
</div>
</div>
</a-layout>
</template>
<script>
import { ref, reactive } from "vue"
import { Howl, Howler } from "howler"
import { useAppStore } from '../store'
import AliFile from '../aliapi/file'
export default {
mounted() {
this.play()
var barWidth = (0.9 * 100) / 100
this.sliderBtnVol =
this.volBar.offsetWidth * barWidth + this.volBar.offsetWidth * 0.05 - 25
},
beforeUnmount() {
this.audios.forEach(audio => {
if (audio.howl) {
audio.howl.stop();
audio.howl.unload();
}
});
},
setup() {
const appStore = useAppStore()
const getAudioUrl = async (index) => {
if (appStore.pageAudio) {
const driveId = appStore.pageAudio.drive_id
const fileId = appStore.pageAudio.fileIdlist[index]
const data = await AliFile.ApiAudioPreviewUrl(appStore.pageAudio.user_id, appStore.pageAudio?.drive_id, fileId)
return data?.url
}
return ""
}
const audios = ref(appStore.pageAudio.fileIdlist.map((fileId, index) => {
return {
name: appStore.pageAudio.fileNamelist[index],
artist: '',
file: '',
howl: null,
}
}))
const step = ref(0)
const nextButton = ref(true)
const prevButton = ref(true)
const random = ref(false)
const repeat = ref(false)
const index = ref(0)
const duration = ref("00:00")
const timer = ref("00:00")
const pauseTrack = ref(false)
const progress = ref(null)
const volBar = ref(null)
const sliderBtn = ref(0)
const sliderBtnVol = ref(null)
const volumeProgress = ref(90)
const mutePlayer = ref(false)
const state = reactive({
audioPlaying: [],
})
function formatTime(secs) {
var minutes = Math.floor(secs / 60) || 0
var seconds = Math.floor(secs - minutes * 60) || 0
return (
(minutes < 10 ? "0" : "") +
minutes +
":" +
(seconds < 10 ? "0" : "") +
seconds
)
}
async function play() {
var sound
var audio = audios.value[index.value]
if (audio.howl) {
sound = audio.howl
} else {
state.audioPlaying[index.value] = false
sound = audio.howl = new Howl({
src: [await getAudioUrl(index.value)],
html5: true, // A live stream can only be played through HTML5 Audio.
// format: ["mp3", "aac", "wma", "ape", "aac", "cda", "dsf", "dtshd", "eac3", "m1a", "m2a", "m4a", "mka", "mpa", "mpc", "opus", "ra","tak","tta", "wv"],
onplay: function() {
pauseTrack.value = true
nextButton.value = true
prevButton.value = true
duration.value = formatTime(sound.duration())
requestAnimationFrame(stepFunction.bind(this))
},
onpause: function() {
pauseTrack.value = false
},
onend: function() {
next()
},
onseek: function() {
window.requestAnimationFrame(stepFunction.bind(this))
},
})
}
sound.play()
state.audioPlaying[index.value] = true
}
function pause(indexo) {
var audio = audios.value[index.value].howl
if (audio) {
audio.pause()
pauseTrack.value = false
state.audioPlaying[index.value] = false
}
}
function stepFunction() {
var sound = audios.value[index.value].howl
var seek = 0
var duration = 0
if (sound) {
seek = sound.seek()
duration = sound.duration()
}
timer.value = formatTime(Math.round(seek))
if (duration !== 0) {
step.value = (seek * 100) / duration
} else {
step.value = 0
}
sliderBtn.value =
progress.value.offsetWidth * (step.value / 100) +
progress.value.offsetWidth * 0.05 -
25
if (sound && sound.playing()) {
window.requestAnimationFrame(stepFunction.bind(this))
}
}
function seek(event) {
var per = event.offsetX / progress.value.clientWidth
var sound = audios.value[index.value].howl
if (sound) {
if (sound.playing()) {
sound.pause()
sound.seek(sound.duration() * per)
var barWidth = (per * 100) / 100
sliderBtn.value =
progress.value.offsetWidth * barWidth +
progress.value.offsetWidth * 0.05 -
25
sound.play()
} else {
sound.seek(sound.duration() * per)
var barWidth = (per * 100) / 100
sliderBtn.value =
progress.value.offsetWidth * barWidth +
progress.value.offsetWidth * 0.05 -
25
}
}
}
function next() {
nextButton.value = false
var audio = audios.value[index.value].howl
state.audioPlaying[index.value] = false
mutePlayer.value ? (mutePlayer.value = false) : ""
audio && audio.mute(true) ? audio.mute(false) : ""
if (audio && audios.value.length - 1 == index.value) {
audio.stop()
audio.unload()
repeat.value
? (index.value = index.value)
: random.value
? (index.value = Math.floor(Math.random() * audios.value.length))
: (index.value = 0)
} else {
if (audio) {
audio.stop()
audio.unload()
}
repeat.value
? (index.value = index.value)
: random.value
? (index.value = Math.floor(Math.random() * audios.value.length))
: index.value++
}
play()
}
function previous() {
var audio = audios.value[index.value].howl
prevButton.value = false
state.audioPlaying[index.value] = false
mutePlayer.value ? (mutePlayer.value = false) : ""
audio && audio.mute(true) ? audio.mute(false) : ""
if (!audio) {
index.value = audios.value.length - 1
} else if (audio && index.value == 0) {
audio.stop()
audio.unload()
repeat.value
? (index.value = index.value)
: random.value
? (index.value = Math.floor(Math.random() * audios.value.length))
: (index.value = audios.value.length - 1)
} else if (audio) {
audio.stop()
repeat.value
? (index.value = index.value)
: random.value
? (index.value = Math.floor(Math.random() * audios.value.length))
: index.value--
}
play()
}
function selectSound(indexSelected) {
var audio = audios.value[index.value].howl
if (audio) {
audio.stop()
audio.unload()
state.audioPlaying[index.value] = false
}
index.value = indexSelected
play()
}
function volume(event) {
var per = event.layerX / parseFloat(volBar.value.scrollWidth)
var barWidth = (per * 100) / 100
volumeProgress.value = barWidth * 100
sliderBtnVol.value =
volBar.value.offsetWidth * barWidth +
volBar.value.offsetWidth * 0.05 -
25
Howler.volume(per)
}
function mute() {
var audio = audios.value[index.value].howl
if (audio) {
mutePlayer.value = !mutePlayer.value
mutePlayer.value ? audio.mute(true) : audio.mute(false)
}
}
function closeByHide() {
window.close()
}
function handleMinClick() {
if (window.WebToElectron) window.WebToElectron({ cmd: 'minsizeAudio' })
}
function handleMaxClick() {
if (window.WebToElectron) window.WebToElectron({ cmd: 'maxsizeAudio' })
}
return {
play,
pause,
duration,
formatTime,
audios,
pauseTrack,
next,
previous,
index,
timer,
step,
stepFunction,
seek,
selectSound,
state,
random,
repeat,
progress,
volume,
volBar,
volumeProgress,
sliderBtn,
mute,
mutePlayer,
sliderBtnVol,
nextButton,
prevButton,
handleMinClick,
handleMaxClick,
closeByHide
}
},
}
</script>
<style>
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
}
#journal-scroll::-webkit-scrollbar {
width: 0.9rem;
cursor: pointer;
}
#journal-scroll::-webkit-scrollbar-track {
background-color: #ffffff;
cursor: pointer;
}
#journal-scroll::-webkit-scrollbar-thumb {
cursor: pointer;
border-radius: 10px;
background-color: #4096de;
border: 3px solid #ffffff;
}
.cd-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #f7fafc;
}
#progressButtonTimer,
#progressButtonVolume {
margin-top: -9px;
right: -8px;
}
@media screen and (max-width: 768px) {
#progressButtonTimer,
#progressButtonVolume {
margin-top: -8px;
right: -7px;
}
}
.flex1 {
display: flex;
flex: 100% 1 1;
}
</style>

View File

@@ -1,61 +1,25 @@
<script setup lang="ts">
<script lang="ts">
import AliFile from '../aliapi/file'
import { KeyboardState, useAppStore, useKeyboardStore } from '../store'
import { useAppStore } from '../store'
import message from '../utils/message'
import { onMounted, ref } from 'vue'
import { TestAlt, TestKey } from '../utils/keyboardhelper'
import { defineComponent, onMounted, ref } from 'vue'
const keyboardStore = useKeyboardStore()
keyboardStore.$subscribe((_m: any, state: KeyboardState) => {
if (TestAlt('f4', state.KeyDownEvent, handleHideClick)) return
if (TestAlt('m', state.KeyDownEvent, handleMinClick)) return
if (TestAlt('enter', state.KeyDownEvent, handleMaxClick)) return
if (TestKey('f11', state.KeyDownEvent, handleMaxClick)) return
})
const onKeyDown = (event: KeyboardEvent) => {
const ele = (event.srcElement || event.target) as any
const nodeName = ele && ele.nodeName
if (document.body.getElementsByClassName('arco-modal-container').length) return
if (event.key == 'Control' || event.key == 'Shift' || event.key == 'Alt' || event.key == 'Meta') return
const isInput = nodeName == 'INPUT' || nodeName == 'TEXTAREA' || false
if (!isInput) {
keyboardStore.KeyDown(event)
export default defineComponent({
setup() {
const handleHideClick = (_e: any) => {
window.close()
}
}
const appStore = useAppStore()
const appStore = useAppStore()
const codeBlock = ref()
const lang = ref('language-' + appStore.pageCode?.code_ext || '')
const codeString = ref('')
const format = ref(false)
const codeBlock = ref()
onMounted(() => {
window.addEventListener('keydown', onKeyDown, true)
const name = appStore.pageCode?.file_name || '文档在线预览'
setTimeout(() => {
document.title = name
}, 1000)
setTimeout(() => {
document.title = name
}, 10000)
const lang = ref('language-' + appStore.pageCode?.code_ext || '')
const codeString = ref('')
const format = ref(false)
loadCode()
})
const handleHideClick = (_e: any) => {
if (window.WebToWindow) window.WebToWindow({ cmd: 'close' })
}
const handleMinClick = (_e: any) => {
if (window.WebToWindow) window.WebToWindow({ cmd: 'minsize' })
}
const handleMaxClick = (_e: any) => {
if (window.WebToWindow) window.WebToWindow({ cmd: 'maxsize' })
}
const loadCode = () => {
const loadCode = () => {
const pageCode = appStore.pageCode!
AliFile.ApiFileDownText(pageCode.user_id, pageCode.drive_id, pageCode.file_id, pageCode.file_size, 512 * 1024, pageCode.encType, pageCode.password).then((data: any) => {
AliFile.ApiFileDownText(pageCode.user_id, pageCode.drive_id, pageCode.file_id, pageCode.file_size, 512 * 1024).then((data: string) => {
if (pageCode.file_size > 512 * 1024) {
message.info('文件较大,只显示了前 512KB 的内容')
}
@@ -78,11 +42,25 @@ const loadCode = () => {
setTimeout(() => {
try {
if (codeBlock.value) window.Prism.highlightAllUnder(codeBlock.value)
} catch {
}
} catch {}
}, 500)
})
}
}
onMounted(() => {
const name = appStore.pageCode?.file_name || '文档在线预览'
setTimeout(() => {
document.title = name
}, 1000)
setTimeout(() => {
document.title = name
}, 10000)
loadCode()
})
return { handleHideClick, appStore, codeBlock, format, lang, codeString }
}
})
</script>
<template>
@@ -96,14 +74,8 @@ const loadCode = () => {
<div class="title">{{ appStore.pageCode?.file_name || '文档在线预览' }}</div>
<div class="flexauto"></div>
<a-button type='text' tabindex='-1' title='最小化 Alt+M' @click='handleMinClick'>
<i class='iconfont iconzuixiaohua'></i>
</a-button>
<a-button type='text' tabindex='-1' title='最大化 Alt+Enter' @click='handleMaxClick'>
<i class='iconfont iconfullscreen'></i>
</a-button>
<a-button type='text' tabindex='-1' title='关闭 Alt+F4' @click='handleHideClick'>
<i class='iconfont iconclose'></i>
<a-button type="text" tabindex="-1" title="关闭 Alt+F4" @click="handleHideClick">
<i class="iconfont iconclose"></i>
</a-button>
</div>
</a-layout-header>
@@ -111,7 +83,7 @@ const loadCode = () => {
<div id="doc-preview" class="doc-preview" style="width: 100%; height: 100%; overflow: auto">
<div ref="codeBlock" class="fullwidthcode">
<pre v-if="format" :class="'line-numbers ' + lang + ' format'">
<code>{{ codeString }}</code>
<code>{{codeString}}</code>
</pre>
<p v-else class="noformat">{{ codeString }}</p>
</div>
@@ -135,7 +107,6 @@ const loadCode = () => {
white-space: pre-wrap;
min-width: 100%;
}
.fullwidthcode .noformat {
font-size: 14px;
color: rgb(217, 217, 217);
@@ -153,7 +124,6 @@ const loadCode = () => {
.fullwidthcode pre:focus {
outline: none;
}
.fullwidthcode pre * {
user-select: text;
-webkit-user-drag: auto;

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { CreatMap, CreatObject, LoadObject } from '../utils/levemap'
export default defineComponent({
setup() {
const handleHideClick = (_e: any) => {
window.close()
}
const handleLoadObject = () => {
LoadObject()
}
const handleCreatMap = () => {
CreatMap()
}
const handleCreatObject = () => {
CreatObject()
}
onMounted(() => {
setTimeout(() => {
document.title = '小白羊帮助文档'
}, 1000)
setTimeout(() => {
document.title = '小白羊帮助文档'
}, 10000)
})
return { handleHideClick, handleCreatObject, handleCreatMap, handleLoadObject }
}
})
</script>
<template>
<a-layout style="height: 100vh; background: #f2f4f7" draggable="false">
<a-layout-header id="xbyhead" draggable="false">
<div id="xbyhead2" class="q-electron-drag">
<div class="title">小白羊云盘</div>
<div class="flexauto"></div>
<a-button type="text" tabindex="-1" title="关闭 Alt+F4" @click="handleLoadObject"> LoadObject </a-button>
<a-button type="text" tabindex="-1" title="关闭 Alt+F4" @click="handleCreatMap"> CreatMap </a-button>
<a-button type="text" tabindex="-1" title="关闭 Alt+F4" @click="handleCreatObject"> CreatObject </a-button>
<a-button type="text" tabindex="-1" title="关闭 Alt+F4" @click="handleHideClick">
<i class="iconfont iconclose"></i>
</a-button>
</div>
</a-layout-header>
<a-layout-content style="height: calc(100vh - 42px); padding-top: 8px; background: #f2f4f7">
<div id="doc-preview" class="doc-preview" style="width: 100%; height: 100%">
</div>
</a-layout-content>
</a-layout>
</template>
<style></style>

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