mirror of
https://github.com/qjfoidnh/BaiduPCS-Go.git
synced 2026-06-04 09:54:22 +08:00
Update README.md
This commit is contained in:
14
BaiduPCS-Go.exe.manifest
Normal file
14
BaiduPCS-Go.exe.manifest
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="com.iikira.baidupcsgo" type="win32"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAware>true</dpiAware>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
||||
22
Info.plist
Normal file
22
Info.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>BaiduPCS-Go</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>BaiduPCS-Go</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.iikira.baidupcsgo</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>BaiduPCS-Go</string>
|
||||
</dict>
|
||||
</plist>
|
||||
202
LICENSE
Normal file
202
LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright iikira.
|
||||
|
||||
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.
|
||||
|
||||
954
README.md
Normal file
954
README.md
Normal file
@@ -0,0 +1,954 @@
|
||||
# BaiduPCS-Go-Transfer 百度网盘客户端
|
||||
|
||||
|
||||
仿 Linux shell 文件处理命令的百度网盘命令行客户端.
|
||||
|
||||
This project was largely inspired by [GangZhuo/BaiduPCS](https://github.com/GangZhuo/BaiduPCS) and iikira/BaiduPCS-Go
|
||||
|
||||
## 注意
|
||||
|
||||
此版本基于iikira原版BaiduPCS-Go最新版本(v3.6.2)修改, 添加了转存功能和部分配置建议, 替换了文档中一些失效的链接和页面并更新少量说明.
|
||||
|
||||
<!-- toc -->
|
||||
## 目录
|
||||
|
||||
- [特色](#特色)
|
||||
- [编译/交叉编译 说明](#编译交叉编译-说明)
|
||||
- [下载/运行 说明](#下载运行-说明)
|
||||
* [Windows](#windows)
|
||||
* [Linux / macOS](#linux--macos)
|
||||
* [Android / iOS](#android--ios)
|
||||
- [命令列表及说明](#命令列表及说明)
|
||||
* [注意 ! ! !](#注意---)
|
||||
* [检测程序更新](#检测程序更新)
|
||||
* [登录百度帐号](#登录百度帐号)
|
||||
* [列出帐号列表](#列出帐号列表)
|
||||
* [获取当前帐号](#获取当前帐号)
|
||||
* [切换百度帐号](#切换百度帐号)
|
||||
* [退出百度帐号](#退出百度帐号)
|
||||
* [获取网盘配额](#获取网盘配额)
|
||||
* [切换工作目录](#切换工作目录)
|
||||
* [输出工作目录](#输出工作目录)
|
||||
* [列出目录](#列出目录)
|
||||
* [列出目录树形图](#列出目录树形图)
|
||||
* [获取文件/目录的元信息](#获取文件目录的元信息)
|
||||
* [搜索文件](#搜索文件)
|
||||
* [下载文件/目录](#下载文件目录)
|
||||
* [上传文件/目录](#上传文件目录)
|
||||
* [获取下载直链](#获取下载直链)
|
||||
* [手动秒传文件](#手动秒传文件)
|
||||
* [修复文件MD5](#修复文件MD5)
|
||||
* [获取本地文件的秒传信息](#获取本地文件的秒传信息)
|
||||
* [导出文件/目录](#导出文件目录)
|
||||
* [创建目录](#创建目录)
|
||||
* [删除文件/目录](#删除文件目录)
|
||||
* [拷贝文件/目录](#拷贝文件目录)
|
||||
* [移动/重命名文件/目录](#移动重命名文件目录)
|
||||
* [转存文件/目录](#转存文件目录)
|
||||
* [分享文件/目录](#分享文件目录)
|
||||
+ [设置分享文件/目录](#设置分享文件目录)
|
||||
+ [列出已分享文件/目录](#列出已分享文件目录)
|
||||
+ [取消分享文件/目录](#取消分享文件目录)
|
||||
* [离线下载](#离线下载)
|
||||
+ [添加离线下载任务](#添加离线下载任务)
|
||||
+ [精确查询离线下载任务](#精确查询离线下载任务)
|
||||
+ [查询离线下载任务列表](#查询离线下载任务列表)
|
||||
+ [取消离线下载任务](#取消离线下载任务)
|
||||
+ [删除离线下载任务](#删除离线下载任务)
|
||||
* [回收站](#回收站)
|
||||
+ [列出回收站文件列表](#列出回收站文件列表)
|
||||
+ [还原回收站文件或目录](#还原回收站文件或目录)
|
||||
+ [删除回收站文件或目录/清空回收站](#删除回收站文件或目录清空回收站)
|
||||
* [显示和修改程序配置项](#显示和修改程序配置项)
|
||||
* [测试通配符](#测试通配符)
|
||||
* [工具箱](#工具箱)
|
||||
- [初级使用教程](#初级使用教程)
|
||||
* [1. 查看程序使用说明](#1-查看程序使用说明)
|
||||
* [2. 登录百度帐号 (必做)](#2-登录百度帐号-必做)
|
||||
* [3. 切换网盘工作目录](#3-切换网盘工作目录)
|
||||
* [4. 网盘内列出文件和目录](#4-网盘内列出文件和目录)
|
||||
* [5. 下载文件](#5-下载文件)
|
||||
* [6. 设置下载最大并发量](#6-设置下载最大并发量)
|
||||
* [7. 退出程序](#7-退出程序)
|
||||
- [已知问题](#已知问题)
|
||||
- [TODO](#todo)
|
||||
- [交流反馈](#交流反馈)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
# 特色
|
||||
|
||||
多平台支持, 支持 Windows, macOS, linux, 移动设备等.
|
||||
|
||||
百度帐号多用户支持;
|
||||
|
||||
通配符匹配网盘路径和 Tab 自动补齐命令和路径, [通配符_百度百科](https://baike.baidu.com/item/通配符);
|
||||
|
||||
[下载](#下载文件目录)网盘内文件, 支持多个文件或目录下载, 支持断点续传和单文件并行下载;
|
||||
|
||||
[上传](#上传文件目录)本地文件, 支持上传大文件(>2GB), 支持多个文件或目录上传;
|
||||
|
||||
[转存](#转存文件目录)其他用户分享的文件, 支持公开、带密码的分享链接, 支持常见的几种秒传链接;
|
||||
|
||||
[离线下载](#离线下载), 支持http/https/ftp/电驴/磁力链协议.
|
||||
|
||||
# 编译/交叉编译 说明
|
||||
设置好 GOOS 和 GOARCH 环境变量,
|
||||
|
||||
运行 go tool dist list 查看所有支持的 GOOS/GOARCH
|
||||
|
||||
## Linux/Darwin 例子: 编译 Windows 下的 64 位程序
|
||||
```
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build
|
||||
```
|
||||
## Windows 例子: 编译 Linux 下的 32 位程序
|
||||
```
|
||||
set GOOS=linux
|
||||
set GOARCH=386
|
||||
set CGO_ENABLED=0
|
||||
go build
|
||||
```
|
||||
|
||||
# 下载/运行 说明
|
||||
|
||||
Go语言程序, 常用几种平台的已编译程序可直接在[蓝奏云](https://ww.lanzous.com/b01berebe)下载使用. 密码:4pix
|
||||
|
||||
如果程序运行时输出乱码, 请检查下终端的编码方式是否为 `UTF-8`.
|
||||
|
||||
使用本程序之前, 建议学习一些 linux 基础知识 和 基础命令.
|
||||
|
||||
如果未带任何参数运行程序, 程序将会进入仿Linux shell系统用户界面的cli交互模式, 可直接运行相关命令.
|
||||
|
||||
cli交互模式下, 光标所在行的前缀应为 `BaiduPCS-Go >`, 如果登录了百度帐号则格式为 `BaiduPCS-Go:<工作目录> <百度ID>$ `
|
||||
|
||||
程序会提供相关命令的使用说明.
|
||||
|
||||
## Windows
|
||||
|
||||
程序应在 命令提示符 (Command Prompt) 或 PowerShell 中运行, 在 mintty (例如: GitBash) 可能会有显示问题.
|
||||
|
||||
也可直接双击程序运行, 具体使用方法请参见 [命令列表及说明](#命令列表及说明) 和 [初级使用教程](#初级使用教程).
|
||||
|
||||
## Linux / macOS
|
||||
|
||||
程序应在 终端 (Terminal) 运行.
|
||||
|
||||
具体使用方法请参见 [命令列表及说明](#命令列表及说明) 和 [初级使用教程](#初级使用教程).
|
||||
|
||||
## Android / iOS
|
||||
|
||||
> Android / iOS 移动设备操作比较麻烦, 不建议在移动设备上使用本程序.
|
||||
|
||||
安卓, 建议使用 [Termux](https://termux.com) 或 [NeoTerm](https://github.com/NeoTerm/NeoTerm) 或 终端模拟器, 以提供终端环境.
|
||||
|
||||
示例: [Android 运行本项目程序参考示例](https://web.archive.org/web/20190820154934/https://github.com/iikira/BaiduPCS-Go/wiki/Android-%E8%BF%90%E8%A1%8C%E6%9C%AC%E9%A1%B9%E7%9B%AE%E7%A8%8B%E5%BA%8F%E5%8F%82%E8%80%83%E7%A4%BA%E4%BE%8B), 有兴趣的可以参考一下.
|
||||
|
||||
苹果iOS, 需要越狱, 在 Cydia 搜索下载并安装 MobileTerminal, 或者其他提供终端环境的软件.
|
||||
|
||||
示例: [iOS 运行本项目程序参考示例](https://web.archive.org/web/20190820155025/https://github.com/iikira/BaiduPCS-Go/wiki/iOS-%E8%BF%90%E8%A1%8C%E6%9C%AC%E9%A1%B9%E7%9B%AE%E7%A8%8B%E5%BA%8F%E5%8F%82%E8%80%83%E7%A4%BA%E4%BE%8B), 有兴趣的可以参考一下.
|
||||
|
||||
具体使用方法请参见 [命令列表及说明](#命令列表及说明) 和 [初级使用教程](#初级使用教程).
|
||||
|
||||
# 命令列表及说明
|
||||
|
||||
## 注意 ! ! !
|
||||
|
||||
命令的前缀 `BaiduPCS-Go` 为指向程序运行的全路径名 (ARGv 的第一个参数)
|
||||
|
||||
直接运行程序时, 未带任何其他参数, 则程序进入cli交互模式, 运行以下命令时, 要把命令的前缀 `BaiduPCS-Go` 去掉!
|
||||
|
||||
cli交互模式已支持按tab键自动补全命令和路径.
|
||||
|
||||
## 检测程序更新
|
||||
```
|
||||
BaiduPCS-Go update
|
||||
```
|
||||
|
||||
## 登录百度帐号
|
||||
|
||||
### 常规登录百度帐号
|
||||
|
||||
支持在线验证绑定的手机号或邮箱,
|
||||
```
|
||||
BaiduPCS-Go login
|
||||
```
|
||||
|
||||
### 使用百度 BDUSS 来登录百度帐号
|
||||
|
||||
[关于 获取百度 BDUSS](https://blog.csdn.net/ykiwmy/article/details/103730962)
|
||||
|
||||
```
|
||||
BaiduPCS-Go login -bduss=<BDUSS>
|
||||
```
|
||||
|
||||
### 使用百度 Cookies 来登录百度账号(唯一可使用转存功能的登录方式)
|
||||
|
||||
[关于 获取百度 Cookies](https://jingyan.baidu.com/article/5553fa829a6a9e65a23934b0.html)
|
||||
教程中为百度经验的Cookies获取, 这里换成百度网盘首页即可.
|
||||
|
||||
```
|
||||
BaiduPCS-Go login -cookies=<Cookies>
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
BaiduPCS-Go login -bduss=1234567
|
||||
```
|
||||
```
|
||||
BaiduPCS-Go login
|
||||
请输入百度用户名(手机号/邮箱/用户名), 回车键提交 > 1234567
|
||||
```
|
||||
```
|
||||
BaiduPCS-Go login -cookies="BAIDUID=50949C0890YG9735EA6Q3870AFE38:FG=1; BIDUPSID=112335C0ACCAFFJW675EA69A870AFE38; PSTM=1981928511; BDORZ=D6745EBF6F3SW24E515D22A1598; PANWEB=1; BDUSS=ASAYUGFHSTFKGBGSU; STOKEN=gfsdge9gisfgspig34254d7879eee5756b10sgeyrw5vyw342td510ffc9414d32251; SCRC=cwrywec5evyetra26bvvehefvfg6a8; BDCLND=C%4sfgGysrZ%2BML6; PANPSC=wreyewygdfhdggedhsdfg4353"
|
||||
```
|
||||
|
||||
## 列出帐号列表
|
||||
|
||||
```
|
||||
BaiduPCS-Go loglist
|
||||
```
|
||||
|
||||
列出所有已登录的百度帐号
|
||||
|
||||
## 获取当前帐号
|
||||
|
||||
```
|
||||
BaiduPCS-Go who
|
||||
```
|
||||
|
||||
## 切换百度帐号
|
||||
|
||||
切换已登录的百度帐号
|
||||
```
|
||||
BaiduPCS-Go su <uid>
|
||||
```
|
||||
```
|
||||
BaiduPCS-Go su
|
||||
|
||||
请输入要切换帐号的 # 值 >
|
||||
```
|
||||
|
||||
## 退出百度帐号
|
||||
|
||||
退出当前登录的百度帐号
|
||||
```
|
||||
BaiduPCS-Go logout
|
||||
```
|
||||
|
||||
程序会进一步确认退出帐号, 防止误操作.
|
||||
|
||||
## 获取网盘配额
|
||||
|
||||
```
|
||||
BaiduPCS-Go quota
|
||||
```
|
||||
获取网盘的总储存空间, 和已使用的储存空间
|
||||
|
||||
## 切换工作目录
|
||||
```
|
||||
BaiduPCS-Go cd <目录>
|
||||
```
|
||||
|
||||
### 切换工作目录后自动列出工作目录下的文件和目录
|
||||
```
|
||||
BaiduPCS-Go cd -l <目录>
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 切换 /我的资源 工作目录
|
||||
BaiduPCS-Go cd /我的资源
|
||||
|
||||
# 切换 上级目录
|
||||
BaiduPCS-Go cd ..
|
||||
|
||||
# 切换 根目录
|
||||
BaiduPCS-Go cd /
|
||||
|
||||
# 切换 /我的资源 工作目录, 并自动列出 /我的资源 下的文件和目录
|
||||
BaiduPCS-Go cd -l 我的资源
|
||||
|
||||
# 使用通配符
|
||||
BaiduPCS-Go cd /我的*
|
||||
```
|
||||
|
||||
## 输出工作目录
|
||||
```
|
||||
BaiduPCS-Go pwd
|
||||
```
|
||||
|
||||
## 列出目录
|
||||
|
||||
列出当前工作目录的文件和目录或指定目录
|
||||
```
|
||||
BaiduPCS-Go ls
|
||||
```
|
||||
```
|
||||
BaiduPCS-Go ls <目录>
|
||||
```
|
||||
|
||||
### 可选参数
|
||||
```
|
||||
-asc: 升序排序
|
||||
-desc: 降序排序
|
||||
-time: 根据时间排序
|
||||
-name: 根据文件名排序
|
||||
-size: 根据大小排序
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 列出 我的资源 内的文件和目录
|
||||
BaiduPCS-Go ls 我的资源
|
||||
|
||||
# 绝对路径
|
||||
BaiduPCS-Go ls /我的资源
|
||||
|
||||
# 降序排序
|
||||
BaiduPCS-Go ls -desc 我的资源
|
||||
|
||||
# 按文件大小降序排序
|
||||
BaiduPCS-Go ls -size -desc 我的资源
|
||||
|
||||
# 使用通配符
|
||||
BaiduPCS-Go ls /我的*
|
||||
```
|
||||
|
||||
## 列出目录树形图
|
||||
|
||||
列出当前工作目录的文件和目录或指定目录的树形图
|
||||
```
|
||||
BaiduPCS-Go tree <目录>
|
||||
|
||||
# 默认获取工作目录元信息
|
||||
BaiduPCS-Go tree
|
||||
```
|
||||
|
||||
## 获取文件/目录的元信息
|
||||
```
|
||||
BaiduPCS-Go meta <文件/目录1> <文件/目录2> <文件/目录3> ...
|
||||
|
||||
# 默认获取工作目录元信息
|
||||
BaiduPCS-Go meta
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
BaiduPCS-Go meta 我的资源
|
||||
BaiduPCS-Go meta /
|
||||
```
|
||||
|
||||
## 搜索文件
|
||||
|
||||
按文件名搜索文件(不支持查找目录)。
|
||||
|
||||
默认在当前工作目录搜索.
|
||||
|
||||
```
|
||||
BaiduPCS-Go search [-path=<需要检索的目录>] [-r] <关键字>
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 搜索根目录的文件
|
||||
BaiduPCS-Go search -path=/ 关键字
|
||||
|
||||
# 搜索当前工作目录的文件
|
||||
BaiduPCS-Go search 关键字
|
||||
|
||||
# 递归搜索当前工作目录的文件
|
||||
BaiduPCS-Go search -r 关键字
|
||||
```
|
||||
|
||||
## 下载文件/目录
|
||||
```
|
||||
BaiduPCS-Go download <网盘文件或目录的路径1> <文件或目录2> <文件或目录3> ...
|
||||
BaiduPCS-Go d <网盘文件或目录的路径1> <文件或目录2> <文件或目录3> ...
|
||||
```
|
||||
|
||||
### 可选参数
|
||||
```
|
||||
--test 测试下载, 此操作不会保存文件到本地
|
||||
--ow overwrite, 覆盖已存在的文件
|
||||
--status 输出所有线程的工作状态
|
||||
--save 将下载的文件直接保存到当前工作目录
|
||||
--saveto value 将下载的文件直接保存到指定的目录
|
||||
-x 为文件加上执行权限, (windows系统无效)
|
||||
--mode value 下载模式, 可选值: pcs, stream, locate, 默认为 locate, 相关说明见上面的帮助 (default: "locate")
|
||||
-p value 指定下载线程数 (default: 0)
|
||||
-l value 指定同时进行下载文件的数量 (default: 0)
|
||||
--retry value 下载失败最大重试次数 (default: 3)
|
||||
--nocheck 下载文件完成后不校验文件
|
||||
|
||||
```
|
||||
|
||||
下载的文件默认保存到 **程序所在目录** 的 download/ 目录, 支持设置指定目录, 重名的文件会自动跳过!
|
||||
|
||||
下载的文件默认保存到, **程序所在目录**的 **download/** 目录.
|
||||
|
||||
通过 `BaiduPCS-Go config set -savedir <savedir>`, 自定义保存的目录.
|
||||
|
||||
支持多个文件或目录下载.
|
||||
|
||||
支持下载完成后自动校验文件, 但并不是所有的文件都支持校验!
|
||||
|
||||
自动跳过下载重名的文件!
|
||||
|
||||
|
||||
#### 下载模式说明
|
||||
|
||||
* pcs: 通过百度网盘的 PCS API 下载(已废弃)
|
||||
|
||||
* stream: 通过百度网盘的 PCS API, 以流式文件的方式下载, 效果同 pcs(已废弃)
|
||||
|
||||
* locate: 默认的下载模式。从百度网盘 Android 客户端, 获取下载链接的方式来下载
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 设置保存目录, 保存到 D:\Downloads
|
||||
# 注意区别反斜杠 "\" 和 斜杠 "/" !!!
|
||||
BaiduPCS-Go config set -savedir D:/Downloads
|
||||
|
||||
# 下载 /我的资源/1.mp4
|
||||
BaiduPCS-Go d /我的资源/1.mp4
|
||||
|
||||
# 下载 /我的资源 整个目录!!
|
||||
BaiduPCS-Go d /我的资源
|
||||
|
||||
# 下载网盘内的全部文件!!
|
||||
BaiduPCS-Go d /
|
||||
BaiduPCS-Go d *
|
||||
```
|
||||
|
||||
## 上传文件/目录
|
||||
```
|
||||
BaiduPCS-Go upload <本地文件/目录的路径1> <文件/目录2> <文件/目录3> ... <目标目录>
|
||||
BaiduPCS-Go u <本地文件/目录的路径1> <文件/目录2> <文件/目录3> ... <目标目录>
|
||||
```
|
||||
|
||||
* 上传默认采用分片上传的方式, 上传的文件将会保存到, <目标目录>.
|
||||
|
||||
* 遇到同名文件将会自动覆盖!!
|
||||
|
||||
* 当上传的文件名和网盘的目录名称相同时, 不会覆盖目录, 防止丢失数据.
|
||||
|
||||
|
||||
#### 注意:
|
||||
|
||||
* 分片上传之后, 服务器可能会记录到错误的文件md5, 可使用 fixmd5 命令尝试修复文件的MD5值, 修复md5不一定能成功, 但文件的完整性是没问题的.
|
||||
|
||||
fixmd5 命令使用方法:
|
||||
```
|
||||
BaiduPCS-Go fixmd5 -h
|
||||
```
|
||||
|
||||
* 禁用分片上传可以保证服务器记录到正确的md5.
|
||||
|
||||
* 禁用分片上传时只能使用单线程上传, 指定的单个文件上传最大线程数将会无效.
|
||||
|
||||
#### 例子:
|
||||
```
|
||||
# 将本地的 C:\Users\Administrator\Desktop\1.mp4 上传到网盘 /视频 目录
|
||||
# 注意区别反斜杠 "\" 和 斜杠 "/" !!!
|
||||
BaiduPCS-Go upload C:/Users/Administrator/Desktop/1.mp4 /视频
|
||||
|
||||
# 将本地的 C:\Users\Administrator\Desktop\1.mp4 和 C:\Users\Administrator\Desktop\2.mp4 上传到网盘 /视频 目录
|
||||
BaiduPCS-Go upload C:/Users/Administrator/Desktop/1.mp4 C:/Users/Administrator/Desktop/2.mp4 /视频
|
||||
|
||||
# 将本地的 C:\Users\Administrator\Desktop 整个目录上传到网盘 /视频 目录
|
||||
BaiduPCS-Go upload C:/Users/Administrator/Desktop /视频
|
||||
```
|
||||
|
||||
## 获取下载直链
|
||||
```
|
||||
BaiduPCS-Go locate <文件1> <文件2> ...
|
||||
```
|
||||
|
||||
#### 注意
|
||||
|
||||
若该功能无法正常使用, 提示`user is not authorized, hitcode:xxx`, 尝试更换 User-Agent 为 `netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android`:
|
||||
```
|
||||
BaiduPCS-Go config set -user_agent "netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android"
|
||||
```
|
||||
|
||||
## 手动秒传文件
|
||||
```
|
||||
BaiduPCS-Go rapidupload -length=<文件的大小> -md5=<文件的md5值> -slicemd5=<文件前256KB切片的md5值(可选)> -crc32=<文件的crc32值(可选)> <保存的网盘路径, 需包含文件名>
|
||||
BaiduPCS-Go ru -length=<文件的大小> -md5=<文件的md5值> -slicemd5=<文件前256KB切片的md5值(可选)> -crc32=<文件的crc32值(可选)> <保存的网盘路径, 需包含文件名>
|
||||
```
|
||||
|
||||
注意: 使用此功能秒传文件, 前提是知道文件的大小, md5, 前256KB切片的 md5 (可选), crc32 (可选), 且百度网盘中存在一模一样的文件.
|
||||
|
||||
上传的文件将会保存到网盘的目标目录.
|
||||
|
||||
遇到同名文件将会自动覆盖!
|
||||
|
||||
可能无法秒传 20GB 以上的文件!!
|
||||
|
||||
#### 例子:
|
||||
```
|
||||
# 如果秒传成功, 则保存到网盘路径 /test
|
||||
BaiduPCS-Go rapidupload -length=56276137 -md5=fbe082d80e90f90f0fb1f94adbbcfa7f -slicemd5=38c6a75b0ec4499271d4ea38a667ab61 -crc32=314332359 /test
|
||||
```
|
||||
|
||||
|
||||
## 修复文件MD5
|
||||
```
|
||||
BaiduPCS-Go fixmd5 <文件1> <文件2> <文件3> ...
|
||||
```
|
||||
|
||||
尝试修复文件的MD5值, 以便于校验文件的完整性和导出文件.
|
||||
|
||||
使用分片上传文件, 当文件分片数大于1时, 百度网盘服务端最终计算所得的md5值和本地的不一致, 这可能是百度网盘的bug.
|
||||
|
||||
不过把上传的文件下载到本地后,对比md5值是匹配的, 也就是文件在传输中没有发生损坏.
|
||||
|
||||
对于MD5值可能有误的文件, 程序会在获取文件的元信息时, 给出MD5值 "可能不正确" 的提示, 表示此文件可以尝试进行MD5值修复.
|
||||
|
||||
修复文件MD5不一定能成功, 原因可能是服务器未刷新, 可过几天后再尝试.
|
||||
|
||||
修复文件MD5的原理为秒传文件, 即修复文件MD5成功后, 文件的**创建日期, 修改日期, fs_id, 版本历史等信息**将会被覆盖, 修复的MD5值将覆盖原先的MD5值, 但不影响文件的完整性.
|
||||
|
||||
注意: 无法修复 **20GB** 以上文件的 md5!!
|
||||
|
||||
#### 例子:
|
||||
```
|
||||
# 修复 /我的资源/1.mp4 的 MD5 值
|
||||
BaiduPCS-Go fixmd5 /我的资源/1.mp4
|
||||
```
|
||||
|
||||
## 获取本地文件的秒传信息
|
||||
```
|
||||
BaiduPCS-Go sumfile <本地文件的路径>
|
||||
BaiduPCS-Go sf <本地文件的路径>
|
||||
```
|
||||
|
||||
获取本地文件的大小, md5, 前256KB切片的 md5, crc32, 可用于秒传文件.
|
||||
|
||||
#### 例子:
|
||||
```
|
||||
# 获取 C:\Users\Administrator\Desktop\1.mp4 的秒传信息
|
||||
BaiduPCS-Go sumfile C:/Users/Administrator/Desktop/1.mp4
|
||||
```
|
||||
|
||||
## 导出文件/目录
|
||||
```
|
||||
BaiduPCS-Go export <文件/目录1> <文件/目录2> ...
|
||||
BaiduPCS-Go ep <文件/目录1> <文件/目录2> ...
|
||||
```
|
||||
|
||||
导出网盘内的文件或目录, 原理为秒传文件, 此操作会生成导出文件或目录的命令.
|
||||
|
||||
#### 注意
|
||||
|
||||
**无法导出 20GB 以上的文件!!**
|
||||
|
||||
**无法导出文件的版本历史等数据!!**
|
||||
|
||||
**以通用秒传格式导出会丢失文件路径信息!!**
|
||||
|
||||
并不是所有的文件都能导出成功, 程序会列出无法导出的文件列表
|
||||
|
||||
#### 例子:
|
||||
```
|
||||
# 导出当前工作目录:
|
||||
BaiduPCS-Go export
|
||||
|
||||
# 导出所有文件和目录, 并设置新的根目录为 /root
|
||||
BaiduPCS-Go export -root=/root /
|
||||
|
||||
# 导出 /我的资源
|
||||
BaiduPCS-Go export /我的资源
|
||||
|
||||
# 导出 /我的资源 格式为通用秒传链接格式
|
||||
BaiduPCS-Go export /我的资源 --link
|
||||
```
|
||||
|
||||
## 创建目录
|
||||
```
|
||||
BaiduPCS-Go mkdir <目录>
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
BaiduPCS-Go mkdir 123
|
||||
```
|
||||
|
||||
## 删除文件/目录
|
||||
```
|
||||
BaiduPCS-Go rm <网盘文件或目录的路径1> <文件或目录2> <文件或目录3> ...
|
||||
```
|
||||
|
||||
注意: 删除多个文件和目录时, 请确保每一个文件和目录都存在, 否则删除操作会失败.
|
||||
|
||||
被删除的文件或目录可在网盘文件回收站找回.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 删除 /我的资源/1.mp4
|
||||
BaiduPCS-Go rm /我的资源/1.mp4
|
||||
|
||||
# 删除 /我的资源/1.mp4 和 /我的资源/2.mp4
|
||||
BaiduPCS-Go rm /我的资源/1.mp4 /我的资源/2.mp4
|
||||
|
||||
# 删除 /我的资源 内的所有文件和目录, 但不删除该目录
|
||||
BaiduPCS-Go rm /我的资源/*
|
||||
|
||||
# 删除 /我的资源 整个目录 !!
|
||||
BaiduPCS-Go rm /我的资源
|
||||
```
|
||||
|
||||
## 拷贝文件/目录
|
||||
```
|
||||
BaiduPCS-Go cp <文件/目录> <目标 文件/目录>
|
||||
BaiduPCS-Go cp <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>
|
||||
```
|
||||
|
||||
注意: 拷贝多个文件和目录时, 请确保每一个文件和目录都存在, 否则拷贝操作会失败.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 将 /我的资源/1.mp4 复制到 根目录 /
|
||||
BaiduPCS-Go cp /我的资源/1.mp4 /
|
||||
|
||||
# 将 /我的资源/1.mp4 和 /我的资源/2.mp4 复制到 根目录 /
|
||||
BaiduPCS-Go cp /我的资源/1.mp4 /我的资源/2.mp4 /
|
||||
```
|
||||
|
||||
## 移动/重命名文件/目录
|
||||
```
|
||||
# 移动:
|
||||
BaiduPCS-Go mv <文件/目录1> <文件/目录2> <文件/目录3> ... <目标目录>
|
||||
# 重命名:
|
||||
BaiduPCS-Go mv <文件/目录> <重命名的文件/目录>
|
||||
```
|
||||
|
||||
注意: 移动多个文件和目录时, 请确保每一个文件和目录都存在, 否则移动操作会失败.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 将 /我的资源/1.mp4 移动到 根目录 /
|
||||
BaiduPCS-Go mv /我的资源/1.mp4 /
|
||||
|
||||
# 将 /我的资源/1.mp4 重命名为 /我的资源/3.mp4
|
||||
BaiduPCS-Go mv /我的资源/1.mp4 /我的资源/3.mp4
|
||||
```
|
||||
|
||||
## 转存文件/目录
|
||||
```
|
||||
# 转存分享链接里的文件到当前目录:
|
||||
BaiduPCS-Go transfer <分享链接> <提取码>
|
||||
# 转存通用秒传链接里的文件到当前目录:
|
||||
BaiduPCS-Go transfer <秒传链接>
|
||||
```
|
||||
|
||||
注意: 公开分享链接不需输入提取码, 支持多个文件/目录; 只支持包含单个文件的秒传链接.
|
||||
|
||||
转存文件保存到当前工作目录下, 不支持指定. 欲使用此功能登录时需要使用cookies方式.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 将 https://pan.baidu.com/s/12L_ZZVNxz5f_2CccoyyVrW (提取码edv4) 转存到当前目录
|
||||
BaiduPCS-Go transfer https://pan.baidu.com/s/12L_ZZVNxz5f_2CccoyyVrW edv4
|
||||
|
||||
# 将 E7E7B8613854379642F70230B179F37A#FA690D0AB7C8BC6A62WD1B6B3FC5248F#128859362#test.7z 转存到当前目录
|
||||
BaiduPCS-Go transfer E7E7B8613854379642F70230B179F37A#FA690D0AB7C8BC6A62WD1B6B3FC5248F#128859362#test.7z
|
||||
```
|
||||
|
||||
## 分享文件/目录
|
||||
```
|
||||
BaiduPCS-Go share
|
||||
```
|
||||
|
||||
### 设置分享文件/目录
|
||||
```
|
||||
BaiduPCS-Go share set <文件/目录1> <文件/目录2> ...
|
||||
BaiduPCS-Go share s <文件/目录1> <文件/目录2> ...
|
||||
```
|
||||
|
||||
### 列出已分享文件/目录
|
||||
```
|
||||
BaiduPCS-Go share list
|
||||
BaiduPCS-Go share l
|
||||
```
|
||||
|
||||
### 取消分享文件/目录
|
||||
```
|
||||
BaiduPCS-Go share cancel <shareid_1> <shareid_2> ...
|
||||
BaiduPCS-Go share c <shareid_1> <shareid_2> ...
|
||||
```
|
||||
|
||||
目前只支持通过分享id (shareid) 来取消分享.
|
||||
|
||||
## 离线下载
|
||||
```
|
||||
BaiduPCS-Go offlinedl
|
||||
BaiduPCS-Go clouddl
|
||||
BaiduPCS-Go od
|
||||
```
|
||||
|
||||
离线下载支持http/https/ftp/电驴/磁力链协议
|
||||
|
||||
离线下载同时进行的任务数量有限, 超出限制的部分将无法添加.
|
||||
|
||||
### 添加离线下载任务
|
||||
```
|
||||
BaiduPCS-Go offlinedl add -path=<离线下载文件保存的路径> 资源地址1 地址2 ...
|
||||
```
|
||||
|
||||
添加任务成功之后, 返回离线下载的任务ID.
|
||||
|
||||
### 精确查询离线下载任务
|
||||
```
|
||||
BaiduPCS-Go offlinedl query 任务ID1 任务ID2 ...
|
||||
```
|
||||
|
||||
### 查询离线下载任务列表
|
||||
```
|
||||
BaiduPCS-Go offlinedl list
|
||||
```
|
||||
|
||||
### 取消离线下载任务
|
||||
```
|
||||
BaiduPCS-Go offlinedl cancel 任务ID1 任务ID2 ...
|
||||
```
|
||||
|
||||
### 删除离线下载任务
|
||||
```
|
||||
BaiduPCS-Go offlinedl delete 任务ID1 任务ID2 ...
|
||||
|
||||
# 清空离线下载任务记录, 程序不会进行二次确认, 谨慎操作!!!
|
||||
BaiduPCS-Go offlinedl delete -all
|
||||
```
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 将百度和腾讯主页, 离线下载到根目录 /
|
||||
BaiduPCS-Go offlinedl add -path=/ http://baidu.com http://qq.com
|
||||
|
||||
# 添加磁力链接任务
|
||||
BaiduPCS-Go offlinedl add magnet:?xt=urn:btih:xxx
|
||||
|
||||
# 查询任务ID为 12345 的离线下载任务状态
|
||||
BaiduPCS-Go offlinedl query 12345
|
||||
|
||||
# 取消任务ID为 12345 的离线下载任务
|
||||
BaiduPCS-Go offlinedl cancel 12345
|
||||
```
|
||||
|
||||
## 回收站
|
||||
```
|
||||
BaiduPCS-Go recycle
|
||||
```
|
||||
|
||||
回收站操作.
|
||||
|
||||
### 列出回收站文件列表
|
||||
```
|
||||
BaiduPCS-Go recycle list
|
||||
```
|
||||
|
||||
#### 可选参数
|
||||
```
|
||||
--page value 回收站文件列表页数 (default: 1)
|
||||
```
|
||||
|
||||
### 还原回收站文件或目录
|
||||
```
|
||||
BaiduPCS-Go recycle restore <fs_id 1> <fs_id 2> <fs_id 3> ...
|
||||
```
|
||||
|
||||
根据文件/目录的 fs_id, 还原回收站指定的文件或目录.
|
||||
|
||||
### 删除回收站文件或目录/清空回收站
|
||||
```
|
||||
BaiduPCS-Go recycle delete [-all] <fs_id 1> <fs_id 2> <fs_id 3> ...
|
||||
```
|
||||
|
||||
根据文件/目录的 fs_id 或 -all 参数, 删除回收站指定的文件或目录或清空回收站.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 从回收站还原两个文件, 其中的两个文件的 fs_id 分别为 1013792297798440 和 643596340463870
|
||||
BaiduPCS-Go recycle restore 1013792297798440 643596340463870
|
||||
|
||||
# 从回收站删除两个文件, 其中的两个文件的 fs_id 分别为 1013792297798440 和 643596340463870
|
||||
BaiduPCS-Go recycle delete 1013792297798440 643596340463870
|
||||
|
||||
# 清空回收站, 程序不会进行二次确认, 谨慎操作!!!
|
||||
BaiduPCS-Go recycle delete -all
|
||||
```
|
||||
|
||||
## 显示程序环境变量
|
||||
```
|
||||
BaiduPCS-Go env
|
||||
```
|
||||
|
||||
BAIDUPCS_GO_CONFIG_DIR: 配置文件路径,
|
||||
|
||||
BAIDUPCS_GO_VERBOSE: 是否启用调试.
|
||||
|
||||
## 显示和修改程序配置项
|
||||
```
|
||||
# 显示配置
|
||||
BaiduPCS-Go config
|
||||
|
||||
# 设置配置
|
||||
BaiduPCS-Go config set
|
||||
```
|
||||
|
||||
注意: v3.5 以后, 程序对配置文件储存路径的寻找做了调整, 配置文件所在的目录可以是程序本身所在目录, 也可以是家目录.
|
||||
|
||||
配置文件所在的目录为家目录的情况:
|
||||
|
||||
Windows: `%APPDATA%\BaiduPCS-Go`
|
||||
|
||||
其他操作系统: `$HOME/.config/BaiduPCS-Go`
|
||||
|
||||
可通过设置环境变量 `BAIDUPCS_GO_CONFIG_DIR`, 指定配置文件存放的目录.
|
||||
|
||||
谨慎修改 `appid`, `user_agent`, `pcs_ua`, `pan_ua` 的值, 否则访问网盘服务器时, 可能会出现错误.
|
||||
|
||||
`cache_size` 的值支持可选设置单位了, 单位不区分大小写, `b` 和 `B` 均表示字节的意思, 如 `64KB`, `1MB`, `32kb`, `65536b`, `65536`.
|
||||
|
||||
`max_upload_parallel`, `max_download_load` 的值支持可选设置单位了, 单位为每秒的传输速率, 后缀`/s` 可省略, 如 `2MB/s`, `2MB`, `2m`, `2mb` 均为一个意思.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 显示所有可以设置的值
|
||||
BaiduPCS-Go config -h
|
||||
BaiduPCS-Go config set -h
|
||||
|
||||
# 设置下载文件的储存目录
|
||||
BaiduPCS-Go config set -savedir D:/Downloads
|
||||
|
||||
# 设置下载最大并发量为 150
|
||||
BaiduPCS-Go config set -max_parallel 150
|
||||
|
||||
# 组合设置
|
||||
BaiduPCS-Go config set -max_parallel 150 -savedir D:/Downloads
|
||||
```
|
||||
|
||||
## 测试通配符
|
||||
```
|
||||
BaiduPCS-Go match <通配符表达式>
|
||||
```
|
||||
|
||||
测试通配符匹配路径, 操作成功则输出所有匹配到的路径.
|
||||
|
||||
#### 例子
|
||||
```
|
||||
# 匹配 /我的资源 目录下所有mp4格式的文件
|
||||
BaiduPCS-Go match /我的资源/*.mp4
|
||||
```
|
||||
|
||||
## 工具箱
|
||||
```
|
||||
BaiduPCS-Go tool
|
||||
```
|
||||
|
||||
目前工具箱支持加解密文件等.
|
||||
|
||||
# 初级使用教程
|
||||
|
||||
新手建议: **双击运行程序**, 进入仿 Linux shell 的 cli 交互模式;
|
||||
|
||||
cli交互模式下, 光标所在行的前缀应为 `BaiduPCS-Go >`, 如果登录了百度帐号则格式为 `BaiduPCS-Go:<工作目录> <百度ID>$ `
|
||||
|
||||
以下例子的命令, 均为 cli交互模式下的命令
|
||||
|
||||
运行命令的正确操作: **输入命令, 按一下回车键 (键盘上的 Enter 键)**, 程序会接收到命令并输出结果
|
||||
|
||||
## 1. 查看程序使用说明
|
||||
|
||||
cli交互模式下, 运行命令 `help`
|
||||
|
||||
## 2. 登录百度帐号 (必做)
|
||||
|
||||
cli交互模式下, 运行命令 `login -h` (注意空格) 查看帮助
|
||||
|
||||
cli交互模式下, 运行命令 `login` 程序将会提示你输入百度用户名(手机号/邮箱/用户名)和密码, 必要时还可以在线验证绑定的手机号或邮箱
|
||||
|
||||
## 3. 切换网盘工作目录
|
||||
|
||||
cli交互模式下, 运行命令 `cd /我的资源` 将工作目录切换为 `/我的资源` (前提: 该目录存在于网盘)
|
||||
|
||||
目录支持通配符匹配, 所以你也可以这样: 运行命令 `cd /我的*` 或 `cd /我的??` 将工作目录切换为 `/我的资源`, 简化输入.
|
||||
|
||||
将工作目录切换为 `/我的资源` 成功后, 运行命令 `cd ..` 切换上级目录, 即将工作目录切换为 `/`
|
||||
|
||||
为什么要这样设计呢, 举个例子,
|
||||
|
||||
假设 你要下载 `/我的资源` 内名为 `1.mp4` 和 `2.mp4` 两个文件, 而未切换工作目录, 你需要依次运行以下命令:
|
||||
|
||||
```
|
||||
d /我的资源/1.mp4
|
||||
d /我的资源/2.mp4
|
||||
```
|
||||
|
||||
而切换网盘工作目录之后, 依次运行以下命令:
|
||||
|
||||
```
|
||||
cd /我的资源
|
||||
d 1.mp4
|
||||
d 2.mp4
|
||||
```
|
||||
|
||||
这样就达到了简化输入的目的
|
||||
|
||||
## 4. 网盘内列出文件和目录
|
||||
|
||||
cli交互模式下, 运行命令 `ls -h` (注意空格) 查看帮助
|
||||
|
||||
cli交互模式下, 运行命令 `ls` 来列出当前所在目录的文件和目录
|
||||
|
||||
cli交互模式下, 运行命令 `ls /我的资源` 来列出 `/我的资源` 内的文件和目录
|
||||
|
||||
cli交互模式下, 运行命令 `ls ..` 来列出当前所在目录的上级目录的文件和目录
|
||||
|
||||
## 5. 下载文件
|
||||
|
||||
说明: 下载的文件默认保存到 download/ 目录 (文件夹)
|
||||
|
||||
cli交互模式下, 运行命令 `d -h` (注意空格) 查看帮助
|
||||
|
||||
cli交互模式下, 运行命令 `d /我的资源/1.mp4` 来下载位于 `/我的资源/1.mp4` 的文件 `1.mp4` , 该操作等效于运行以下命令:
|
||||
|
||||
```
|
||||
cd /我的资源
|
||||
d 1.mp4
|
||||
```
|
||||
|
||||
现在已经支持目录 (文件夹) 下载, 所以, 运行以下命令, 会下载 `/我的资源` 内的所有文件 (违规文件除外):
|
||||
|
||||
```
|
||||
d /我的资源
|
||||
```
|
||||
|
||||
## 6. 设置下载最大并发量
|
||||
|
||||
cli交互模式下, 运行命令 `config set -h` (注意空格) 查看设置帮助以及可供设置的值
|
||||
|
||||
cli交互模式下, 运行命令 `config set -max_parallel 2` 将下载最大并发量设置为 2
|
||||
|
||||
注意:下载最大并发量的值不易设置过高, 可能会导致百度帐号被限制下载
|
||||
|
||||
## 7. 退出程序
|
||||
|
||||
运行命令 `quit` 或 `exit` 或 组合键 `Ctrl+C` 或 组合键 `Ctrl+D`
|
||||
|
||||
# 已知问题
|
||||
|
||||
* 分片上传文件时, 当文件分片数大于1, 网盘端最终计算所得的md5值和本地的不一致, 这可能是百度网盘的bug, 测试把上传的文件下载到本地后,对比md5值是匹配的. 可通过秒传的原理来修复md5值.
|
||||
* 下载文件时可能会check MD5 不通过, 但文件其实并未出错, 使用--no-check下载即可.
|
||||
* 下载线程, 即max_parallel参数设置过大时可能导致账号进入黑名单状态, 建议普通用户设置为1, 超级会员可尝试调大, 但不建议超过5.
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
|
||||
# 交流反馈
|
||||
|
||||
提交Issue: [Issues](https://github.com/qjfoidnh/BaiduPCS-Go-Transfer/issues)
|
||||
|
||||
BIN
assets/BaiduPCS-Go.ico
Normal file
BIN
assets/BaiduPCS-Go.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
assets/caution.png
Normal file
BIN
assets/caution.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
368
baidupcs/baidupcs.go
Normal file
368
baidupcs/baidupcs.go
Normal file
@@ -0,0 +1,368 @@
|
||||
// Package baidupcs BaiduPCS RESTful API 工具包
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires/cachemap"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/internal/panhome"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsverbose"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
)
|
||||
|
||||
const (
|
||||
// OperationGetUK 获取UK
|
||||
OperationGetUK = "获取UK"
|
||||
// OperationQuotaInfo 获取当前用户空间配额信息
|
||||
OperationQuotaInfo = "获取当前用户空间配额信息"
|
||||
// OperationFilesDirectoriesMeta 获取文件/目录的元信息
|
||||
OperationFilesDirectoriesMeta = "获取文件/目录的元信息"
|
||||
// OperationFilesDirectoriesList 获取目录下的文件列表
|
||||
OperationFilesDirectoriesList = "获取目录下的文件列表"
|
||||
// OperationSearch 搜索
|
||||
OperationSearch = "搜索"
|
||||
// OperationRemove 删除文件/目录
|
||||
OperationRemove = "删除文件/目录"
|
||||
// OperationMkdir 创建目录
|
||||
OperationMkdir = "创建目录"
|
||||
// OperationRename 重命名文件/目录
|
||||
OperationRename = "重命名文件/目录"
|
||||
// OperationCopy 拷贝文件/目录
|
||||
OperationCopy = "拷贝文件/目录"
|
||||
// OperationMove 移动文件/目录
|
||||
OperationMove = "移动文件/目录"
|
||||
// OperationRapidUpload 秒传文件
|
||||
OperationRapidUpload = "秒传文件"
|
||||
// OperationUpload 上传单个文件
|
||||
OperationUpload = "上传单个文件"
|
||||
// OperationUploadTmpFile 分片上传—文件分片及上传
|
||||
OperationUploadTmpFile = "分片上传—文件分片及上传"
|
||||
// OperationUploadCreateSuperFile 分片上传—合并分片文件
|
||||
OperationUploadCreateSuperFile = "分片上传—合并分片文件"
|
||||
// OperationUploadPrecreate 分片上传—Precreate
|
||||
OperationUploadPrecreate = "分片上传—Precreate"
|
||||
// OperationUploadSuperfile2 分片上传—Superfile2
|
||||
OperationUploadSuperfile2 = "分片上传—Superfile2"
|
||||
// OperationDownloadFile 下载单个文件
|
||||
OperationDownloadFile = "下载单个文件"
|
||||
// OperationDownloadStreamFile 下载流式文件
|
||||
OperationDownloadStreamFile = "下载流式文件"
|
||||
// OperationLocateDownload 获取下载链接
|
||||
OperationLocateDownload = "获取下载链接"
|
||||
// OperationLocatePanAPIDownload 从百度网盘首页获取下载链接
|
||||
OperationLocatePanAPIDownload = "获取下载链接2"
|
||||
// OperationCloudDlAddTask 添加离线下载任务
|
||||
OperationCloudDlAddTask = "添加离线下载任务"
|
||||
// OperationCloudDlQueryTask 精确查询离线下载任务
|
||||
OperationCloudDlQueryTask = "精确查询离线下载任务"
|
||||
// OperationCloudDlListTask 查询离线下载任务列表
|
||||
OperationCloudDlListTask = "查询离线下载任务列表"
|
||||
// OperationCloudDlCancelTask 取消离线下载任务
|
||||
OperationCloudDlCancelTask = "取消离线下载任务"
|
||||
// OperationCloudDlDeleteTask 删除离线下载任务
|
||||
OperationCloudDlDeleteTask = "删除离线下载任务"
|
||||
// OperationCloudDlClearTask 清空离线下载任务记录
|
||||
OperationCloudDlClearTask = "清空离线下载任务记录"
|
||||
// OperationShareSet 创建分享链接
|
||||
OperationShareSet = "创建分享链接"
|
||||
// OperationShareCancel 取消分享
|
||||
OperationShareCancel = "取消分享"
|
||||
// OperationShareList 列出分享列表
|
||||
OperationShareList = "列出分享列表"
|
||||
// OperationShareSURLInfo 获取分享详细信息
|
||||
OperationShareSURLInfo = "获取分享详细信息"
|
||||
// OperationShareFileSavetoLocal 用分享链接转存到网盘
|
||||
OperationShareFileSavetoLocal = "用分享链接转存到网盘"
|
||||
// OperationRapidLinkSavetoLocal 用秒传链接转存到网盘
|
||||
OperationRapidLinkSavetoLocal = "用秒传链接转存到网盘"
|
||||
// OperationRecycleList 列出回收站文件列表
|
||||
OperationRecycleList = "列出回收站文件列表"
|
||||
// OperationRecycleRestore 还原回收站文件或目录
|
||||
OperationRecycleRestore = "还原回收站文件或目录"
|
||||
// OperationRecycleDelete 删除回收站文件或目录
|
||||
OperationRecycleDelete = "删除回收站文件或目录"
|
||||
// OperationRecycleClear 清空回收站
|
||||
OperationRecycleClear = "清空回收站"
|
||||
|
||||
// OperationExportFileInfo 导出文件信息
|
||||
OperationExportFileInfo = "导出文件信息"
|
||||
// OperationGetRapidUploadInfo 获取文件秒传信息
|
||||
OperationGetRapidUploadInfo = "获取文件秒传信息"
|
||||
// OperationFixMD5 修复文件md5
|
||||
OperationFixMD5 = "修复文件md5"
|
||||
// OperrationMatchPathByShellPattern 通配符匹配文件路径
|
||||
OperrationMatchPathByShellPattern = "通配符匹配文件路径"
|
||||
|
||||
// PCSBaiduCom pcs api地址
|
||||
PCSBaiduCom = "pcs.baidu.com"
|
||||
// PanBaiduCom 网盘首页api地址
|
||||
PanBaiduCom = "pan.baidu.com"
|
||||
// YunBaiduCom 网盘首页api地址2
|
||||
YunBaiduCom = "yun.baidu.com"
|
||||
// PanAppID 百度网盘appid
|
||||
PanAppID = "250528"
|
||||
// NetdiskUA 网盘客户端ua
|
||||
NetdiskUA = "netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android"
|
||||
// DotBaiduCom .baidu.com
|
||||
DotBaiduCom = ".baidu.com"
|
||||
// PathSeparator 路径分隔符
|
||||
PathSeparator = "/"
|
||||
)
|
||||
|
||||
var (
|
||||
baiduPCSVerbose = pcsverbose.New("BAIDUPCS")
|
||||
|
||||
baiduComURL = &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "baidu.com",
|
||||
}
|
||||
|
||||
baiduPcsComURL = &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "baidupcs.com",
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// BaiduPCS 百度 PCS API 详情
|
||||
BaiduPCS struct {
|
||||
appID int // app_id
|
||||
isHTTPS bool // 是否启用https
|
||||
uid uint64 // 百度uid
|
||||
client *requester.HTTPClient // http 客户端
|
||||
pcsUA string
|
||||
panUA string
|
||||
isSetPanUA bool
|
||||
ph *panhome.PanHome
|
||||
cacheOpMap cachemap.CacheOpMap
|
||||
}
|
||||
|
||||
userInfoJSON struct {
|
||||
*pcserror.PanErrorInfo
|
||||
Records []struct {
|
||||
Uk int64 `json:"uk"`
|
||||
} `json:"records"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewPCS 提供app_id, 百度BDUSS, 返回 BaiduPCS 对象
|
||||
func NewPCS(appID int, bduss string) *BaiduPCS {
|
||||
client := requester.NewHTTPClient()
|
||||
client.ResetCookiejar()
|
||||
client.Jar.SetCookies(baiduComURL, []*http.Cookie{
|
||||
&http.Cookie{
|
||||
Name: "BDUSS",
|
||||
Value: bduss,
|
||||
Domain: DotBaiduCom,
|
||||
},
|
||||
})
|
||||
|
||||
return &BaiduPCS{
|
||||
appID: appID,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPCSWithClient 提供app_id, 自定义客户端, 返回 BaiduPCS 对象
|
||||
func NewPCSWithClient(appID int, client *requester.HTTPClient) *BaiduPCS {
|
||||
pcs := &BaiduPCS{
|
||||
appID: appID,
|
||||
client: client,
|
||||
}
|
||||
return pcs
|
||||
}
|
||||
|
||||
// NewPCSWithCookieStr 提供app_id, cookie 字符串, 返回 BaiduPCS 对象
|
||||
func NewPCSWithCookieStr(appID int, cookieStr string) *BaiduPCS {
|
||||
pcs := &BaiduPCS{
|
||||
appID: appID,
|
||||
client: requester.NewHTTPClient(),
|
||||
}
|
||||
|
||||
cookies := requester.ParseCookieStr(cookieStr)
|
||||
for _, cookie := range cookies {
|
||||
cookie.Domain = DotBaiduCom
|
||||
}
|
||||
|
||||
jar, _ := cookiejar.New(nil)
|
||||
jar.SetCookies(baiduComURL, cookies)
|
||||
pcs.client.SetCookiejar(jar)
|
||||
|
||||
return pcs
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) lazyInit() {
|
||||
if pcs.client == nil {
|
||||
pcs.client = requester.NewHTTPClient()
|
||||
}
|
||||
if pcs.ph == nil {
|
||||
pcs.ph = panhome.NewPanHome(pcs.client)
|
||||
}
|
||||
if !pcs.isSetPanUA {
|
||||
pcs.panUA = NetdiskUA
|
||||
}
|
||||
}
|
||||
|
||||
// GetClient 获取当前的http client
|
||||
func (pcs *BaiduPCS) GetClient() *requester.HTTPClient {
|
||||
pcs.lazyInit()
|
||||
return pcs.client
|
||||
}
|
||||
|
||||
// GetBDUSS 获取BDUSS
|
||||
func (pcs *BaiduPCS) GetBDUSS() (bduss string) {
|
||||
if pcs.client == nil || pcs.client.Jar == nil {
|
||||
return ""
|
||||
}
|
||||
cookies := pcs.client.Jar.Cookies(baiduComURL)
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == "BDUSS" {
|
||||
return cookie.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetAPPID 设置app_id
|
||||
func (pcs *BaiduPCS) SetAPPID(appID int) {
|
||||
pcs.appID = appID
|
||||
}
|
||||
|
||||
// SetUID 设置百度UID
|
||||
// 只有locatedownload才需要设置此项
|
||||
func (pcs *BaiduPCS) SetUID(uid uint64) {
|
||||
pcs.uid = uid
|
||||
}
|
||||
|
||||
// SetStoken 设置stoken
|
||||
func (pcs *BaiduPCS) SetStoken(stoken string) {
|
||||
pcs.lazyInit()
|
||||
if pcs.client.Jar == nil {
|
||||
pcs.client.ResetCookiejar()
|
||||
}
|
||||
|
||||
pcs.client.Jar.SetCookies(baiduComURL, []*http.Cookie{
|
||||
&http.Cookie{
|
||||
Name: "STOKEN",
|
||||
Value: stoken,
|
||||
Domain: DotBaiduCom,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SetPCSUserAgent 设置 PCS User-Agent
|
||||
func (pcs *BaiduPCS) SetPCSUserAgent(ua string) {
|
||||
pcs.pcsUA = ua
|
||||
}
|
||||
|
||||
// SetPanUserAgent 设置 Pan User-Agent
|
||||
func (pcs *BaiduPCS) SetPanUserAgent(ua string) {
|
||||
pcs.panUA = ua
|
||||
pcs.isSetPanUA = true
|
||||
}
|
||||
|
||||
// SetHTTPS 是否启用https连接
|
||||
func (pcs *BaiduPCS) SetHTTPS(https bool) {
|
||||
pcs.isHTTPS = https
|
||||
}
|
||||
|
||||
// URL 返回 url
|
||||
func (pcs *BaiduPCS) URL() *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: GetHTTPScheme(pcs.isHTTPS),
|
||||
Host: PCSBaiduCom,
|
||||
}
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) getPanUAHeader() (header map[string]string) {
|
||||
return map[string]string{
|
||||
"User-Agent": pcs.panUA,
|
||||
}
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) generatePCSURL(subPath, method string, param ...map[string]string) *url.URL {
|
||||
pcsURL := pcs.URL()
|
||||
pcsURL.Path = "/rest/2.0/pcs/" + subPath
|
||||
|
||||
uv := pcsURL.Query()
|
||||
uv.Set("app_id", strconv.Itoa(pcs.appID))
|
||||
uv.Set("method", method)
|
||||
for k := range param {
|
||||
for k2 := range param[k] {
|
||||
uv.Set(k2, param[k][k2])
|
||||
}
|
||||
}
|
||||
|
||||
pcsURL.RawQuery = uv.Encode()
|
||||
return pcsURL
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) generatePCSURL2(subPath, method string, param ...map[string]string) *url.URL {
|
||||
pcsURL2 := &url.URL{
|
||||
Scheme: GetHTTPScheme(pcs.isHTTPS),
|
||||
Host: PanBaiduCom,
|
||||
Path: "/rest/2.0/" + subPath,
|
||||
}
|
||||
|
||||
uv := pcsURL2.Query()
|
||||
uv.Set("app_id", PanAppID)
|
||||
uv.Set("method", method)
|
||||
for k := range param {
|
||||
for k2 := range param[k] {
|
||||
uv.Set(k2, param[k][k2])
|
||||
}
|
||||
}
|
||||
|
||||
pcsURL2.RawQuery = uv.Encode()
|
||||
return pcsURL2
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) generatePanURL(subPath string, param map[string]string) *url.URL {
|
||||
panURL := url.URL{
|
||||
Scheme: GetHTTPScheme(pcs.isHTTPS),
|
||||
Host: PanBaiduCom,
|
||||
Path: "/api/" + subPath,
|
||||
}
|
||||
|
||||
if param != nil {
|
||||
uv := url.Values{}
|
||||
for k := range param {
|
||||
uv.Set(k, param[k])
|
||||
}
|
||||
panURL.RawQuery = uv.Encode()
|
||||
}
|
||||
return &panURL
|
||||
}
|
||||
|
||||
// UK 获取用户 UK
|
||||
func (pcs *BaiduPCS) UK() (uk int64, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUK()
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationGetUK)
|
||||
jsonData := userInfoJSON{
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationGetUK, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(jsonData.Records) != 1 {
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
errInfo.Err = errors.New("Unknown remote data")
|
||||
return 0, errInfo
|
||||
}
|
||||
|
||||
return jsonData.Records[0].Uk, nil
|
||||
}
|
||||
35
baidupcs/cache.go
Normal file
35
baidupcs/cache.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"time"
|
||||
)
|
||||
|
||||
// deleteCache 删除含有 dirs 的缓存
|
||||
func (pcs *BaiduPCS) deleteCache(dirs []string) {
|
||||
cache := pcs.cacheOpMap.LazyInitCachePoolOp(OperationFilesDirectoriesList)
|
||||
for _, v := range dirs {
|
||||
key := v + "_" + defaultOrderOptionsStr
|
||||
_, ok := cache.Load(key)
|
||||
if ok {
|
||||
cache.Delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CacheFilesDirectoriesList 缓存获取
|
||||
func (pcs *BaiduPCS) CacheFilesDirectoriesList(path string, options *OrderOptions) (fdl FileDirectoryList, pcsError pcserror.Error) {
|
||||
data := pcs.cacheOpMap.CacheOperation(OperationFilesDirectoriesList, path+"_"+fmt.Sprint(options), func() expires.DataExpires {
|
||||
fdl, pcsError = pcs.FilesDirectoriesList(path, options)
|
||||
if pcsError != nil {
|
||||
return nil
|
||||
}
|
||||
return expires.NewDataExpires(fdl, 1*time.Minute)
|
||||
})
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
return data.Data().(FileDirectoryList), nil
|
||||
}
|
||||
337
baidupcs/cloud_dl.go
Normal file
337
baidupcs/cloud_dl.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/pcstime"
|
||||
"io"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// CloudDlFileInfo 离线下载的文件信息
|
||||
CloudDlFileInfo struct {
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
}
|
||||
|
||||
// CloudDlTaskInfo 离线下载的任务信息
|
||||
CloudDlTaskInfo struct {
|
||||
TaskID int64
|
||||
Status int // 0下载成功, 1下载进行中, 2系统错误, 3资源不存在, 4下载超时, 5资源存在但下载失败, 6存储空间不足, 7任务取消
|
||||
StatusText string
|
||||
FileSize int64 // 文件大小
|
||||
FinishedSize int64 // 文件大小
|
||||
CreateTime int64 // 创建时间
|
||||
StartTime int64 // 开始时间
|
||||
FinishTime int64 // 结束时间
|
||||
SavePath string // 保存的路径
|
||||
SourceURL string // 资源地址
|
||||
TaskName string // 任务名称, 一般为文件名
|
||||
OdType int
|
||||
FileList []*CloudDlFileInfo
|
||||
Result int // 0查询成功,结果有效,1要查询的task_id不存在
|
||||
}
|
||||
|
||||
// CloudDlTaskList 离线下载的任务信息列表
|
||||
CloudDlTaskList []*CloudDlTaskInfo
|
||||
|
||||
// cloudDlTaskInfo 用于解析远程返回的JSON
|
||||
cloudDlTaskInfo struct {
|
||||
Status string `json:"status"`
|
||||
FileSize string `json:"file_size"`
|
||||
FinishedSize string `json:"finished_size"`
|
||||
CreateTime string `json:"create_time"`
|
||||
StartTime string `json:"start_time"`
|
||||
FinishTime string `json:"finish_time"`
|
||||
SavePath string `json:"save_path"`
|
||||
SourceURL string `json:"source_url"`
|
||||
TaskName string `json:"task_name"`
|
||||
OdType string `json:"od_type"`
|
||||
FileList []*struct {
|
||||
FileName string `json:"file_name"`
|
||||
FileSize string `json:"file_size"`
|
||||
} `json:"file_list"`
|
||||
Result int `json:"result"`
|
||||
}
|
||||
|
||||
taskIDJSON struct {
|
||||
TaskID string `json:"task_id"`
|
||||
}
|
||||
|
||||
taskIDInt64JSON struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
}
|
||||
|
||||
cloudDlAddTaskJSON struct {
|
||||
*pcserror.PCSErrInfo
|
||||
taskIDInt64JSON
|
||||
}
|
||||
|
||||
cloudDlQueryTaskJSON struct {
|
||||
TaskInfo map[string]*cloudDlTaskInfo `json:"task_info"`
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
|
||||
cloudDlListTaskJSON struct {
|
||||
TaskInfo []*taskIDJSON `json:"task_info"`
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
|
||||
cloudDlClearJSON struct {
|
||||
Total int `json:"total"`
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
)
|
||||
|
||||
func (ci *cloudDlTaskInfo) convert() *CloudDlTaskInfo {
|
||||
ci2 := &CloudDlTaskInfo{
|
||||
Status: converter.MustInt(ci.Status),
|
||||
FileSize: converter.MustInt64(ci.FileSize),
|
||||
FinishedSize: converter.MustInt64(ci.FinishedSize),
|
||||
CreateTime: converter.MustInt64(ci.CreateTime),
|
||||
StartTime: converter.MustInt64(ci.StartTime),
|
||||
FinishTime: converter.MustInt64(ci.FinishTime),
|
||||
SavePath: ci.SavePath,
|
||||
SourceURL: ci.SourceURL,
|
||||
TaskName: ci.TaskName,
|
||||
OdType: converter.MustInt(ci.OdType),
|
||||
Result: ci.Result,
|
||||
}
|
||||
|
||||
ci2.FileList = make([]*CloudDlFileInfo, 0, len(ci.FileList))
|
||||
for _, v := range ci.FileList {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ci2.FileList = append(ci2.FileList, &CloudDlFileInfo{
|
||||
FileName: v.FileName,
|
||||
FileSize: converter.MustInt64(v.FileSize),
|
||||
})
|
||||
}
|
||||
|
||||
return ci2
|
||||
}
|
||||
|
||||
// CloudDlAddTask 添加离线下载任务
|
||||
func (pcs *BaiduPCS) CloudDlAddTask(sourceURL, savePath string) (taskID int64, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareCloudDlAddTask(sourceURL, savePath)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationCloudDlAddTask)
|
||||
taskInfo := cloudDlAddTaskJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationCloudDlAddTask, dataReadCloser, &taskInfo)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return taskInfo.TaskID, nil
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) cloudDlQueryTask(op string, taskIDs []int64) (cl CloudDlTaskList, pcsError pcserror.Error) {
|
||||
errInfo := pcserror.NewPCSErrorInfo(op)
|
||||
if len(taskIDs) == 0 {
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
errInfo.Err = errors.New("no input any task_ids")
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// TODO: 支持100条以上的task_id查询
|
||||
if len(taskIDs) > 100 {
|
||||
taskIDs = taskIDs[:100]
|
||||
}
|
||||
|
||||
taskStrIDs := make([]string, len(taskIDs))
|
||||
for k := range taskStrIDs {
|
||||
taskStrIDs[k] = strconv.FormatInt(taskIDs[k], 10)
|
||||
}
|
||||
|
||||
dataReadCloser, pcsError := pcs.PrepareCloudDlQueryTask(strings.Join(taskStrIDs, ","))
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
taskInfo := cloudDlQueryTaskJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationCloudDlQueryTask, dataReadCloser, &taskInfo)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var v2 *CloudDlTaskInfo
|
||||
cl = make(CloudDlTaskList, 0, len(taskStrIDs))
|
||||
for k := range taskStrIDs {
|
||||
var err error
|
||||
v := taskInfo.TaskInfo[taskStrIDs[k]]
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v2 = v.convert()
|
||||
|
||||
v2.TaskID, err = strconv.ParseInt(taskStrIDs[k], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v2.ParseText()
|
||||
cl = append(cl, v2)
|
||||
}
|
||||
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// CloudDlQueryTask 精确查询离线下载任务
|
||||
func (pcs *BaiduPCS) CloudDlQueryTask(taskIDs []int64) (cl CloudDlTaskList, pcsError pcserror.Error) {
|
||||
return pcs.cloudDlQueryTask(OperationCloudDlQueryTask, taskIDs)
|
||||
}
|
||||
|
||||
// CloudDlListTask 查询离线下载任务列表
|
||||
func (pcs *BaiduPCS) CloudDlListTask() (cl CloudDlTaskList, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareCloudDlListTask()
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationCloudDlListTask)
|
||||
taskInfo := cloudDlListTaskJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationCloudDlListTask, dataReadCloser, &taskInfo)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 没有任务
|
||||
if len(taskInfo.TaskInfo) <= 0 {
|
||||
return CloudDlTaskList{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
taskID int64
|
||||
taskIDs = make([]int64, 0, len(taskInfo.TaskInfo))
|
||||
)
|
||||
for _, v := range taskInfo.TaskInfo {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
if taskID, err = strconv.ParseInt(v.TaskID, 10, 64); err == nil {
|
||||
taskIDs = append(taskIDs, taskID)
|
||||
}
|
||||
}
|
||||
|
||||
cl, pcsError = pcs.cloudDlQueryTask(OperationCloudDlListTask, taskIDs)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) cloudDlManipTask(op string, taskID int64) (pcsError pcserror.Error) {
|
||||
var dataReadCloser io.ReadCloser
|
||||
|
||||
switch op {
|
||||
case OperationCloudDlCancelTask:
|
||||
dataReadCloser, pcsError = pcs.PrepareCloudDlCancelTask(taskID)
|
||||
case OperationCloudDlDeleteTask:
|
||||
dataReadCloser, pcsError = pcs.PrepareCloudDlDeleteTask(taskID)
|
||||
default:
|
||||
panic("unknown op, " + op)
|
||||
}
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.DecodePCSJSONError(op, dataReadCloser)
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// CloudDlCancelTask 取消离线下载任务
|
||||
func (pcs *BaiduPCS) CloudDlCancelTask(taskID int64) (pcsError pcserror.Error) {
|
||||
return pcs.cloudDlManipTask(OperationCloudDlCancelTask, taskID)
|
||||
}
|
||||
|
||||
// CloudDlDeleteTask 删除离线下载任务
|
||||
func (pcs *BaiduPCS) CloudDlDeleteTask(taskID int64) (pcsError pcserror.Error) {
|
||||
return pcs.cloudDlManipTask(OperationCloudDlDeleteTask, taskID)
|
||||
}
|
||||
|
||||
// CloudDlClearTask 清空离线下载任务记录
|
||||
func (pcs *BaiduPCS) CloudDlClearTask() (total int, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareCloudDlClearTask()
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationCloudDlClearTask)
|
||||
clearInfo := cloudDlClearJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationCloudDlClearTask, dataReadCloser, &clearInfo)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return clearInfo.Total, nil
|
||||
}
|
||||
|
||||
// ParseText 解析状态码
|
||||
func (ci *CloudDlTaskInfo) ParseText() {
|
||||
switch ci.Status {
|
||||
case 0:
|
||||
ci.StatusText = "下载成功"
|
||||
case 1:
|
||||
ci.StatusText = "下载进行中"
|
||||
case 2:
|
||||
ci.StatusText = "系统错误"
|
||||
case 3:
|
||||
ci.StatusText = "资源不存在"
|
||||
case 4:
|
||||
ci.StatusText = "下载超时"
|
||||
case 5:
|
||||
ci.StatusText = "资源存在但下载失败"
|
||||
case 6:
|
||||
ci.StatusText = "存储空间不足"
|
||||
case 7:
|
||||
ci.StatusText = "任务取消"
|
||||
default:
|
||||
ci.StatusText = "未知状态码: " + strconv.Itoa(ci.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func (cl CloudDlTaskList) String() string {
|
||||
builder := &strings.Builder{}
|
||||
tb := pcstable.NewTable(builder)
|
||||
tb.SetHeader([]string{"#", "任务ID", "任务名称", "文件大小", "创建日期", "保存路径", "资源地址", "状态"})
|
||||
for k, v := range cl {
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(v.TaskID, 10), v.TaskName, converter.ConvertFileSize(v.FileSize), pcstime.FormatTime(v.CreateTime), path.Clean(v.SavePath), v.SourceURL, v.StatusText})
|
||||
}
|
||||
tb.Render()
|
||||
return builder.String()
|
||||
}
|
||||
42
baidupcs/cp_mv_rename.go
Normal file
42
baidupcs/cp_mv_rename.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Rename 重命名文件/目录
|
||||
func (pcs *BaiduPCS) Rename(from, to string) (pcsError pcserror.Error) {
|
||||
return pcs.cpmvOp(OperationRename, &CpMvJSON{
|
||||
From: from,
|
||||
To: to,
|
||||
})
|
||||
}
|
||||
|
||||
// Copy 批量拷贝文件/目录
|
||||
func (pcs *BaiduPCS) Copy(cpmvJSON ...*CpMvJSON) (pcsError pcserror.Error) {
|
||||
return pcs.cpmvOp(OperationCopy, cpmvJSON...)
|
||||
}
|
||||
|
||||
// Move 批量移动文件/目录
|
||||
func (pcs *BaiduPCS) Move(cpmvJSON ...*CpMvJSON) (pcsError pcserror.Error) {
|
||||
return pcs.cpmvOp(OperationMove, cpmvJSON...)
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) cpmvOp(op string, cpmvJSON ...*CpMvJSON) (pcsError pcserror.Error) {
|
||||
dataReadCloser, err := pcs.prepareCpMvOp(op, cpmvJSON...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.DecodePCSJSONError(op, dataReadCloser)
|
||||
if errInfo != nil {
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache((*CpMvJSONList)(unsafe.Pointer(&cpmvJSON)).AllRelatedDir())
|
||||
return nil
|
||||
}
|
||||
169
baidupcs/download.go
Normal file
169
baidupcs/download.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLocateDownloadURLNotFound 未找到下载链接
|
||||
ErrLocateDownloadURLNotFound = errors.New("locatedownload url not found")
|
||||
// MaxDownloadRangeSize 文件片段最大值
|
||||
MaxDownloadRangeSize = 55 * converter.MB
|
||||
)
|
||||
|
||||
type (
|
||||
// DownloadFunc 下载文件处理函数
|
||||
DownloadFunc func(downloadURL string, jar http.CookieJar) error
|
||||
|
||||
// URLInfo 下载链接详情
|
||||
URLInfo struct {
|
||||
URLs []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"urls"`
|
||||
}
|
||||
|
||||
// LocateDownloadInfoV1 locatedownload api v1
|
||||
LocateDownloadInfoV1 struct {
|
||||
Server []string `json:"server"`
|
||||
PathJSON
|
||||
}
|
||||
|
||||
locateDownloadJSON struct {
|
||||
*pcserror.PCSErrInfo
|
||||
URLInfo
|
||||
}
|
||||
|
||||
// APIDownloadDlinkInfo 下载信息
|
||||
APIDownloadDlinkInfo struct {
|
||||
Dlink string `json:"dlink"`
|
||||
FsID string `json:"fs_id"`
|
||||
}
|
||||
|
||||
// APIDownloadDlinkInfoList 下载信息列表
|
||||
APIDownloadDlinkInfoList []*APIDownloadDlinkInfo
|
||||
|
||||
panAPIDownloadJSON struct {
|
||||
*pcserror.PanErrorInfo
|
||||
DlinkList APIDownloadDlinkInfoList `json:"dlink"`
|
||||
}
|
||||
)
|
||||
|
||||
// URLStrings 返回下载链接数组
|
||||
func (ui *URLInfo) URLStrings(https bool) (urls []*url.URL) {
|
||||
urls = make([]*url.URL, 0, len(ui.URLs))
|
||||
for k := range ui.URLs {
|
||||
thisURL, err := url.Parse(ui.URLs[k].URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
thisURL.Scheme = GetHTTPScheme(https)
|
||||
urls = append(urls, thisURL)
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
// SingleURL 返回单条下载链接
|
||||
func (ui *URLInfo) SingleURL(https bool) *url.URL {
|
||||
if len(ui.URLs) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(ui.URLs[0].URL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
u.Scheme = GetHTTPScheme(https)
|
||||
return u
|
||||
}
|
||||
|
||||
// LastURL 返回最后一条下载链接
|
||||
func (ui *URLInfo) LastURL(https bool) *url.URL {
|
||||
if len(ui.URLs) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(ui.URLs[len(ui.URLs)-1].URL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
u.Scheme = GetHTTPScheme(https)
|
||||
return u
|
||||
}
|
||||
|
||||
// DownloadFile 下载单个文件
|
||||
func (pcs *BaiduPCS) DownloadFile(path string, downloadFunc DownloadFunc) (err error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("file", "download", map[string]string{
|
||||
"path": path,
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationDownloadFile, pcsURL)
|
||||
|
||||
return downloadFunc(pcsURL.String(), pcs.client.Jar)
|
||||
}
|
||||
|
||||
// DownloadStreamFile 下载流式文件
|
||||
func (pcs *BaiduPCS) DownloadStreamFile(path string, downloadFunc DownloadFunc) (err error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("stream", "download", map[string]string{
|
||||
"path": path,
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationDownloadStreamFile, pcsURL)
|
||||
|
||||
return downloadFunc(pcsURL.String(), pcs.client.Jar)
|
||||
}
|
||||
|
||||
// LocateDownloadWithUserAgent 获取下载链接
|
||||
func (pcs *BaiduPCS) LocateDownload(pcspath string) (info *URLInfo, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareLocateDownload(pcspath)
|
||||
if dataReadCloser != nil {
|
||||
defer dataReadCloser.Close()
|
||||
}
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationLocateDownload)
|
||||
jsonData := locateDownloadJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationLocateDownload, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return &jsonData.URLInfo, nil
|
||||
}
|
||||
|
||||
// LocatePanAPIDownload 从百度网盘首页获取下载链接
|
||||
func (pcs *BaiduPCS) LocatePanAPIDownload(fidList ...int64) (dlinkInfoList APIDownloadDlinkInfoList, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareLocatePanAPIDownload(fidList...)
|
||||
if dataReadCloser != nil {
|
||||
defer dataReadCloser.Close()
|
||||
}
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
jsonData := panAPIDownloadJSON{
|
||||
PanErrorInfo: pcserror.NewPanErrorInfo(OperationLocatePanAPIDownload),
|
||||
}
|
||||
pcsError = pcserror.HandleJSONParse(OperationLocatePanAPIDownload, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
if pcsError.GetErrType() == pcserror.ErrTypeRemoteError {
|
||||
switch pcsError.GetRemoteErrCode() {
|
||||
case 112: // 页面已过期
|
||||
fallthrough
|
||||
case 113: // 签名错误
|
||||
pcs.ph.SetSignExpires() // 重置
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return jsonData.DlinkList, nil
|
||||
}
|
||||
43
baidupcs/expires/cachemap/cachemap.go
Normal file
43
baidupcs/expires/cachemap/cachemap.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package cachemap
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
GlobalCacheOpMap = CacheOpMap{}
|
||||
)
|
||||
|
||||
type (
|
||||
CacheOpMap struct {
|
||||
cachePool sync.Map
|
||||
}
|
||||
)
|
||||
|
||||
func (cm *CacheOpMap) LazyInitCachePoolOp(op string) CacheUnit {
|
||||
cacheItf, _ := cm.cachePool.LoadOrStore(op, &cacheUnit{})
|
||||
return cacheItf.(CacheUnit)
|
||||
}
|
||||
|
||||
func (cm *CacheOpMap) RemoveCachePoolOp(op string) {
|
||||
cm.cachePool.Delete(op)
|
||||
}
|
||||
|
||||
// ClearInvalidate 清除已过期的数据(一般用不到)
|
||||
func (cm *CacheOpMap) ClearInvalidate() {
|
||||
cm.cachePool.Range(func(_, cacheItf interface{}) bool {
|
||||
cache := cacheItf.(CacheUnit)
|
||||
cache.Range(func(key interface{}, exp expires.DataExpires) bool {
|
||||
if exp.IsExpires() {
|
||||
cache.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// PrintAll 输出所有缓冲项目
|
||||
func (cm *CacheOpMap) PrintAll() {
|
||||
}
|
||||
80
baidupcs/expires/cachemap/cachemap_test.go
Normal file
80
baidupcs/expires/cachemap/cachemap_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package cachemap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires/cachemap"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCacheMapDataExpires(t *testing.T) {
|
||||
cm := cachemap.CacheOpMap{}
|
||||
cache := cm.LazyInitCachePoolOp("op")
|
||||
cache.Store("key_1", expires.NewDataExpires("value_1", 1*time.Second))
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
data, ok := cache.Load("key_1")
|
||||
if ok {
|
||||
fmt.Printf("data: %s\n", data.Data())
|
||||
// 超时仍能读取到数据, 失败
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheOperation(t *testing.T) {
|
||||
cm := cachemap.CacheOpMap{}
|
||||
data := cm.CacheOperation("op", "key_1", func() expires.DataExpires {
|
||||
return expires.NewDataExpires("value_1", 1*time.Second)
|
||||
})
|
||||
fmt.Printf("data: %s\n", data.Data())
|
||||
|
||||
newData := cm.CacheOperation("op", "key_1", func() expires.DataExpires {
|
||||
return expires.NewDataExpires("value_3", 1*time.Second)
|
||||
})
|
||||
if data != newData {
|
||||
t.FailNow()
|
||||
}
|
||||
fmt.Printf("data: %s\n", data.Data())
|
||||
}
|
||||
|
||||
func TestCacheOperation_LockKey(t *testing.T) {
|
||||
cm := cachemap.CacheOpMap{}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(5000)
|
||||
|
||||
var (
|
||||
execTimes1 int32 = 0 // 执行次数1
|
||||
execTimes2 int32 = 0 // 执行次数2
|
||||
)
|
||||
|
||||
for i := 0; i < 5000; i++ {
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
cm.CacheOperation("op", "key_1", func() expires.DataExpires {
|
||||
time.Sleep(50 * time.Microsecond) // 一些耗时的操作
|
||||
atomic.AddInt32(&execTimes1, 1)
|
||||
return expires.NewDataExpires(fmt.Sprintf("value_1: %d", i), 10*time.Second)
|
||||
})
|
||||
|
||||
cm.CacheOperation("op", "key_2", func() expires.DataExpires {
|
||||
time.Sleep(50 * time.Microsecond) // 一些耗时的操作
|
||||
atomic.AddInt32(&execTimes2, 1)
|
||||
return expires.NewDataExpires(fmt.Sprintf("value_2: %d", i), 10*time.Second)
|
||||
})
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 执行次数应为1
|
||||
if execTimes1 != 1 {
|
||||
fmt.Printf("execTimes1: %d\n", execTimes1)
|
||||
t.FailNow()
|
||||
}
|
||||
if execTimes2 != 1 {
|
||||
fmt.Printf("execTimes2: %d\n", execTimes2)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
81
baidupcs/expires/cachemap/cacheunit.go
Normal file
81
baidupcs/expires/cachemap/cacheunit.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package cachemap
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type (
|
||||
CacheUnit interface {
|
||||
Delete(key interface{})
|
||||
Load(key interface{}) (value expires.DataExpires, ok bool)
|
||||
LoadOrStore(key interface{}, value expires.DataExpires) (actual expires.DataExpires, loaded bool)
|
||||
Range(f func(key interface{}, value expires.DataExpires) bool)
|
||||
Store(key interface{}, value expires.DataExpires)
|
||||
LockKey(key interface{})
|
||||
UnlockKey(key interface{})
|
||||
}
|
||||
|
||||
cacheUnit struct {
|
||||
unit sync.Map
|
||||
keyMap sync.Map
|
||||
}
|
||||
)
|
||||
|
||||
func (cu *cacheUnit) Delete(key interface{}) {
|
||||
cu.unit.Delete(key)
|
||||
cu.keyMap.Delete(key)
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) Load(key interface{}) (value expires.DataExpires, ok bool) {
|
||||
val, ok := cu.unit.Load(key)
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
exp := val.(expires.DataExpires)
|
||||
if exp.IsExpires() {
|
||||
cu.unit.Delete(key)
|
||||
return nil, false
|
||||
}
|
||||
return exp, ok
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) Range(f func(key interface{}, value expires.DataExpires) bool) {
|
||||
cu.unit.Range(func(k, val interface{}) bool {
|
||||
exp := val.(expires.DataExpires)
|
||||
if exp.IsExpires() {
|
||||
cu.unit.Delete(k)
|
||||
return true
|
||||
}
|
||||
return f(k, val.(expires.DataExpires))
|
||||
})
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) LoadOrStore(key interface{}, value expires.DataExpires) (actual expires.DataExpires, loaded bool) {
|
||||
ac, loaded := cu.unit.LoadOrStore(key, value)
|
||||
exp := ac.(expires.DataExpires)
|
||||
if exp.IsExpires() {
|
||||
cu.unit.Delete(key)
|
||||
return nil, false
|
||||
}
|
||||
return exp, loaded
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) Store(key interface{}, value expires.DataExpires) {
|
||||
if value.IsExpires() {
|
||||
return
|
||||
}
|
||||
cu.unit.Store(key, value)
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) LockKey(key interface{}) {
|
||||
muItf, _ := cu.keyMap.LoadOrStore(key, &sync.Mutex{})
|
||||
mu := muItf.(*sync.Mutex)
|
||||
mu.Lock()
|
||||
}
|
||||
|
||||
func (cu *cacheUnit) UnlockKey(key interface{}) {
|
||||
muItf, _ := cu.keyMap.LoadOrStore(key, &sync.Mutex{})
|
||||
mu := muItf.(*sync.Mutex)
|
||||
mu.Unlock()
|
||||
}
|
||||
54
baidupcs/expires/cachemap/utils.go
Normal file
54
baidupcs/expires/cachemap/utils.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package cachemap
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
)
|
||||
|
||||
type (
|
||||
OpFunc func() expires.DataExpires
|
||||
OpFuncWithError func() (expires.DataExpires, error)
|
||||
)
|
||||
|
||||
func (cm *CacheOpMap) CacheOperation(op string, key interface{}, opFunc OpFunc) (data expires.DataExpires) {
|
||||
var (
|
||||
cache = cm.LazyInitCachePoolOp(op)
|
||||
ok bool
|
||||
)
|
||||
|
||||
cache.LockKey(key)
|
||||
defer cache.UnlockKey(key)
|
||||
data, ok = cache.Load(key)
|
||||
if !ok {
|
||||
data = opFunc()
|
||||
if data != nil {
|
||||
cache.Store(key, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cm *CacheOpMap) CacheOperationWithError(op string, key interface{}, opFunc OpFuncWithError) (data expires.DataExpires, err error) {
|
||||
var (
|
||||
cache = cm.LazyInitCachePoolOp(op)
|
||||
ok bool
|
||||
)
|
||||
|
||||
cache.LockKey(key)
|
||||
defer cache.UnlockKey(key)
|
||||
data, ok = cache.Load(key)
|
||||
if !ok {
|
||||
data, err = opFunc()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if data == nil {
|
||||
// 数据为空时也不存
|
||||
return
|
||||
}
|
||||
cache.Store(key, data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
28
baidupcs/expires/dataexpires.go
Normal file
28
baidupcs/expires/dataexpires.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package expires
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
DataExpires interface {
|
||||
Data() interface{}
|
||||
Expires
|
||||
}
|
||||
|
||||
dataExpires struct {
|
||||
data interface{}
|
||||
Expires
|
||||
}
|
||||
)
|
||||
|
||||
func NewDataExpires(data interface{}, dur time.Duration) DataExpires {
|
||||
return &dataExpires{
|
||||
data: data,
|
||||
Expires: NewExpires(dur),
|
||||
}
|
||||
}
|
||||
|
||||
func (de *dataExpires) Data() interface{} {
|
||||
return de.data
|
||||
}
|
||||
60
baidupcs/expires/expires.go
Normal file
60
baidupcs/expires/expires.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package expires
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
type (
|
||||
Expires interface {
|
||||
IsExpires() bool
|
||||
GetExpires() time.Time
|
||||
SetExpires(e bool)
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
expires struct {
|
||||
expiresAt time.Time
|
||||
abort bool
|
||||
}
|
||||
)
|
||||
|
||||
//go:linkname stripMono time.(*Time).stripMono
|
||||
func stripMono(t *time.Time)
|
||||
|
||||
// StripMono strip monotonic clocks
|
||||
func StripMono(t *time.Time) {
|
||||
stripMono(t)
|
||||
}
|
||||
|
||||
func NewExpires(dur time.Duration) Expires {
|
||||
t := time.Now().Add(dur)
|
||||
StripMono(&t)
|
||||
return &expires{
|
||||
expiresAt: t,
|
||||
}
|
||||
}
|
||||
|
||||
func NewExpiresAt(at time.Time) Expires {
|
||||
StripMono(&at)
|
||||
return &expires{
|
||||
expiresAt: at,
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *expires) GetExpires() time.Time {
|
||||
return ep.expiresAt
|
||||
}
|
||||
|
||||
func (ep *expires) SetExpires(e bool) {
|
||||
ep.abort = e
|
||||
}
|
||||
|
||||
func (ep *expires) IsExpires() bool {
|
||||
return ep.abort || time.Now().After(ep.expiresAt)
|
||||
}
|
||||
|
||||
func (ep *expires) String() string {
|
||||
return fmt.Sprintf("expires at: %s, abort: %t", ep.expiresAt, ep.abort)
|
||||
}
|
||||
0
baidupcs/expires/expires.s
Normal file
0
baidupcs/expires/expires.s
Normal file
362
baidupcs/extends.go
Normal file
362
baidupcs/extends.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/cachepool"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/escaper"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/downloader"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ShellPatternCharacters 通配符字符串
|
||||
ShellPatternCharacters = "*?[]"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFixMD5Isdir 目录不需要修复md5
|
||||
ErrFixMD5Isdir = errors.New("directory not support fix md5")
|
||||
// ErrFixMD5Failed 修复MD5失败, 可能服务器未刷新
|
||||
ErrFixMD5Failed = errors.New("fix md5 failed")
|
||||
// ErrFixMD5FileInfoNil 文件信息对象为空
|
||||
ErrFixMD5FileInfoNil = errors.New("file info is nil")
|
||||
// ErrMatchPathByShellPatternNotAbsPath 不是绝对路径
|
||||
ErrMatchPathByShellPatternNotAbsPath = errors.New("not absolute path")
|
||||
|
||||
ErrContentRangeNotFound = errors.New("Content-Range not found")
|
||||
ErrGetRapidUploadInfoLengthNotFound = errors.New("Content-Length not found")
|
||||
ErrGetRapidUploadInfoMD5NotFound = errors.New("Content-MD5 not found")
|
||||
ErrGetRapidUploadInfoCrc32NotFound = errors.New("x-bs-meta-crc32 not found")
|
||||
ErrGetRapidUploadInfoFilenameNotEqual = errors.New("文件名不匹配")
|
||||
ErrGetRapidUploadInfoLengthNotEqual = errors.New("Content-Length 不匹配")
|
||||
ErrGetRapidUploadInfoMD5NotEqual = errors.New("Content-MD5 不匹配")
|
||||
ErrGetRapidUploadInfoCrc32NotEqual = errors.New("x-bs-meta-crc32 不匹配")
|
||||
ErrGetRapidUploadInfoSliceMD5NotEqual = errors.New("slice-md5 不匹配")
|
||||
|
||||
ErrFileTooLarge = errors.New("文件大于20GB, 无法秒传")
|
||||
)
|
||||
|
||||
func (pcs *BaiduPCS) getLocateDownloadLink(pcspath string) (link string, pcsError pcserror.Error) {
|
||||
info, pcsError := pcs.LocateDownload(pcspath)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
u := info.SingleURL(pcs.isHTTPS)
|
||||
if u == nil {
|
||||
return "", &pcserror.PCSErrInfo{
|
||||
Operation: OperationLocateDownload,
|
||||
ErrType: pcserror.ErrTypeOthers,
|
||||
Err: ErrLocateDownloadURLNotFound,
|
||||
}
|
||||
}
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// ExportByFileInfo 通过文件信息对象, 导出文件信息
|
||||
func (pcs *BaiduPCS) ExportByFileInfo(finfo *FileDirectory) (rinfo *RapidUploadInfo, pcsError pcserror.Error) {
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationExportFileInfo)
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
if finfo.Size > MaxRapidUploadSize {
|
||||
errInfo.Err = ErrFileTooLarge
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
rinfo, pcsError = pcs.GetRapidUploadInfoByFileInfo(finfo)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
if rinfo.Filename != finfo.Filename {
|
||||
baiduPCSVerbose.Infof("%s filename not equal, local: %s, remote link: %s\n", OperationExportFileInfo, finfo.Filename, rinfo.Filename)
|
||||
rinfo.Filename = finfo.Filename
|
||||
}
|
||||
return rinfo, nil
|
||||
}
|
||||
|
||||
// GetRapidUploadInfoByFileInfo 通过文件信息对象, 获取秒传信息
|
||||
func (pcs *BaiduPCS) GetRapidUploadInfoByFileInfo(finfo *FileDirectory) (rinfo *RapidUploadInfo, pcsError pcserror.Error) {
|
||||
if finfo.Size <= SliceMD5Size && len(finfo.BlockList) == 1 && finfo.BlockList[0] == finfo.MD5 {
|
||||
// 可直接秒传
|
||||
return &RapidUploadInfo{
|
||||
Filename: finfo.Filename,
|
||||
ContentLength: finfo.Size,
|
||||
ContentMD5: finfo.MD5,
|
||||
SliceMD5: finfo.MD5,
|
||||
ContentCrc32: "0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
link, pcsError := pcs.getLocateDownloadLink(finfo.Path)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
// 只有ContentLength可以比较
|
||||
// finfo记录的ContentMD5不一定是正确的
|
||||
// finfo记录的Filename不一定与获取到的一致
|
||||
return pcs.GetRapidUploadInfoByLink(link, &RapidUploadInfo{
|
||||
ContentLength: finfo.Size,
|
||||
})
|
||||
}
|
||||
|
||||
// GetRapidUploadInfoByLink 通过下载链接, 获取文件秒传信息
|
||||
func (pcs *BaiduPCS) GetRapidUploadInfoByLink(link string, compareRInfo *RapidUploadInfo) (rinfo *RapidUploadInfo, pcsError pcserror.Error) {
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationGetRapidUploadInfo)
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
|
||||
var (
|
||||
header = pcs.getPanUAHeader()
|
||||
isSetRange = compareRInfo != nil && compareRInfo.ContentLength > SliceMD5Size // 是否设置Range
|
||||
)
|
||||
if isSetRange {
|
||||
header["Range"] = "bytes=0-" + strconv.FormatInt(SliceMD5Size-1, 10)
|
||||
}
|
||||
|
||||
resp, err := pcs.client.Req(http.MethodGet, link, nil, header)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
errInfo.SetNetError(err)
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// 检测响应状态码
|
||||
if resp.StatusCode/100 != 2 {
|
||||
errInfo.SetNetError(errors.New(resp.Status))
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// 检测是否存在MD5
|
||||
md5Str := resp.Header.Get("Content-MD5")
|
||||
if md5Str == "" { // 未找到md5值, 可能是服务器未刷新
|
||||
errInfo.Err = ErrGetRapidUploadInfoMD5NotFound
|
||||
return nil, errInfo
|
||||
}
|
||||
if compareRInfo != nil && compareRInfo.ContentMD5 != "" && compareRInfo.ContentMD5 != md5Str {
|
||||
errInfo.Err = ErrGetRapidUploadInfoMD5NotEqual
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// 获取文件名
|
||||
_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
|
||||
if err != nil {
|
||||
errInfo.Err = err
|
||||
return nil, errInfo
|
||||
}
|
||||
filename, err := url.QueryUnescape(params["filename"])
|
||||
if err != nil {
|
||||
errInfo.Err = err
|
||||
return nil, errInfo
|
||||
}
|
||||
if compareRInfo != nil && compareRInfo.Filename != "" && compareRInfo.Filename != filename {
|
||||
errInfo.Err = ErrGetRapidUploadInfoFilenameNotEqual
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
var (
|
||||
contentLength int64
|
||||
)
|
||||
if isSetRange {
|
||||
// 检测Content-Range
|
||||
contentRange := resp.Header.Get("Content-Range")
|
||||
if contentRange == "" {
|
||||
errInfo.Err = ErrContentRangeNotFound
|
||||
return nil, errInfo
|
||||
}
|
||||
contentLength = downloader.ParseContentRange(contentRange)
|
||||
} else {
|
||||
contentLength = resp.ContentLength
|
||||
}
|
||||
|
||||
// 检测Content-Length
|
||||
switch contentLength {
|
||||
case -1:
|
||||
errInfo.Err = ErrGetRapidUploadInfoLengthNotFound
|
||||
return nil, errInfo
|
||||
case 0:
|
||||
return &RapidUploadInfo{
|
||||
Filename: filename,
|
||||
ContentLength: contentLength,
|
||||
ContentMD5: EmptyContentMD5,
|
||||
SliceMD5: EmptyContentMD5,
|
||||
ContentCrc32: "0",
|
||||
}, nil
|
||||
default:
|
||||
if compareRInfo != nil && compareRInfo.ContentLength > 0 && compareRInfo.ContentLength != contentLength {
|
||||
errInfo.Err = ErrGetRapidUploadInfoLengthNotEqual
|
||||
return nil, errInfo
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否存在crc32 值, 一般都会存在的
|
||||
crc32Str := resp.Header.Get("x-bs-meta-crc32")
|
||||
if crc32Str == "" || crc32Str == "0" {
|
||||
errInfo.Err = ErrGetRapidUploadInfoCrc32NotFound
|
||||
return nil, errInfo
|
||||
}
|
||||
if compareRInfo != nil && compareRInfo.ContentCrc32 != "" && compareRInfo.ContentCrc32 != crc32Str {
|
||||
errInfo.Err = ErrGetRapidUploadInfoCrc32NotEqual
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// 获取slice-md5
|
||||
// 忽略比较slice-md5
|
||||
if contentLength <= SliceMD5Size {
|
||||
return &RapidUploadInfo{
|
||||
Filename: filename,
|
||||
ContentLength: contentLength,
|
||||
ContentMD5: md5Str,
|
||||
SliceMD5: md5Str,
|
||||
ContentCrc32: crc32Str,
|
||||
}, nil
|
||||
}
|
||||
|
||||
buf := cachepool.RawMallocByteSlice(int(SliceMD5Size))
|
||||
_, err = io.ReadFull(resp.Body, buf)
|
||||
if err != nil {
|
||||
errInfo.SetNetError(err)
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
// 计算slice-md5
|
||||
m := md5.New()
|
||||
_, err = m.Write(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sliceMD5Str := hex.EncodeToString(m.Sum(nil))
|
||||
|
||||
// 检测slice-md5, 不必要的
|
||||
if compareRInfo != nil && compareRInfo.SliceMD5 != "" && compareRInfo.SliceMD5 != sliceMD5Str {
|
||||
errInfo.Err = ErrGetRapidUploadInfoSliceMD5NotEqual
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
return &RapidUploadInfo{
|
||||
Filename: filename,
|
||||
ContentLength: contentLength,
|
||||
ContentMD5: md5Str,
|
||||
SliceMD5: sliceMD5Str,
|
||||
ContentCrc32: crc32Str,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FixMD5ByFileInfo 尝试修复文件的md5, 通过文件信息对象
|
||||
func (pcs *BaiduPCS) FixMD5ByFileInfo(finfo *FileDirectory) (pcsError pcserror.Error) {
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationFixMD5)
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
if finfo == nil {
|
||||
errInfo.Err = ErrFixMD5FileInfoNil
|
||||
return errInfo
|
||||
}
|
||||
|
||||
if finfo.Size > MaxRapidUploadSize { // 文件大于20GB
|
||||
errInfo.Err = ErrFileTooLarge
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 忽略目录
|
||||
if finfo.Isdir {
|
||||
errInfo.Err = ErrFixMD5Isdir
|
||||
return errInfo
|
||||
}
|
||||
|
||||
if len(finfo.BlockList) == 1 && finfo.BlockList[0] == finfo.MD5 {
|
||||
// 不需要修复
|
||||
return nil
|
||||
}
|
||||
|
||||
link, pcsError := pcs.getLocateDownloadLink(finfo.Path)
|
||||
if pcsError != nil {
|
||||
return pcsError
|
||||
}
|
||||
|
||||
var (
|
||||
cmpInfo = &RapidUploadInfo{
|
||||
Filename: finfo.Filename,
|
||||
ContentLength: finfo.Size,
|
||||
}
|
||||
)
|
||||
rinfo, pcsError := pcs.GetRapidUploadInfoByLink(link, cmpInfo)
|
||||
if pcsError != nil {
|
||||
switch pcsError.GetError() {
|
||||
case ErrGetRapidUploadInfoMD5NotFound, ErrGetRapidUploadInfoCrc32NotFound:
|
||||
errInfo.Err = ErrFixMD5Failed
|
||||
default:
|
||||
errInfo.Err = pcsError
|
||||
}
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 开始修复
|
||||
return pcs.RapidUploadNoCheckDir(finfo.Path, rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentCrc32, rinfo.ContentLength)
|
||||
}
|
||||
|
||||
// FixMD5 尝试修复文件的md5
|
||||
func (pcs *BaiduPCS) FixMD5(pcspath string) (pcsError pcserror.Error) {
|
||||
finfo, pcsError := pcs.FilesDirectoriesMeta(pcspath)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return pcs.FixMD5ByFileInfo(finfo)
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) recurseMatchPathByShellPattern(index int, patternSlice *[]string, ps *[]string, pcspaths *[]string) {
|
||||
if index == len(*patternSlice) {
|
||||
*pcspaths = append(*pcspaths, strings.Join(*ps, PathSeparator))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.ContainsAny((*patternSlice)[index], ShellPatternCharacters) {
|
||||
(*ps)[index] = (*patternSlice)[index]
|
||||
pcs.recurseMatchPathByShellPattern(index+1, patternSlice, ps, pcspaths)
|
||||
return
|
||||
}
|
||||
|
||||
fds, pcsError := pcs.FilesDirectoriesList(strings.Join((*ps)[:index], PathSeparator), DefaultOrderOptions)
|
||||
if pcsError != nil {
|
||||
panic(pcsError) // 抛出异常
|
||||
}
|
||||
|
||||
for k := range fds {
|
||||
if matched, _ := path.Match((*patternSlice)[index], fds[k].Filename); matched {
|
||||
(*ps)[index] = fds[k].Filename
|
||||
pcs.recurseMatchPathByShellPattern(index+1, patternSlice, ps, pcspaths)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MatchPathByShellPattern 通配符匹配文件路径, pattern 为绝对路径
|
||||
func (pcs *BaiduPCS) MatchPathByShellPattern(pattern string) (pcspaths []string, pcsError pcserror.Error) {
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperrationMatchPathByShellPattern)
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
|
||||
patternSlice := strings.Split(escaper.Escape(path.Clean(pattern), []rune{'['}), PathSeparator) // 转义中括号
|
||||
if patternSlice[0] != "" {
|
||||
errInfo.Err = ErrMatchPathByShellPatternNotAbsPath
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
ps := make([]string, len(patternSlice))
|
||||
defer func() { // 捕获异常
|
||||
if err := recover(); err != nil {
|
||||
pcspaths = nil
|
||||
pcsError = err.(pcserror.Error)
|
||||
}
|
||||
}()
|
||||
pcs.recurseMatchPathByShellPattern(1, &patternSlice, &ps, &pcspaths)
|
||||
return pcspaths, nil
|
||||
}
|
||||
370
baidupcs/file_directory.go
Normal file
370
baidupcs/file_directory.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/pcstime"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
// OrderBy 排序字段
|
||||
OrderBy string
|
||||
// Order 升序降序
|
||||
Order string
|
||||
)
|
||||
|
||||
const (
|
||||
// OrderByName 根据文件名排序
|
||||
OrderByName OrderBy = "name"
|
||||
// OrderByTime 根据时间排序
|
||||
OrderByTime OrderBy = "time"
|
||||
// OrderBySize 根据大小排序, 注意目录无大小
|
||||
OrderBySize OrderBy = "size"
|
||||
// OrderAsc 升序
|
||||
OrderAsc Order = "asc"
|
||||
// OrderDesc 降序
|
||||
OrderDesc Order = "desc"
|
||||
)
|
||||
|
||||
type (
|
||||
// HandleFileDirectoryFunc 处理文件或目录的元信息, 返回值控制是否退出递归
|
||||
HandleFileDirectoryFunc func(depth int, fdPath string, fd *FileDirectory, pcsError pcserror.Error) bool
|
||||
|
||||
// FileDirectory 文件或目录的元信息
|
||||
FileDirectory struct {
|
||||
FsID int64 // fs_id
|
||||
AppID int64 // app_id
|
||||
Path string // 路径
|
||||
Filename string // 文件名 或 目录名
|
||||
Ctime int64 // 创建日期
|
||||
Mtime int64 // 修改日期
|
||||
MD5 string // md5 值
|
||||
BlockListJSON
|
||||
Size int64 // 文件大小 (目录为0)
|
||||
Isdir bool // 是否为目录
|
||||
Ifhassubdir bool // 是否含有子目录 (只对目录有效)
|
||||
|
||||
Parent *FileDirectory // 父目录信息
|
||||
Children FileDirectoryList // 子目录信息
|
||||
}
|
||||
|
||||
// FileDirectoryList FileDirectory 的 指针数组
|
||||
FileDirectoryList []*FileDirectory
|
||||
|
||||
// fdJSON 用于解析远程JSON数据
|
||||
fdJSON struct {
|
||||
FsID int64 `json:"fs_id"` // fs_id
|
||||
AppID int64 `json:"app_id"`
|
||||
Path string `json:"path"` // 路径
|
||||
Filename string `json:"server_filename"` // 文件名 或 目录名
|
||||
Ctime int64 `json:"ctime"` // 创建日期
|
||||
Mtime int64 `json:"mtime"` // 修改日期
|
||||
MD5 string `json:"md5"` // md5 值
|
||||
BlockListJSON
|
||||
Size int64 `json:"size"` // 文件大小 (目录为0)
|
||||
IsdirInt int8 `json:"isdir"`
|
||||
IfhassubdirInt int8 `json:"ifhassubdir"`
|
||||
|
||||
// 对齐
|
||||
_ *fdJSON
|
||||
_ []*fdJSON
|
||||
}
|
||||
|
||||
fdData struct {
|
||||
*pcserror.PCSErrInfo
|
||||
List FileDirectoryList
|
||||
}
|
||||
|
||||
fdDataJSONExport struct {
|
||||
*pcserror.PCSErrInfo
|
||||
List []*fdJSON `json:"list"`
|
||||
}
|
||||
|
||||
// OrderOptions 列文件/目录可选项
|
||||
OrderOptions struct {
|
||||
By OrderBy
|
||||
Order Order
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultOrderOptions 默认的排序
|
||||
DefaultOrderOptions = &OrderOptions{
|
||||
By: OrderByName,
|
||||
Order: OrderAsc,
|
||||
}
|
||||
|
||||
defaultOrderOptionsStr = fmt.Sprint(DefaultOrderOptions)
|
||||
)
|
||||
|
||||
// FilesDirectoriesMeta 获取单个文件/目录的元信息
|
||||
func (pcs *BaiduPCS) FilesDirectoriesMeta(path string) (data *FileDirectory, pcsError pcserror.Error) {
|
||||
if path == "" {
|
||||
path = PathSeparator
|
||||
}
|
||||
|
||||
fds, err := pcs.FilesDirectoriesBatchMeta(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 返回了多条元信息
|
||||
if len(fds) != 1 {
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: OperationFilesDirectoriesMeta,
|
||||
ErrType: pcserror.ErrTypeOthers,
|
||||
Err: errors.New("未知返回数据"),
|
||||
}
|
||||
}
|
||||
|
||||
return fds[0], nil
|
||||
}
|
||||
|
||||
// FilesDirectoriesBatchMeta 获取多个文件/目录的元信息
|
||||
func (pcs *BaiduPCS) FilesDirectoriesBatchMeta(paths ...string) (data FileDirectoryList, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareFilesDirectoriesBatchMeta(paths...)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationFilesDirectoriesMeta)
|
||||
// 服务器返回数据进行处理
|
||||
jsonData := fdData{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationFilesDirectoriesMeta, dataReadCloser, (*fdDataJSONExport)(unsafe.Pointer(&jsonData)))
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 修复MD5
|
||||
jsonData.List.fixMD5()
|
||||
|
||||
data = jsonData.List
|
||||
return
|
||||
}
|
||||
|
||||
// FilesDirectoriesList 获取目录下的文件和目录列表
|
||||
func (pcs *BaiduPCS) FilesDirectoriesList(path string, options *OrderOptions) (data FileDirectoryList, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareFilesDirectoriesList(path, options)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
jsonData := fdData{
|
||||
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationFilesDirectoriesList),
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationFilesDirectoriesList, dataReadCloser, (*fdDataJSONExport)(unsafe.Pointer(&jsonData)))
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
// 修复MD5
|
||||
jsonData.List.fixMD5()
|
||||
|
||||
data = jsonData.List
|
||||
return
|
||||
}
|
||||
|
||||
// Search 按文件名搜索文件, 不支持查找目录
|
||||
func (pcs *BaiduPCS) Search(targetPath, keyword string, recursive bool) (fdl FileDirectoryList, pcsError pcserror.Error) {
|
||||
if targetPath == "" {
|
||||
targetPath = PathSeparator
|
||||
}
|
||||
|
||||
dataReadCloser, pcsError := pcs.PrepareSearch(targetPath, keyword, recursive)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationSearch)
|
||||
jsonData := fdData{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationSearch, dataReadCloser, (*fdDataJSONExport)(unsafe.Pointer(&jsonData)))
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 修复MD5
|
||||
jsonData.List.fixMD5()
|
||||
|
||||
fdl = jsonData.List
|
||||
return
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) recurseList(path string, depth int, options *OrderOptions, handleFileDirectoryFunc HandleFileDirectoryFunc) (fdl FileDirectoryList, ok bool) {
|
||||
fdl, pcsError := pcs.FilesDirectoriesList(path, options)
|
||||
if pcsError != nil {
|
||||
ok := handleFileDirectoryFunc(depth, path, nil, pcsError) // 传递错误
|
||||
return nil, ok
|
||||
}
|
||||
|
||||
for k := range fdl {
|
||||
ok = handleFileDirectoryFunc(depth+1, fdl[k].Path, fdl[k], nil)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !fdl[k].Isdir {
|
||||
continue
|
||||
}
|
||||
|
||||
fdl[k].Children, ok = pcs.recurseList(fdl[k].Path, depth+1, options, handleFileDirectoryFunc)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return fdl, true
|
||||
}
|
||||
|
||||
// FilesDirectoriesRecurseList 递归获取目录下的文件和目录列表
|
||||
func (pcs *BaiduPCS) FilesDirectoriesRecurseList(path string, options *OrderOptions, handleFileDirectoryFunc HandleFileDirectoryFunc) (data FileDirectoryList) {
|
||||
fd, pcsError := pcs.FilesDirectoriesMeta(path)
|
||||
if pcsError != nil {
|
||||
handleFileDirectoryFunc(0, path, nil, pcsError) // 传递错误
|
||||
return nil
|
||||
}
|
||||
|
||||
if !fd.Isdir { // 不是一个目录
|
||||
handleFileDirectoryFunc(0, path, fd, nil)
|
||||
return FileDirectoryList{fd}
|
||||
}
|
||||
|
||||
data, _ = pcs.recurseList(path, 0, options, handleFileDirectoryFunc)
|
||||
return data
|
||||
}
|
||||
|
||||
// fixMD5 尝试修复MD5字段
|
||||
// 服务器返回的MD5字段不一定正确了, 即是BlockList只有一个md5
|
||||
// MD5字段使用BlockList中的md5
|
||||
func (f *FileDirectory) fixMD5() {
|
||||
if len(f.BlockList) != 1 {
|
||||
return
|
||||
}
|
||||
f.MD5 = f.BlockList[0]
|
||||
}
|
||||
|
||||
func (f *FileDirectory) String() string {
|
||||
builder := &strings.Builder{}
|
||||
tb := pcstable.NewTable(builder)
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||
|
||||
if f.Isdir {
|
||||
tb.AppendBulk([][]string{
|
||||
[]string{"类型", "目录"},
|
||||
[]string{"目录路径", f.Path},
|
||||
[]string{"目录名称", f.Filename},
|
||||
})
|
||||
} else {
|
||||
var md5info string
|
||||
if len(f.BlockList) > 1 {
|
||||
md5info = "md5 (可能不正确)"
|
||||
} else {
|
||||
md5info = "md5 (截图请打码)"
|
||||
}
|
||||
tb.AppendBulk([][]string{
|
||||
[]string{"类型", "文件"},
|
||||
[]string{"文件路径", f.Path},
|
||||
[]string{"文件名称", f.Filename},
|
||||
[]string{"文件大小", strconv.FormatInt(f.Size, 10) + ", " + converter.ConvertFileSize(f.Size)},
|
||||
[]string{md5info, f.MD5},
|
||||
})
|
||||
}
|
||||
|
||||
tb.Append([]string{"app_id", strconv.FormatInt(f.AppID, 10)})
|
||||
tb.Append([]string{"fs_id", strconv.FormatInt(f.FsID, 10)})
|
||||
tb.AppendBulk([][]string{
|
||||
[]string{"创建日期", pcstime.FormatTime(f.Ctime)},
|
||||
[]string{"修改日期", pcstime.FormatTime(f.Mtime)},
|
||||
})
|
||||
|
||||
if f.Ifhassubdir {
|
||||
tb.Append([]string{"是否含有子目录", "true"})
|
||||
}
|
||||
|
||||
tb.Render()
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (fl FileDirectoryList) fixMD5() {
|
||||
for _, v := range fl {
|
||||
v.fixMD5()
|
||||
}
|
||||
}
|
||||
|
||||
// TotalSize 获取目录下文件的总大小
|
||||
func (fl FileDirectoryList) TotalSize() int64 {
|
||||
var size int64
|
||||
for k := range fl {
|
||||
if fl[k] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
size += fl[k].Size
|
||||
|
||||
// 递归获取
|
||||
if fl[k].Children != nil {
|
||||
size += fl[k].Children.TotalSize()
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// Count 获取文件总数和目录总数
|
||||
func (fl FileDirectoryList) Count() (fileN, directoryN int64) {
|
||||
for k := range fl {
|
||||
if fl[k] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if fl[k].Isdir {
|
||||
directoryN++
|
||||
} else {
|
||||
fileN++
|
||||
}
|
||||
|
||||
// 递归获取
|
||||
if fl[k].Children != nil {
|
||||
fN, dN := fl[k].Children.Count()
|
||||
fileN += fN
|
||||
directoryN += dN
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AllFilePaths 返回所有的网盘路径, 包括子目录
|
||||
func (fl FileDirectoryList) AllFilePaths() (pcspaths []string) {
|
||||
fN, dN := fl.Count()
|
||||
pcspaths = make([]string, 0, fN+dN)
|
||||
for k := range fl {
|
||||
if fl[k] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pcspaths = append(pcspaths, fl[k].Path)
|
||||
|
||||
if fl[k].Children != nil {
|
||||
pcspaths = append(pcspaths, fl[k].Children.AllFilePaths()...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
29
baidupcs/internal/panhome/cache.go
Normal file
29
baidupcs/internal/panhome/cache.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package panhome
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SetSignExpires 设置sign过期
|
||||
func (ph *PanHome) SetSignExpires() {
|
||||
if ph.signExpires != nil {
|
||||
ph.signExpires.SetExpires(true)
|
||||
}
|
||||
}
|
||||
|
||||
// CacheSignature 在有效期内返回缓存结果
|
||||
func (ph *PanHome) CacheSignature() (sign SignRes, err error) {
|
||||
if ph.signExpires == nil || ph.signExpires.IsExpires() {
|
||||
// 先签名再设置有效期
|
||||
ph.signRes, err = ph.Signature()
|
||||
if err != nil { // 空指针与空接口不等价
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ph.signExpires = expires.NewExpires(1 * time.Hour) // 设置一小时有效期
|
||||
return ph.signRes, nil
|
||||
}
|
||||
|
||||
return ph.signRes, nil
|
||||
}
|
||||
50
baidupcs/internal/panhome/panhome.go
Normal file
50
baidupcs/internal/panhome/panhome.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package panhome
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/expires"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
// OperationSignature signature
|
||||
OperationSignature = "signature"
|
||||
)
|
||||
|
||||
var (
|
||||
panBaiduComURL = &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "pan.baidu.com",
|
||||
}
|
||||
// PanHomeUserAgent PanHome User-Agent
|
||||
PanHomeUserAgent = "Mozilla/5.0"
|
||||
)
|
||||
|
||||
type (
|
||||
PanHome struct {
|
||||
client *requester.HTTPClient
|
||||
ua string
|
||||
bduss string
|
||||
|
||||
sign1, sign3 []rune
|
||||
timestamp string
|
||||
|
||||
signRes SignRes
|
||||
signExpires expires.Expires
|
||||
}
|
||||
)
|
||||
|
||||
func NewPanHome(client *requester.HTTPClient) *PanHome {
|
||||
ph := PanHome{}
|
||||
if client != nil {
|
||||
newC := *client
|
||||
ph.client = &newC
|
||||
}
|
||||
return &ph
|
||||
}
|
||||
|
||||
func (ph *PanHome) lazyInit() {
|
||||
if ph.client == nil {
|
||||
ph.client = requester.NewHTTPClient()
|
||||
}
|
||||
}
|
||||
66
baidupcs/internal/panhome/parse.go
Normal file
66
baidupcs/internal/panhome/parse.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package panhome
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
signInfoRE = regexp.MustCompile(`"sign1":"(.*?)"[\s\S]*"sign3":"(.*?)","timestamp":(\d*?),`)
|
||||
ErrCookieInvalid = errors.New("cookie is invalid")
|
||||
ErrUnknownLocation = errors.New("unknown location")
|
||||
ErrMatchPanHome = errors.New("网盘首页数据匹配出错")
|
||||
)
|
||||
|
||||
func (ph *PanHome) getSignInfo() error {
|
||||
ph.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
u := *panBaiduComURL
|
||||
u.Path = "/disk/home"
|
||||
resp, err := ph.client.Req(http.MethodGet, u.String(), nil, map[string]string{
|
||||
"User-Agent": PanHomeUserAgent,
|
||||
})
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loc := resp.Header.Get("Location")
|
||||
switch loc {
|
||||
case "/":
|
||||
return ErrCookieInvalid
|
||||
case "":
|
||||
//pass
|
||||
default:
|
||||
locU, err := url.Parse(loc)
|
||||
if err != nil {
|
||||
return ErrUnknownLocation
|
||||
}
|
||||
if locU.Host == "passport.baidu.com" {
|
||||
return ErrCookieInvalid
|
||||
}
|
||||
return ErrUnknownLocation
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
matchRes := signInfoRE.FindSubmatch(body)
|
||||
if len(matchRes) <= 3 {
|
||||
return ErrMatchPanHome
|
||||
}
|
||||
|
||||
ph.sign1 = []rune(*(*string)(unsafe.Pointer(&matchRes[1])))
|
||||
ph.sign3 = []rune(*(*string)(unsafe.Pointer(&matchRes[2])))
|
||||
ph.timestamp = *(*string)(unsafe.Pointer(&matchRes[3]))
|
||||
return nil
|
||||
}
|
||||
41
baidupcs/internal/panhome/sign.go
Normal file
41
baidupcs/internal/panhome/sign.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package panhome
|
||||
|
||||
import (
|
||||
"github.com/iikira/Baidu-Login/bdcrypto"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/netdisksign"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
)
|
||||
|
||||
type (
|
||||
// SignRes 签名结果
|
||||
SignRes interface {
|
||||
Sign() string
|
||||
Timestamp() string
|
||||
}
|
||||
|
||||
signRes struct {
|
||||
sign string
|
||||
timestamp string
|
||||
}
|
||||
)
|
||||
|
||||
func (sr *signRes) Sign() string {
|
||||
return sr.sign
|
||||
}
|
||||
func (sr *signRes) Timestamp() string {
|
||||
return sr.timestamp
|
||||
}
|
||||
|
||||
func (ph *PanHome) Signature() (sign SignRes, err error) {
|
||||
err = ph.getSignInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := netdisksign.Sign2(ph.sign3, ph.sign1)
|
||||
signed := bdcrypto.Base64Encode(o)
|
||||
return &signRes{
|
||||
sign: converter.ToString(signed),
|
||||
timestamp: ph.timestamp,
|
||||
}, nil
|
||||
}
|
||||
114
baidupcs/jsontable.go
Normal file
114
baidupcs/jsontable.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil"
|
||||
"github.com/json-iterator/go"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// PathJSON 网盘路径
|
||||
PathJSON struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// PathsListJSON 网盘路径列表
|
||||
PathsListJSON struct {
|
||||
List []*PathJSON `json:"list"`
|
||||
}
|
||||
|
||||
// FsIDJSON 文件或目录ID
|
||||
FsIDJSON struct {
|
||||
FsID int64 `json:"fs_id"` // fs_id
|
||||
}
|
||||
|
||||
// FsIDListJSON fs_id 列表
|
||||
FsIDListJSON struct {
|
||||
List []*FsIDJSON `json:"list"`
|
||||
}
|
||||
|
||||
// CpMvJSON 源文件目录的地址和目标文件目录的地址
|
||||
CpMvJSON struct {
|
||||
From string `json:"from"` // 源文件或目录
|
||||
To string `json:"to"` // 目标文件或目录
|
||||
}
|
||||
|
||||
// CpMvJSONList CpMvJSON 列表
|
||||
CpMvJSONList []*CpMvJSON
|
||||
|
||||
// CpMvListJSON []*CpMvJSON 对象数组
|
||||
CpMvListJSON struct {
|
||||
List CpMvJSONList `json:"list"`
|
||||
}
|
||||
|
||||
// BlockListJSON 文件分块信息JSON
|
||||
BlockListJSON struct {
|
||||
BlockList []string `json:"block_list"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNilJSONValue 解析出的json值为空
|
||||
ErrNilJSONValue = errors.New("json value is nil")
|
||||
)
|
||||
|
||||
// JSON json 数据构造
|
||||
func (plj *PathsListJSON) JSON(paths ...string) (data []byte, err error) {
|
||||
plj.List = make([]*PathJSON, len(paths))
|
||||
|
||||
for k := range paths {
|
||||
plj.List[k] = &PathJSON{
|
||||
Path: paths[k],
|
||||
}
|
||||
}
|
||||
|
||||
data, err = jsoniter.Marshal(plj)
|
||||
return
|
||||
}
|
||||
|
||||
// JSON json 数据构造
|
||||
func (cj *CpMvJSON) JSON() (data []byte, err error) {
|
||||
data, err = jsoniter.Marshal(cj)
|
||||
return
|
||||
}
|
||||
|
||||
// JSON json 数据构造
|
||||
func (clj *CpMvListJSON) JSON() (data []byte, err error) {
|
||||
data, err = jsoniter.Marshal(clj)
|
||||
return
|
||||
}
|
||||
|
||||
func (clj *CpMvListJSON) String() string {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
tb := pcstable.NewTable(builder)
|
||||
tb.SetHeader([]string{"#", "原路径", "目标路径"})
|
||||
|
||||
for k := range clj.List {
|
||||
if clj.List[k] == nil {
|
||||
continue
|
||||
}
|
||||
tb.Append([]string{strconv.Itoa(k), clj.List[k].From, clj.List[k].To})
|
||||
}
|
||||
|
||||
tb.Render()
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// AllRelatedDir 获取所有相关的目录
|
||||
func (cjl *CpMvJSONList) AllRelatedDir() (dirs []string) {
|
||||
for _, cj := range *cjl {
|
||||
fromDir, toDir := path.Dir(cj.From), path.Dir(cj.To)
|
||||
if !pcsutil.ContainsString(dirs, fromDir) {
|
||||
dirs = append(dirs, fromDir)
|
||||
}
|
||||
if !pcsutil.ContainsString(dirs, toDir) {
|
||||
dirs = append(dirs, toDir)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
20
baidupcs/netdisksign/devuid.go
Normal file
20
baidupcs/netdisksign/devuid.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package netdisksign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/cachepool"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
)
|
||||
|
||||
func DevUID(feature string) string {
|
||||
m := md5.New()
|
||||
m.Write(converter.ToBytes(feature))
|
||||
res := m.Sum(nil)
|
||||
resHex := cachepool.RawMallocByteSlice(34)
|
||||
hex.Encode(resHex[2:], res)
|
||||
resHex[0] = 'O'
|
||||
resHex[1] = '|'
|
||||
return converter.ToString(bytes.ToUpper(resHex))
|
||||
}
|
||||
52
baidupcs/netdisksign/locatedownloadsign.go
Normal file
52
baidupcs/netdisksign/locatedownloadsign.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package netdisksign
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/cachepool"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
LocateDownloadSign struct {
|
||||
Time int64
|
||||
Rand string
|
||||
DevUID string
|
||||
}
|
||||
)
|
||||
|
||||
func NewLocateDownloadSign(uid uint64, bduss string) *LocateDownloadSign {
|
||||
return NewLocateDownloadSignWithTimeAndDevUID(time.Now().Unix(), DevUID(bduss), uid, bduss)
|
||||
}
|
||||
|
||||
func NewLocateDownloadSignWithTimeAndDevUID(timeunix int64, devuid string, uid uint64, bduss string) *LocateDownloadSign {
|
||||
l := &LocateDownloadSign{
|
||||
Time: timeunix,
|
||||
DevUID: devuid,
|
||||
}
|
||||
l.Sign(uid, bduss)
|
||||
return l
|
||||
}
|
||||
|
||||
func (s *LocateDownloadSign) Sign(uid uint64, bduss string) {
|
||||
randSha1 := sha1.New()
|
||||
bdussSha1 := sha1.New()
|
||||
bdussSha1.Write(converter.ToBytes(bduss))
|
||||
sha1ResHex := cachepool.RawMallocByteSlice(40)
|
||||
hex.Encode(sha1ResHex, bdussSha1.Sum(nil))
|
||||
randSha1.Write(sha1ResHex)
|
||||
uidStr := strconv.FormatUint(uid, 10)
|
||||
randSha1.Write(converter.ToBytes(uidStr))
|
||||
randSha1.Write([]byte{'\x65', '\x62', '\x72', '\x63', '\x55', '\x59', '\x69', '\x75', '\x78', '\x61', '\x5a', '\x76', '\x32', '\x58', '\x47', '\x75', '\x37', '\x4b', '\x49', '\x59', '\x4b', '\x78', '\x55', '\x72', '\x71', '\x66', '\x6e', '\x4f', '\x66', '\x70', '\x44', '\x46'})
|
||||
timeStr := strconv.FormatInt(s.Time, 10)
|
||||
randSha1.Write(converter.ToBytes(timeStr))
|
||||
randSha1.Write(converter.ToBytes(s.DevUID))
|
||||
hex.Encode(sha1ResHex, randSha1.Sum(nil))
|
||||
s.Rand = converter.ToString(sha1ResHex)
|
||||
}
|
||||
|
||||
func (s *LocateDownloadSign) URLParam() string {
|
||||
return "time=" + strconv.FormatInt(s.Time, 10) + "&rand=" + s.Rand + "&devuid=" + s.DevUID
|
||||
}
|
||||
12
baidupcs/netdisksign/locatedownloadsign_test.go
Normal file
12
baidupcs/netdisksign/locatedownloadsign_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package netdisksign_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/netdisksign"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocateDownloadSign(t *testing.T) {
|
||||
sign := netdisksign.NewLocateDownloadSignWithTimeAndDevUID(1571140066, "O|1E67351CCE80B2CF48DB511CD77ACD9F", 10086, "test_bduss")
|
||||
fmt.Printf("%#v\n", sign.Rand == "b6bb7a6f46899e181baea58798d4fdb889775c2c")
|
||||
}
|
||||
20
baidupcs/netdisksign/share_sign.go
Normal file
20
baidupcs/netdisksign/share_sign.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package netdisksign
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/cachepool"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func ShareSURLInfoSign(shareID int64) []byte {
|
||||
s := strconv.FormatInt(shareID, 10)
|
||||
m := md5.New()
|
||||
m.Write(converter.ToBytes(s))
|
||||
m.Write([]byte("_sharesurlinfo!@#"))
|
||||
res := m.Sum(nil)
|
||||
resHex := cachepool.RawMallocByteSlice(32)
|
||||
hex.Encode(resHex, res)
|
||||
return resHex
|
||||
}
|
||||
36
baidupcs/netdisksign/sign2.go
Normal file
36
baidupcs/netdisksign/sign2.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package netdisksign
|
||||
|
||||
func Sign2(j, r []rune) []byte {
|
||||
var (
|
||||
a = make([]rune, 256)
|
||||
p = make([]rune, 256)
|
||||
o = make([]byte, len(r))
|
||||
v = len(j)
|
||||
q int
|
||||
u rune
|
||||
i int
|
||||
k rune
|
||||
dr int
|
||||
)
|
||||
if v == 0 {
|
||||
return o
|
||||
}
|
||||
for ; q < 256; q++ {
|
||||
dr = q % v
|
||||
a[q] = j[dr : 1+dr][0]
|
||||
p[q] = rune(q)
|
||||
}
|
||||
for q = 0; q < 256; q++ {
|
||||
u = (u + p[q] + a[q]) % 256
|
||||
p[q], p[u] = p[u], p[q]
|
||||
}
|
||||
u = 0
|
||||
for q = 0; q < len(r); q++ {
|
||||
i = (i + 1) % 256
|
||||
u = (u + p[i]) % 256
|
||||
p[i], p[u] = p[u], p[i]
|
||||
k = p[(p[i]+p[u])%256]
|
||||
o[q] = byte(r[q] ^ k)
|
||||
}
|
||||
return o
|
||||
}
|
||||
62
baidupcs/netdisksign/sign2.js
Normal file
62
baidupcs/netdisksign/sign2.js
Normal file
@@ -0,0 +1,62 @@
|
||||
function s(j, r) {
|
||||
var a = [];
|
||||
var p = [];
|
||||
var o = "";
|
||||
var v = j.length;
|
||||
for (var q = 0; q < 256; q++) {
|
||||
a[q] = j.substr((q % v), 1).charCodeAt(0);
|
||||
// console.log(q, q%v);
|
||||
p[q] = q;
|
||||
// console.log(q, a[q], p[q]);
|
||||
}
|
||||
for (var u = q = 0; q < 256; q++) {
|
||||
u = (u + p[q] + a[q]) % 256;
|
||||
var t = p[q];
|
||||
p[q] = p[u];
|
||||
p[u] = t;
|
||||
}
|
||||
// console.log(p)
|
||||
for (var i = u = q = 0; q < r.length; q++) {
|
||||
i = (i + 1) % 256;
|
||||
u = (u + p[i]) % 256;
|
||||
var t = p[i];
|
||||
p[i] = p[u];
|
||||
p[u] = t;
|
||||
k = p[((p[i] + p[u]) % 256)];
|
||||
o += String.fromCharCode(r.charCodeAt(q) ^ k);
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
function base64encode(t) {
|
||||
var r, e, a, o, n, i, s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
for (a = t.length,
|
||||
e = 0,
|
||||
r = ""; a > e; ) {
|
||||
if (o = 255 & t.charCodeAt(e++),
|
||||
e == a) {
|
||||
r += s.charAt(o >> 2),
|
||||
r += s.charAt((3 & o) << 4),
|
||||
r += "==";
|
||||
break
|
||||
}
|
||||
if (n = t.charCodeAt(e++),
|
||||
e == a) {
|
||||
r += s.charAt(o >> 2),
|
||||
r += s.charAt((3 & o) << 4 | (240 & n) >> 4),
|
||||
r += s.charAt((15 & n) << 2),
|
||||
r += "=";
|
||||
break
|
||||
}
|
||||
i = t.charCodeAt(e++),
|
||||
r += s.charAt(o >> 2),
|
||||
r += s.charAt((3 & o) << 4 | (240 & n) >> 4),
|
||||
r += s.charAt((15 & n) << 2 | (192 & i) >> 6),
|
||||
r += s.charAt(63 & i)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
var s1 = s("e8c7d729eea7b54551aa594f942decbe", "37dbe07ade9359c1aa70807e847f768c13360ad2");
|
||||
console.log(s1);
|
||||
console.log(base64encode(s1));
|
||||
20
baidupcs/netdisksign/sign2_test.go
Normal file
20
baidupcs/netdisksign/sign2_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package netdisksign_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/Baidu-Login/bdcrypto"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/netdisksign"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSign2(t *testing.T) {
|
||||
standard := bdcrypto.Base64Decode([]byte("8RxCbsVeSzn2UjxJAAiV9QQs/WetOj2FJUGwjsMG6SgxFMWlLS/U1Q=="))
|
||||
fmt.Println("standard,", standard)
|
||||
fmt.Printf("standard s %s\n", standard)
|
||||
|
||||
res := netdisksign.Sign2([]rune("e8c7d729eea7b54551aa594f942decbe"), []rune("37dbe07ade9359c1aa70807e847f768c13360ad2"))
|
||||
fmt.Println(res)
|
||||
fmt.Printf("%s\n", string(res))
|
||||
fmt.Println([]byte(string(res)))
|
||||
fmt.Println(bdcrypto.Base64Encode([]byte(string(res))))
|
||||
}
|
||||
97
baidupcs/pcserror/dlinkerrinfo.go
Normal file
97
baidupcs/pcserror/dlinkerrinfo.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package pcserror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
// DlinkErrInfo dlink服务器错误信息
|
||||
DlinkErrInfo struct {
|
||||
Operation string
|
||||
ErrType ErrType
|
||||
Err error
|
||||
ErrNo int `json:"errno"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewDlinkErrInfo 初始化DlinkErrInfo
|
||||
func NewDlinkErrInfo(op string) *DlinkErrInfo {
|
||||
return &DlinkErrInfo{
|
||||
Operation: op,
|
||||
}
|
||||
}
|
||||
|
||||
// SetJSONError 设置JSON错误
|
||||
func (dle *DlinkErrInfo) SetJSONError(err error) {
|
||||
dle.ErrType = ErrTypeJSONParseError
|
||||
dle.Err = err
|
||||
}
|
||||
|
||||
// SetNetError 设置网络错误
|
||||
func (dle *DlinkErrInfo) SetNetError(err error) {
|
||||
dle.ErrType = ErrTypeNetError
|
||||
dle.Err = err
|
||||
}
|
||||
|
||||
// SetRemoteError 设置远端服务器错误
|
||||
func (dle *DlinkErrInfo) SetRemoteError() {
|
||||
dle.ErrType = ErrTypeRemoteError
|
||||
}
|
||||
|
||||
// GetOperation 获取操作
|
||||
func (dle *DlinkErrInfo) GetOperation() string {
|
||||
return dle.Operation
|
||||
}
|
||||
|
||||
// GetErrType 获取错误类型
|
||||
func (dle *DlinkErrInfo) GetErrType() ErrType {
|
||||
return dle.ErrType
|
||||
}
|
||||
|
||||
// GetRemoteErrCode 获取远端服务器错误代码
|
||||
func (dle *DlinkErrInfo) GetRemoteErrCode() int {
|
||||
return dle.ErrNo
|
||||
}
|
||||
|
||||
// GetRemoteErrMsg 获取远端服务器错误消息
|
||||
func (dle *DlinkErrInfo) GetRemoteErrMsg() string {
|
||||
return dle.Msg
|
||||
}
|
||||
|
||||
// GetError 获取原始错误
|
||||
func (dle *DlinkErrInfo) GetError() error {
|
||||
return dle.Err
|
||||
}
|
||||
|
||||
func (dle *DlinkErrInfo) Error() string {
|
||||
if dle.Operation == "" {
|
||||
if dle.Err != nil {
|
||||
return dle.Err.Error()
|
||||
}
|
||||
return StrSuccess
|
||||
}
|
||||
|
||||
switch dle.ErrType {
|
||||
case ErrTypeInternalError:
|
||||
return fmt.Sprintf("%s: %s, %s", dle.Operation, StrInternalError, dle.Err)
|
||||
case ErrTypeJSONParseError:
|
||||
return fmt.Sprintf("%s: %s, %s", dle.Operation, StrJSONParseError, dle.Err)
|
||||
case ErrTypeNetError:
|
||||
return fmt.Sprintf("%s: %s, %s", dle.Operation, StrNetError, dle.Err)
|
||||
case ErrTypeRemoteError:
|
||||
if dle.ErrNo == 0 {
|
||||
return fmt.Sprintf("%s: %s", dle.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s: 遇到错误, %s, 代码: %d, 消息: %s", dle.Operation, StrRemoteError, dle.ErrNo, dle.Msg)
|
||||
case ErrTypeOthers:
|
||||
if dle.Err == nil {
|
||||
return fmt.Sprintf("%s: %s", dle.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s, 遇到错误, %s", dle.Operation, dle.Err)
|
||||
default:
|
||||
panic("dlinkerrinfo: unknown ErrType")
|
||||
}
|
||||
}
|
||||
177
baidupcs/pcserror/panerrorinfo.go
Normal file
177
baidupcs/pcserror/panerrorinfo.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package pcserror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
// PanErrorInfo 网盘网页的api错误
|
||||
PanErrorInfo struct {
|
||||
Operation string
|
||||
ErrType ErrType
|
||||
Err error
|
||||
ErrNo int `json:"errno"`
|
||||
// ErrMsg string `json:"err_msg"`
|
||||
}
|
||||
)
|
||||
|
||||
// NewPanErrorInfo 提供operation操作名称, 返回 *PanErrorInfo
|
||||
func NewPanErrorInfo(operation string) *PanErrorInfo {
|
||||
return &PanErrorInfo{
|
||||
Operation: operation,
|
||||
ErrType: ErrorTypeNoError,
|
||||
}
|
||||
}
|
||||
|
||||
// SetJSONError 设置JSON错误
|
||||
func (pane *PanErrorInfo) SetJSONError(err error) {
|
||||
pane.ErrType = ErrTypeJSONParseError
|
||||
pane.Err = err
|
||||
}
|
||||
|
||||
// SetNetError 设置网络错误
|
||||
func (pane *PanErrorInfo) SetNetError(err error) {
|
||||
pane.ErrType = ErrTypeNetError
|
||||
pane.Err = err
|
||||
}
|
||||
|
||||
// SetRemoteError 设置远端服务器错误
|
||||
func (pane *PanErrorInfo) SetRemoteError() {
|
||||
pane.ErrType = ErrTypeRemoteError
|
||||
}
|
||||
|
||||
// GetOperation 获取操作
|
||||
func (pane *PanErrorInfo) GetOperation() string {
|
||||
return pane.Operation
|
||||
}
|
||||
|
||||
// GetErrType 获取错误类型
|
||||
func (pane *PanErrorInfo) GetErrType() ErrType {
|
||||
return pane.ErrType
|
||||
}
|
||||
|
||||
// GetRemoteErrCode 获取远端服务器错误代码
|
||||
func (pane *PanErrorInfo) GetRemoteErrCode() int {
|
||||
return pane.ErrNo
|
||||
}
|
||||
|
||||
// GetRemoteErrMsg 获取远端服务器错误消息
|
||||
func (pane *PanErrorInfo) GetRemoteErrMsg() string {
|
||||
return FindPanErr(pane.ErrNo)
|
||||
}
|
||||
|
||||
// GetError 获取原始错误
|
||||
func (pane *PanErrorInfo) GetError() error {
|
||||
return pane.Err
|
||||
}
|
||||
|
||||
func (pane *PanErrorInfo) Error() string {
|
||||
if pane.Operation == "" {
|
||||
if pane.Err != nil {
|
||||
return pane.Err.Error()
|
||||
}
|
||||
return StrSuccess
|
||||
}
|
||||
|
||||
switch pane.ErrType {
|
||||
case ErrTypeInternalError:
|
||||
return fmt.Sprintf("%s: %s, %s", pane.Operation, StrInternalError, pane.Err)
|
||||
case ErrTypeJSONParseError:
|
||||
return fmt.Sprintf("%s: %s, %s", pane.Operation, StrJSONParseError, pane.Err)
|
||||
case ErrTypeNetError:
|
||||
return fmt.Sprintf("%s: %s, %s", pane.Operation, StrNetError, pane.Err)
|
||||
case ErrTypeRemoteError:
|
||||
if pane.ErrNo == 0 {
|
||||
return fmt.Sprintf("%s: %s", pane.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
errmsg := FindPanErr(pane.ErrNo)
|
||||
return fmt.Sprintf("%s: 遇到错误, %s, 代码: %d, 消息: %s", pane.Operation, StrRemoteError, pane.ErrNo, errmsg)
|
||||
case ErrTypeOthers:
|
||||
if pane.Err == nil {
|
||||
return fmt.Sprintf("%s: %s", pane.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s, 遇到错误, %s", pane.Operation, pane.Err)
|
||||
default:
|
||||
panic("panerrorinfo: unknown ErrType")
|
||||
}
|
||||
}
|
||||
|
||||
// FindPanErr 根据 ErrNo, 解析网盘错误信息
|
||||
func FindPanErr(errno int) (errmsg string) {
|
||||
switch errno {
|
||||
case 0:
|
||||
return StrSuccess
|
||||
case -1:
|
||||
return "由于您分享了违反相关法律法规的文件,分享功能已被禁用,之前分享出去的文件不受影响。"
|
||||
case -2:
|
||||
return "用户不存在,请刷新页面后重试"
|
||||
case -3:
|
||||
return "文件不存在,请刷新页面后重试"
|
||||
case -4:
|
||||
return "登录信息有误,请重新登录试试"
|
||||
case -5:
|
||||
return "host_key和user_key无效"
|
||||
case -6:
|
||||
return "请重新登录"
|
||||
case -7:
|
||||
return "该分享已删除或已取消"
|
||||
case -8:
|
||||
return "该分享已经过期"
|
||||
case -9:
|
||||
return "文件不存在"
|
||||
case -10:
|
||||
return "分享外链已经达到最大上限100000条,不能再次分享"
|
||||
case -11:
|
||||
return "验证cookie无效"
|
||||
case -12:
|
||||
return "访问密码错误"
|
||||
case -14:
|
||||
return "对不起,短信分享每天限制20条,你今天已经分享完,请明天再来分享吧!"
|
||||
case -15:
|
||||
return "对不起,邮件分享每天限制20封,你今天已经分享完,请明天再来分享吧!"
|
||||
case -16:
|
||||
return "对不起,该文件已经限制分享!"
|
||||
case -17:
|
||||
return "文件分享超过限制"
|
||||
case -19:
|
||||
return "需要输入验证码"
|
||||
case -21:
|
||||
return "分享已取消或分享信息无效"
|
||||
case -30:
|
||||
return "文件已存在"
|
||||
case -31:
|
||||
return "文件保存失败"
|
||||
case -33:
|
||||
return "一次支持操作999个,减点试试吧"
|
||||
case -62:
|
||||
return "可能需要输入验证码"
|
||||
case -70:
|
||||
return "你分享的文件中包含病毒或疑似病毒,为了你和他人的数据安全,换个文件分享吧"
|
||||
case 2:
|
||||
return "参数错误"
|
||||
case 3:
|
||||
return "未登录或帐号无效"
|
||||
case 4:
|
||||
return "存储好像出问题了,请稍候再试"
|
||||
case 105:
|
||||
return "啊哦,链接错误没找到文件,请打开正确的分享链接"
|
||||
case 108:
|
||||
return "文件名有敏感词,优化一下吧"
|
||||
case 110:
|
||||
return "分享次数超出限制,可以到“我的分享”中查看已分享的文件链接"
|
||||
case 112:
|
||||
return "页面已过期,请刷新后重试"
|
||||
case 113:
|
||||
return "签名错误"
|
||||
case 114:
|
||||
return "当前任务不存在,保存失败"
|
||||
case 115:
|
||||
return "该文件禁止分享"
|
||||
case 132:
|
||||
return "您的帐号可能存在安全风险,为了确保为您本人操作,请先进行安全验证。"
|
||||
default:
|
||||
return "未知错误"
|
||||
}
|
||||
}
|
||||
90
baidupcs/pcserror/pcserror.go
Normal file
90
baidupcs/pcserror/pcserror.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Package pcserror PCS错误包
|
||||
package pcserror
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/jsonhelper"
|
||||
"io"
|
||||
)
|
||||
|
||||
type (
|
||||
// ErrType 错误类型
|
||||
ErrType int
|
||||
|
||||
// Error 错误信息接口
|
||||
Error interface {
|
||||
error
|
||||
SetJSONError(err error)
|
||||
SetNetError(err error)
|
||||
SetRemoteError()
|
||||
GetOperation() string
|
||||
GetErrType() ErrType
|
||||
GetRemoteErrCode() int
|
||||
GetRemoteErrMsg() string
|
||||
GetError() error
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrorTypeNoError 无错误
|
||||
ErrorTypeNoError ErrType = iota
|
||||
// ErrTypeInternalError 内部错误
|
||||
ErrTypeInternalError
|
||||
// ErrTypeRemoteError 远端服务器返回错误
|
||||
ErrTypeRemoteError
|
||||
// ErrTypeNetError 网络错误
|
||||
ErrTypeNetError
|
||||
// ErrTypeJSONParseError json 数据解析失败
|
||||
ErrTypeJSONParseError
|
||||
// ErrTypeOthers 其他错误
|
||||
ErrTypeOthers
|
||||
)
|
||||
|
||||
const (
|
||||
// StrSuccess 操作成功
|
||||
StrSuccess = "操作成功"
|
||||
// StrInternalError 内部错误
|
||||
StrInternalError = "内部错误"
|
||||
// StrRemoteError 远端服务器返回错误
|
||||
StrRemoteError = "远端服务器返回错误"
|
||||
// StrNetError 网络错误
|
||||
StrNetError = "网络错误"
|
||||
// StrJSONParseError json 数据解析失败
|
||||
StrJSONParseError = "json 数据解析失败"
|
||||
)
|
||||
|
||||
// DecodePCSJSONError 解析PCS JSON的错误
|
||||
func DecodePCSJSONError(opreation string, data io.Reader) Error {
|
||||
errInfo := NewPCSErrorInfo(opreation)
|
||||
return HandleJSONParse(opreation, data, errInfo)
|
||||
}
|
||||
|
||||
// DecodePanJSONError 解析Pan JSON的错误
|
||||
func DecodePanJSONError(opreation string, data io.Reader) Error {
|
||||
errInfo := NewPanErrorInfo(opreation)
|
||||
return HandleJSONParse(opreation, data, errInfo)
|
||||
}
|
||||
|
||||
// HandleJSONParse 处理解析json
|
||||
func HandleJSONParse(op string, data io.Reader, info interface{}) (pcsError Error) {
|
||||
var (
|
||||
err = jsonhelper.UnmarshalData(data, info)
|
||||
errInfo = info.(Error)
|
||||
)
|
||||
|
||||
if errInfo == nil {
|
||||
errInfo = NewPCSErrorInfo(op)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errInfo.SetJSONError(err)
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 设置出错类型为远程错误
|
||||
if errInfo.GetRemoteErrCode() != 0 {
|
||||
errInfo.SetRemoteError()
|
||||
return errInfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
115
baidupcs/pcserror/pcserrorinfo.go
Normal file
115
baidupcs/pcserror/pcserrorinfo.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package pcserror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
// PCSErrInfo PCS错误信息
|
||||
PCSErrInfo struct {
|
||||
Operation string // 正在进行的操作
|
||||
ErrType ErrType
|
||||
Err error
|
||||
ErrCode int `json:"error_code"` // 错误代码
|
||||
ErrMsg string `json:"error_msg"` // 错误消息
|
||||
}
|
||||
)
|
||||
|
||||
// NewPCSErrorInfo 提供operation操作名称, 返回 *PCSErrInfo
|
||||
func NewPCSErrorInfo(operation string) *PCSErrInfo {
|
||||
return &PCSErrInfo{
|
||||
Operation: operation,
|
||||
ErrType: ErrorTypeNoError,
|
||||
}
|
||||
}
|
||||
|
||||
// SetJSONError 设置JSON错误
|
||||
func (pcse *PCSErrInfo) SetJSONError(err error) {
|
||||
pcse.ErrType = ErrTypeJSONParseError
|
||||
pcse.Err = err
|
||||
}
|
||||
|
||||
// SetNetError 设置网络错误
|
||||
func (pcse *PCSErrInfo) SetNetError(err error) {
|
||||
pcse.ErrType = ErrTypeNetError
|
||||
pcse.Err = err
|
||||
}
|
||||
|
||||
// SetRemoteError 设置远端服务器错误
|
||||
func (pcse *PCSErrInfo) SetRemoteError() {
|
||||
pcse.ErrType = ErrTypeRemoteError
|
||||
}
|
||||
|
||||
// GetOperation 获取操作
|
||||
func (pcse *PCSErrInfo) GetOperation() string {
|
||||
return pcse.Operation
|
||||
}
|
||||
|
||||
// GetErrType 获取错误类型
|
||||
func (pcse *PCSErrInfo) GetErrType() ErrType {
|
||||
return pcse.ErrType
|
||||
}
|
||||
|
||||
// GetRemoteErrCode 获取远端服务器错误代码
|
||||
func (pcse *PCSErrInfo) GetRemoteErrCode() int {
|
||||
return pcse.ErrCode
|
||||
}
|
||||
|
||||
// GetRemoteErrMsg 获取远端服务器错误消息
|
||||
func (pcse *PCSErrInfo) GetRemoteErrMsg() string {
|
||||
_, msg := findPCSErr(pcse.ErrCode, pcse.ErrMsg)
|
||||
return msg
|
||||
}
|
||||
|
||||
// GetError 获取原始错误
|
||||
func (pcse *PCSErrInfo) GetError() error {
|
||||
return pcse.Err
|
||||
}
|
||||
|
||||
func (pcse *PCSErrInfo) Error() string {
|
||||
if pcse.Operation == "" {
|
||||
if pcse.Err != nil {
|
||||
return pcse.Err.Error()
|
||||
}
|
||||
return StrSuccess
|
||||
}
|
||||
|
||||
switch pcse.ErrType {
|
||||
case ErrTypeInternalError:
|
||||
return fmt.Sprintf("%s: %s, %s", pcse.Operation, StrInternalError, pcse.Err)
|
||||
case ErrTypeJSONParseError:
|
||||
return fmt.Sprintf("%s: %s, %s", pcse.Operation, StrJSONParseError, pcse.Err)
|
||||
case ErrTypeNetError:
|
||||
return fmt.Sprintf("%s: %s, %s", pcse.Operation, StrNetError, pcse.Err)
|
||||
case ErrTypeRemoteError:
|
||||
if pcse.ErrCode == 0 {
|
||||
return fmt.Sprintf("%s: %s", pcse.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
code, msg := findPCSErr(pcse.ErrCode, pcse.ErrMsg)
|
||||
return fmt.Sprintf("%s: 遇到错误, %s, 代码: %d, 消息: %s", pcse.Operation, StrRemoteError, code, msg)
|
||||
case ErrTypeOthers:
|
||||
if pcse.Err == nil {
|
||||
return fmt.Sprintf("%s: %s", pcse.Operation, StrSuccess)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s, 遇到错误, %s", pcse.Operation, pcse.Err)
|
||||
default:
|
||||
panic("pcserrorinfo: unknown ErrType")
|
||||
}
|
||||
}
|
||||
|
||||
// findPCSErr 检查 PCS 错误, 查找已知错误
|
||||
func findPCSErr(errCode int, errMsg string) (int, string) {
|
||||
switch errCode {
|
||||
case 0:
|
||||
return errCode, ""
|
||||
case 31045: // user not exists
|
||||
return errCode, "操作失败, 可能百度帐号登录状态过期, 请尝试重新登录, 消息: " + errMsg
|
||||
case 31066: // file does not exist
|
||||
return errCode, "文件或目录不存在"
|
||||
case 31079: // file md5 not found, you should use upload api to upload the whole file.
|
||||
return errCode, "秒传文件失败"
|
||||
}
|
||||
return errCode, errMsg
|
||||
}
|
||||
743
baidupcs/prepare.go
Normal file
743
baidupcs/prepare.go
Normal file
@@ -0,0 +1,743 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/netdisksign"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/multipartreader"
|
||||
"github.com/iikira/baidu-tools/tieba"
|
||||
"github.com/json-iterator/go"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
reqType int
|
||||
)
|
||||
|
||||
const (
|
||||
reqTypePCS = iota
|
||||
reqTypePan
|
||||
)
|
||||
|
||||
func handleRespClose(resp *http.Response) error {
|
||||
if resp != nil {
|
||||
return resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleRespStatusError(opreation string, resp *http.Response) pcserror.Error {
|
||||
errInfo := pcserror.NewPCSErrorInfo(opreation)
|
||||
// http 响应错误处理
|
||||
switch resp.StatusCode / 100 {
|
||||
case 4, 5:
|
||||
resp.Body.Close()
|
||||
errInfo.SetNetError(fmt.Errorf("http 响应错误, %s", resp.Status))
|
||||
return errInfo
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) sendReqReturnResp(rt reqType, op, method, urlStr string, post interface{}, header map[string]string) (resp *http.Response, pcsError pcserror.Error) {
|
||||
if header == nil {
|
||||
header = map[string]string{}
|
||||
}
|
||||
|
||||
var (
|
||||
_, uaok = header["User-Agent"]
|
||||
)
|
||||
|
||||
if !uaok {
|
||||
switch rt {
|
||||
case reqTypePCS:
|
||||
header["User-Agent"] = pcs.pcsUA
|
||||
case reqTypePan:
|
||||
header["User-Agent"] = pcs.panUA
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := pcs.client.Req(method, urlStr, post, header)
|
||||
if err != nil {
|
||||
handleRespClose(resp)
|
||||
switch rt {
|
||||
case reqTypePCS:
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: op,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
case reqTypePan:
|
||||
return nil, &pcserror.PanErrorInfo{
|
||||
Operation: op,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) sendReqReturnReadCloser(rt reqType, op, method, urlStr string, post interface{}, header map[string]string) (readCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
resp, pcsError := pcs.sendReqReturnResp(rt, op, method, urlStr, post, header)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// PrepareUK 获取用户 UK, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareUK() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("need_selfinfo", "1")
|
||||
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "api/user/getinfo",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationGetUK, http.MethodGet, panURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareQuotaInfo 获取当前用户空间配额信息, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareQuotaInfo() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("quota", "info")
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationQuotaInfo, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationQuotaInfo, http.MethodGet, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareFilesDirectoriesBatchMeta 获取多个文件/目录的元信息, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareFilesDirectoriesBatchMeta(paths ...string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
sendData, err := (&PathsListJSON{}).JSON(paths...)
|
||||
if err != nil {
|
||||
panic(OperationFilesDirectoriesMeta + ", json 数据构造失败, " + err.Error())
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "meta")
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationFilesDirectoriesMeta, pcsURL)
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("param", bytes.NewReader(sendData))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationFilesDirectoriesMeta, http.MethodPost, pcsURL.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareFilesDirectoriesList 获取目录下的文件和目录列表, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareFilesDirectoriesList(path string, options *OrderOptions) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
if options == nil {
|
||||
options = DefaultOrderOptions
|
||||
}
|
||||
if path == "" {
|
||||
path = PathSeparator
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "list", map[string]string{
|
||||
"path": path,
|
||||
"by": *(*string)(unsafe.Pointer(&options.By)),
|
||||
"order": *(*string)(unsafe.Pointer(&options.Order)),
|
||||
"limit": "0-2147483647",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationFilesDirectoriesList, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationFilesDirectoriesList, http.MethodGet, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareSearch 按文件名搜索文件, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareSearch(targetPath, keyword string, recursive bool) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
var re string
|
||||
if recursive {
|
||||
re = "1"
|
||||
} else {
|
||||
re = "0"
|
||||
}
|
||||
pcsURL := pcs.generatePCSURL("file", "search", map[string]string{
|
||||
"path": targetPath,
|
||||
"wd": keyword,
|
||||
"re": re,
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationSearch, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationSearch, http.MethodGet, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRemove 批量删除文件/目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRemove(paths ...string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
sendData, err := (&PathsListJSON{}).JSON(paths...)
|
||||
if err != nil {
|
||||
panic(OperationMove + ", json 数据构造失败, " + err.Error())
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "delete")
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRemove, pcsURL)
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("param", bytes.NewReader(sendData))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationRemove, http.MethodPost, pcsURL.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareMkdir 创建目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareMkdir(pcspath string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("file", "mkdir", map[string]string{
|
||||
"path": pcspath,
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s", OperationMkdir, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationMkdir, http.MethodPost, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) prepareCpMvOp(op string, cpmvJSON ...*CpMvJSON) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
var method string
|
||||
switch op {
|
||||
case OperationCopy:
|
||||
method = "copy"
|
||||
case OperationMove, OperationRename:
|
||||
method = "move"
|
||||
default:
|
||||
panic("Unknown opreation: " + op)
|
||||
}
|
||||
|
||||
sendData, err := (&CpMvListJSON{
|
||||
List: cpmvJSON,
|
||||
}).JSON()
|
||||
if err != nil {
|
||||
//json 数据生成失败
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", method)
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", op, pcsURL)
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("param", bytes.NewReader(sendData))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, op, http.MethodPost, pcsURL.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRename 重命名文件/目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRename(from, to string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
return pcs.prepareCpMvOp(OperationRename, &CpMvJSON{
|
||||
From: from,
|
||||
To: to,
|
||||
})
|
||||
}
|
||||
|
||||
// PrepareCopy 批量拷贝文件/目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCopy(cpmvJSON ...*CpMvJSON) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
return pcs.prepareCpMvOp(OperationCopy, cpmvJSON...)
|
||||
}
|
||||
|
||||
// PrepareMove 批量移动文件/目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareMove(cpmvJSON ...*CpMvJSON) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
return pcs.prepareCpMvOp(OperationMove, cpmvJSON...)
|
||||
}
|
||||
|
||||
// prepareRapidUpload 秒传文件, 不进行文件夹检查
|
||||
func (pcs *BaiduPCS) prepareRapidUpload(targetPath, contentMD5, sliceMD5, crc32 string, length int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("file", "rapidupload", map[string]string{
|
||||
"path": targetPath, // 上传文件的全路径名
|
||||
"content-length": strconv.FormatInt(length, 10), // 待秒传的文件长度
|
||||
"content-md5": contentMD5, // 待秒传的文件的MD5
|
||||
"slice-md5": sliceMD5, // 待秒传的文件前256kb的MD5
|
||||
"content-crc32": crc32, // 待秒传文件CRC32
|
||||
"ondup": "overwrite", // overwrite: 表示覆盖同名文件; newcopy: 表示生成文件副本并进行重命名,命名规则为“文件名_日期.后缀”
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRapidUpload, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationRapidUpload, http.MethodGet, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRapidUpload 秒传文件, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRapidUpload(targetPath, contentMD5, sliceMD5, crc32 string, length int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsError = pcs.checkIsdir(OperationRapidUpload, targetPath)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
return pcs.prepareRapidUpload(targetPath, contentMD5, sliceMD5, crc32, length)
|
||||
}
|
||||
|
||||
// PrepareLocateDownload 获取下载链接, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareLocateDownload(pcspath string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
bduss := pcs.GetBDUSS()
|
||||
// 检测uid
|
||||
if pcs.uid == 0 {
|
||||
t, err := tieba.NewUserInfoByBDUSS(bduss)
|
||||
if err != nil {
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: OperationLocateDownload,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
pcs.uid = t.Baidu.UID
|
||||
}
|
||||
|
||||
ns := netdisksign.NewLocateDownloadSign(pcs.uid, bduss)
|
||||
pcsURL := &url.URL{
|
||||
Scheme: GetHTTPScheme(pcs.isHTTPS),
|
||||
Host: PCSBaiduCom,
|
||||
Path: "/rest/2.0/pcs/file",
|
||||
RawQuery: (url.Values{
|
||||
"app_id": []string{PanAppID},
|
||||
"method": []string{"locatedownload"},
|
||||
"path": []string{pcspath},
|
||||
"ver": []string{"2"},
|
||||
}).Encode() + "&" + ns.URLParam(),
|
||||
}
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationLocateDownload, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationLocateDownload, http.MethodGet, pcsURL.String(), nil, pcs.getPanUAHeader())
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareLocatePanAPIDownload 从百度网盘首页获取下载链接, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareLocatePanAPIDownload(fidList ...int64) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
// 初始化
|
||||
var (
|
||||
sign, err = pcs.ph.CacheSignature()
|
||||
)
|
||||
if err != nil {
|
||||
return nil, &pcserror.PanErrorInfo{
|
||||
Operation: OperationLocatePanAPIDownload,
|
||||
ErrType: pcserror.ErrTypeOthers,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
panURL := pcs.generatePanURL("download", nil)
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationLocatePanAPIDownload, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationLocatePanAPIDownload, http.MethodPost, panURL.String(), map[string]string{
|
||||
"sign": sign.Sign(),
|
||||
"timestamp": sign.Timestamp(),
|
||||
"fidlist": mergeInt64List(fidList...),
|
||||
}, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareUpload 上传单个文件, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareUpload(targetPath string, uploadFunc UploadFunc) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsError = pcs.checkIsdir(OperationUpload, targetPath)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "upload", map[string]string{
|
||||
"path": targetPath,
|
||||
"ondup": "overwrite",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationUpload, pcsURL)
|
||||
|
||||
resp, err := uploadFunc(pcsURL.String(), pcs.client.Jar)
|
||||
if err != nil {
|
||||
handleRespClose(resp)
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: OperationUpload,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
pcsError = handleRespStatusError(OperationUpload, resp)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// PrepareUploadTmpFile 分片上传—文件分片及上传, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareUploadTmpFile(uploadFunc UploadFunc) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("file", "upload", map[string]string{
|
||||
"type": "tmpfile",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationUploadTmpFile, pcsURL)
|
||||
|
||||
resp, err := uploadFunc(pcsURL.String(), pcs.client.Jar)
|
||||
if err != nil {
|
||||
handleRespClose(resp)
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: OperationUploadTmpFile,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
pcsError = handleRespStatusError(OperationUploadTmpFile, resp)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// PrepareUploadCreateSuperFile 分片上传—合并分片文件, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareUploadCreateSuperFile(checkDir bool, targetPath string, blockList ...string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
if checkDir {
|
||||
// 检查是否为目录
|
||||
pcsError = pcs.checkIsdir(OperationUploadCreateSuperFile, targetPath)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
}
|
||||
|
||||
bl := BlockListJSON{
|
||||
BlockList: blockList,
|
||||
}
|
||||
|
||||
sendData, err := jsoniter.Marshal(&bl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "createsuperfile", map[string]string{
|
||||
"path": targetPath,
|
||||
"ondup": "overwrite",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationUploadCreateSuperFile, pcsURL)
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("param", bytes.NewReader(sendData))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationUploadCreateSuperFile, http.MethodPost, pcsURL.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareUploadPrecreate 分片上传—Precreate, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareUploadPrecreate(targetPath, contentMD5, sliceMD5, crc32 string, size int64, bolckList ...string) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "api/precreate",
|
||||
}
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationUploadPrecreate, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationUploadPrecreate, http.MethodPost, panURL.String(), map[string]string{
|
||||
"path": targetPath,
|
||||
"size": strconv.FormatInt(size, 10),
|
||||
"isdir": "0",
|
||||
"block_list": mergeStringList(bolckList...),
|
||||
"autoinit": "1",
|
||||
"content-md5": contentMD5,
|
||||
"slice-md5": sliceMD5,
|
||||
"contentCrc32": crc32,
|
||||
"rtype": "2",
|
||||
}, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareUploadSuperfile2 另一个上传接口
|
||||
func (pcs *BaiduPCS) PrepareUploadSuperfile2(uploadid, targetPath string, partseq int, partOffset int64, uploadFunc UploadFunc) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL := pcs.generatePCSURL("superfile2", "upload", map[string]string{
|
||||
"type": "tmpfile",
|
||||
"path": targetPath,
|
||||
"partseq": strconv.Itoa(partseq),
|
||||
"partoffset": strconv.FormatInt(partOffset, 10),
|
||||
"uploadid": uploadid,
|
||||
"vip": "1",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationUploadSuperfile2, pcsURL)
|
||||
|
||||
resp, err := uploadFunc(pcsURL.String(), pcs.client.Jar)
|
||||
if err != nil {
|
||||
handleRespClose(resp)
|
||||
return nil, &pcserror.PCSErrInfo{
|
||||
Operation: OperationUploadSuperfile2,
|
||||
ErrType: pcserror.ErrTypeNetError,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
pcsError = handleRespStatusError(OperationUpload, resp)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// PrepareCloudDlAddTask 添加离线下载任务, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCloudDlAddTask(sourceURL, savePath string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL2 := pcs.generatePCSURL2("services/cloud_dl", "add_task", map[string]string{
|
||||
"save_path": savePath,
|
||||
"source_url": sourceURL,
|
||||
"timeout": "2147483647",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationCloudDlAddTask, pcsURL2)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationCloudDlAddTask, http.MethodPost, pcsURL2.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareCloudDlQueryTask 精确查询离线下载任务, 只返回服务器响应数据和错误信息,
|
||||
// taskids 例子: 12123,234234,2344, 用逗号隔开多个 task_id
|
||||
func (pcs *BaiduPCS) PrepareCloudDlQueryTask(taskIDs string) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL2 := pcs.generatePCSURL2("services/cloud_dl", "query_task", map[string]string{
|
||||
"op_type": "1",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationCloudDlQueryTask, pcsURL2)
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("task_ids", strings.NewReader(taskIDs))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationCloudDlQueryTask, http.MethodPost, pcsURL2.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareCloudDlListTask 查询离线下载任务列表, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCloudDlListTask() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL2 := pcs.generatePCSURL2("services/cloud_dl", "list_task", map[string]string{
|
||||
"need_task_info": "1",
|
||||
"status": "255",
|
||||
"start": "0",
|
||||
"limit": "1000",
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationCloudDlListTask, pcsURL2)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationCloudDlListTask, http.MethodPost, pcsURL2.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) prepareCloudDlCDTask(opreation, method string, taskID int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL2 := pcs.generatePCSURL2("services/cloud_dl", method, map[string]string{
|
||||
"task_id": strconv.FormatInt(taskID, 10),
|
||||
})
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", opreation, pcsURL2)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, opreation, http.MethodPost, pcsURL2.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareCloudDlCancelTask 取消离线下载任务, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCloudDlCancelTask(taskID int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
return pcs.prepareCloudDlCDTask(OperationCloudDlCancelTask, "cancel_task", taskID)
|
||||
}
|
||||
|
||||
// PrepareCloudDlDeleteTask 取消离线下载任务, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCloudDlDeleteTask(taskID int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
return pcs.prepareCloudDlCDTask(OperationCloudDlDeleteTask, "delete_task", taskID)
|
||||
}
|
||||
|
||||
// PrepareCloudDlClearTask 清空离线下载任务记录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareCloudDlClearTask() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
pcsURL2 := pcs.generatePCSURL2("services/cloud_dl", "clear_task")
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationCloudDlClearTask, pcsURL2)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationCloudDlClearTask, http.MethodPost, pcsURL2.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareSharePSet 私密分享文件, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareSharePSet(paths []string, period int) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "share/pset",
|
||||
}
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationShareSet, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationShareSet, http.MethodPost, panURL.String(), map[string]string{
|
||||
"path_list": mergeStringList(paths...),
|
||||
"schannel": "0",
|
||||
"channel_list": "[]",
|
||||
"period": strconv.Itoa(period),
|
||||
}, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareShareCancel 取消分享, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareShareCancel(shareIDs []int64) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "share/cancel",
|
||||
}
|
||||
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationShareCancel, panURL)
|
||||
|
||||
ss := converter.SliceInt64ToString(shareIDs)
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationShareCancel, http.MethodPost, panURL.String(), map[string]string{
|
||||
"shareid_list": "[" + strings.Join(ss, ",") + "]",
|
||||
}, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareShareList 列出分享列表, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareShareList(page int) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("page", strconv.Itoa(page))
|
||||
query.Set("desc", "1")
|
||||
query.Set("order", "time")
|
||||
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "share/record",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationShareList, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationShareList, http.MethodGet, panURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareShareSURLInfo 获取分享的详细信息, 包含密码, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareShareSURLInfo(shareID int64) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("shareid", strconv.FormatInt(shareID, 10))
|
||||
query.Set("sign", converter.ToString(netdisksign.ShareSURLInfoSign(shareID)))
|
||||
|
||||
panURL := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: PanBaiduCom,
|
||||
Path: "share/surlinfoinrecord",
|
||||
RawQuery: query.Encode(),
|
||||
}
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationShareSURLInfo, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationShareSURLInfo, http.MethodGet, panURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRecycleList 列出回收站文件列表, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRecycleList(page int) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
panURL := pcs.generatePanURL("recycle/list", map[string]string{
|
||||
"num": "100",
|
||||
"page": strconv.Itoa(page),
|
||||
})
|
||||
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRecycleList, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationRecycleList, http.MethodGet, panURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRecycleRestore 还原回收站文件或目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRecycleRestore(fidList ...int64) (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "restore")
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRecycleRestore, pcsURL)
|
||||
|
||||
fsIDList := make([]*FsIDJSON, 0, len(fidList))
|
||||
for k := range fidList {
|
||||
fsIDList = append(fsIDList, &FsIDJSON{
|
||||
FsID: fidList[k],
|
||||
})
|
||||
}
|
||||
fsIDListJSON := FsIDListJSON{
|
||||
List: fsIDList,
|
||||
}
|
||||
|
||||
sendData, err := jsoniter.Marshal(&fsIDListJSON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 表单上传
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFeild("param", bytes.NewReader(sendData))
|
||||
mr.CloseMultipart()
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationRecycleRestore, http.MethodPost, pcsURL.String(), mr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRecycleDelete 删除回收站文件或目录, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRecycleDelete(fidList ...int64) (dataReadCloser io.ReadCloser, panError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
panURL := pcs.generatePanURL("recycle/delete", nil)
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRecycleDelete, panURL)
|
||||
|
||||
dataReadCloser, panError = pcs.sendReqReturnReadCloser(reqTypePan, OperationRecycleDelete, http.MethodPost, panURL.String(), map[string]string{
|
||||
"fidlist": mergeInt64List(fidList...),
|
||||
}, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// PrepareRecycleClear 清空回收站, 只返回服务器响应数据和错误信息
|
||||
func (pcs *BaiduPCS) PrepareRecycleClear() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
|
||||
pcs.lazyInit()
|
||||
|
||||
pcsURL := pcs.generatePCSURL("file", "delete", map[string]string{
|
||||
"type": "recycle",
|
||||
})
|
||||
|
||||
baiduPCSVerbose.Infof("%s URL: %s\n", OperationRecycleClear, pcsURL)
|
||||
|
||||
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationRecycleClear, http.MethodGet, pcsURL.String(), nil, nil)
|
||||
return
|
||||
}
|
||||
22
baidupcs/publicsuffix.go
Normal file
22
baidupcs/publicsuffix.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"net/http/cookiejar"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type list struct{}
|
||||
|
||||
// PublicSuffixList baidupcs PublicSuffixList
|
||||
var PublicSuffixList cookiejar.PublicSuffixList = list{}
|
||||
|
||||
func (list) PublicSuffix(domain string) string {
|
||||
if strings.HasSuffix(domain, ".baidu.com") {
|
||||
return "com"
|
||||
}
|
||||
return domain
|
||||
}
|
||||
|
||||
func (list) String() string {
|
||||
return "baidupcs"
|
||||
}
|
||||
32
baidupcs/quota.go
Normal file
32
baidupcs/quota.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
)
|
||||
|
||||
type quotaInfo struct {
|
||||
*pcserror.PCSErrInfo
|
||||
Quota int64 `json:"quota"`
|
||||
Used int64 `json:"used"`
|
||||
}
|
||||
|
||||
// QuotaInfo 获取当前用户空间配额信息
|
||||
func (pcs *BaiduPCS) QuotaInfo() (quota, used int64, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareQuotaInfo()
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
quotaInfo := "aInfo{
|
||||
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationQuotaInfo),
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationQuotaInfo, dataReadCloser, quotaInfo)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return quotaInfo.Quota, quotaInfo.Used, nil
|
||||
}
|
||||
115
baidupcs/recycle.go
Normal file
115
baidupcs/recycle.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
)
|
||||
|
||||
type (
|
||||
// RecycleFDInfo 回收站中文件/目录信息
|
||||
RecycleFDInfo struct {
|
||||
FsID int64 `json:"fs_id"` // fs_id
|
||||
Isdir int `json:"isdir"`
|
||||
LeftTime int `json:"leftTime"` // 剩余时间
|
||||
Path string `json:"path"` // 路径
|
||||
Filename string `json:"server_filename"` // 文件名 或 目录名
|
||||
Ctime int64 `json:"server_ctime"` // 创建日期
|
||||
Mtime int64 `json:"server_mtime"` // 修改日期
|
||||
MD5 string `json:"md5"` // md5 值
|
||||
Size int64 `json:"size"` // 文件大小 (目录为0)
|
||||
}
|
||||
|
||||
// RecycleFDInfoList 回收站中文件/目录列表
|
||||
RecycleFDInfoList []*RecycleFDInfo
|
||||
|
||||
recycleListJSON struct {
|
||||
*pcserror.PanErrorInfo
|
||||
List RecycleFDInfoList `json:"list"`
|
||||
}
|
||||
|
||||
recycleRestoreJSON struct {
|
||||
*pcserror.PCSErrInfo
|
||||
Extra FsIDListJSON `json:"extra"`
|
||||
}
|
||||
|
||||
// RecycleClearInfo 清空回收站的信息
|
||||
RecycleClearInfo struct {
|
||||
List RecycleFDInfoList `json:"list"`
|
||||
SussNum int `json:"succNum"`
|
||||
}
|
||||
|
||||
recycleClearJSON struct {
|
||||
*pcserror.PCSErrInfo
|
||||
Extra RecycleClearInfo `json:"extra"`
|
||||
}
|
||||
)
|
||||
|
||||
// RecycleList 列出回收站文件列表
|
||||
func (pcs *BaiduPCS) RecycleList(page int) (fdl RecycleFDInfoList, panError pcserror.Error) {
|
||||
dataReadCloser, panError := pcs.PrepareRecycleList(page)
|
||||
if panError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationRecycleList)
|
||||
jsonData := recycleListJSON{
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
panError = pcserror.HandleJSONParse(OperationRecycleList, dataReadCloser, &jsonData)
|
||||
if panError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return jsonData.List, nil
|
||||
}
|
||||
|
||||
// RecycleRestore 还原回收站文件或目录
|
||||
func (pcs *BaiduPCS) RecycleRestore(fidList ...int64) (sussFsIDList []*FsIDJSON, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareRecycleRestore(fidList...)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationRecycleRestore)
|
||||
jsonData := recycleRestoreJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationRecycleRestore, dataReadCloser, &jsonData)
|
||||
return jsonData.Extra.List, pcsError
|
||||
}
|
||||
|
||||
// RecycleDelete 删除回收站文件或目录
|
||||
func (pcs *BaiduPCS) RecycleDelete(fidList ...int64) (panError pcserror.Error) {
|
||||
dataReadCloser, panError := pcs.PrepareRecycleDelete(fidList...)
|
||||
if panError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
panError = pcserror.DecodePanJSONError(OperationRecycleDelete, dataReadCloser)
|
||||
return
|
||||
}
|
||||
|
||||
// RecycleClear 清空回收站
|
||||
func (pcs *BaiduPCS) RecycleClear() (sussNum int, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareRecycleClear()
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(OperationRecycleClear)
|
||||
jsonData := recycleClearJSON{
|
||||
PCSErrInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationRecycleClear, dataReadCloser, &jsonData)
|
||||
return jsonData.Extra.SussNum, pcsError
|
||||
}
|
||||
44
baidupcs/rm_mkdir.go
Normal file
44
baidupcs/rm_mkdir.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Remove 批量删除文件/目录
|
||||
func (pcs *BaiduPCS) Remove(paths ...string) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareRemove(paths...)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.DecodePCSJSONError(OperationRemove, dataReadCloser)
|
||||
if errInfo != nil {
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache(allRelatedDir(paths))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mkdir 创建目录
|
||||
func (pcs *BaiduPCS) Mkdir(pcspath string) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareMkdir(pcspath)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.DecodePCSJSONError(OperationMkdir, dataReadCloser)
|
||||
if errInfo != nil {
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache([]string{path.Dir(pcspath)})
|
||||
return
|
||||
}
|
||||
174
baidupcs/share.go
Normal file
174
baidupcs/share.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// ShareOption 分享可选项
|
||||
ShareOption struct {
|
||||
Password string // 密码
|
||||
Period int // 有效期
|
||||
}
|
||||
|
||||
// Shared 分享信息
|
||||
Shared struct {
|
||||
Link string `json:"link"`
|
||||
ShareID int64 `json:"shareid"`
|
||||
}
|
||||
|
||||
// ShareRecordInfo 分享信息
|
||||
ShareRecordInfo struct {
|
||||
ShareID int64 `json:"shareId"`
|
||||
FsIds []int64 `json:"fsIds"`
|
||||
Passwd string `json:"-"` // 这个字段已经没有用了, 需要从ShareSURLInfo中获取
|
||||
Shortlink string `json:"shortlink"`
|
||||
Status int `json:"status"` // 状态
|
||||
Public int `json:"public"` // 是否为公开分享
|
||||
TypicalCategory int `json:"typicalCategory"` // 文件类型
|
||||
TypicalPath string `json:"typicalPath"`
|
||||
}
|
||||
|
||||
shareSURLInfo struct {
|
||||
*pcserror.PanErrorInfo
|
||||
*ShareSURLInfo
|
||||
}
|
||||
|
||||
// ShareSURLInfo 分享的子信息
|
||||
ShareSURLInfo struct {
|
||||
Pwd string `json:"pwd"` // 新密码
|
||||
ShortURL string `json:"shorturl"`
|
||||
}
|
||||
|
||||
// ShareRecordInfoList 分享信息列表
|
||||
ShareRecordInfoList []*ShareRecordInfo
|
||||
|
||||
sharePSetJSON struct {
|
||||
*Shared
|
||||
*pcserror.PanErrorInfo
|
||||
}
|
||||
|
||||
shareListJSON struct {
|
||||
List ShareRecordInfoList `json:"list"`
|
||||
*pcserror.PanErrorInfo
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrShareLinkNotFound 未找到分享链接
|
||||
ErrShareLinkNotFound = errors.New("未找到分享链接")
|
||||
)
|
||||
|
||||
// ShareSet 分享文件
|
||||
func (pcs *BaiduPCS) ShareSet(paths []string, option *ShareOption) (s *Shared, pcsError pcserror.Error) {
|
||||
if option == nil {
|
||||
option = &ShareOption{}
|
||||
}
|
||||
|
||||
dataReadCloser, pcsError := pcs.PrepareSharePSet(paths, option.Period)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationShareSet)
|
||||
jsonData := sharePSetJSON{
|
||||
Shared: &Shared{},
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationShareSet, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if jsonData.Link == "" {
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
errInfo.Err = ErrShareLinkNotFound
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
return jsonData.Shared, nil
|
||||
}
|
||||
|
||||
// ShareCancel 取消分享
|
||||
func (pcs *BaiduPCS) ShareCancel(shareIDs []int64) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareShareCancel(shareIDs)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
pcsError = pcserror.DecodePanJSONError(OperationShareCancel, dataReadCloser)
|
||||
return
|
||||
}
|
||||
|
||||
// ShareList 列出分享列表
|
||||
func (pcs *BaiduPCS) ShareList(page int) (records ShareRecordInfoList, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareShareList(page)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationShareList)
|
||||
jsonData := shareListJSON{
|
||||
List: records,
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationShareList, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
// json解析错误
|
||||
if pcsError.GetErrType() == pcserror.ErrTypeJSONParseError {
|
||||
// 服务器更改, List为空时变成{}, 导致解析错误
|
||||
if strings.Contains(pcsError.GetError().Error(), `"list":{}`) {
|
||||
// 返回空列表
|
||||
return jsonData.List, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if jsonData.List == nil {
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
errInfo.Err = errors.New("shared list is nil")
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
return jsonData.List, nil
|
||||
}
|
||||
|
||||
//ShareSURLInfo 获取分享的详细信息, 包含密码
|
||||
func (pcs *BaiduPCS) ShareSURLInfo(shareID int64) (info *ShareSURLInfo, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareShareSURLInfo(shareID)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationShareSURLInfo)
|
||||
|
||||
jsonData := shareSURLInfo{
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationShareList, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
// json解析错误
|
||||
return
|
||||
}
|
||||
|
||||
// 去掉0
|
||||
if jsonData.Pwd == "0" {
|
||||
jsonData.Pwd = ""
|
||||
}
|
||||
|
||||
return jsonData.ShareSURLInfo, nil
|
||||
}
|
||||
157
baidupcs/transfer.go
Normal file
157
baidupcs/transfer.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func (pcs *BaiduPCS) GenerateShareQueryURL(subPath string, params map[string]string) *url.URL {
|
||||
shareURL := &url.URL{
|
||||
Scheme: GetHTTPScheme(true),
|
||||
Host: PanBaiduCom,
|
||||
Path: "/share/" + subPath,
|
||||
}
|
||||
uv := shareURL.Query()
|
||||
uv.Set("app_id", PanAppID)
|
||||
uv.Set("channel", "chunlei")
|
||||
uv.Set("clienttype", "0")
|
||||
uv.Set("web", "1")
|
||||
for key, value := range params {
|
||||
uv.Set(key, value)
|
||||
}
|
||||
|
||||
shareURL.RawQuery = uv.Encode()
|
||||
return shareURL
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) PostShareQuery(url string, data map[string]string) (res map[string]string) {
|
||||
dataReadCloser, panError := pcs.sendReqReturnReadCloser(reqTypePan, OperationShareFileSavetoLocal, http.MethodPost, url, data, map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Referer": "https://pan.baidu.com/disk/home",
|
||||
})
|
||||
res = make(map[string]string)
|
||||
if panError != nil {
|
||||
res["ErrMsg"] = "提交分享项查询请求时发生错误"
|
||||
return
|
||||
}
|
||||
defer dataReadCloser.Close()
|
||||
body, _ := ioutil.ReadAll(dataReadCloser)
|
||||
errno := gjson.Get(string(body), `errno`).String()
|
||||
if errno != "0" {
|
||||
res["ErrMsg"] = "分享码错误"
|
||||
return
|
||||
}
|
||||
res["ErrMsg"] = "0"
|
||||
return
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) AccessSharePage(sharelink string, first bool) (tokens map[string]string) {
|
||||
tokens = make(map[string]string)
|
||||
tokens["ErrMsg"] = "0"
|
||||
headers := make(map[string]string)
|
||||
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
|
||||
headers["Referer"] = "https://pan.baidu.com/disk/home"
|
||||
|
||||
dataReadCloser, panError := pcs.sendReqReturnReadCloser(reqTypePan, OperationShareFileSavetoLocal, http.MethodGet, sharelink, nil, headers)
|
||||
|
||||
if panError != nil {
|
||||
tokens["ErrMsg"] = "访问分享页失败"
|
||||
return
|
||||
}
|
||||
defer dataReadCloser.Close()
|
||||
body, _ := ioutil.ReadAll(dataReadCloser)
|
||||
not_found_flag := strings.Contains(string(body), "platform-non-found")
|
||||
error_page_title := strings.Contains(string(body), "error-404")
|
||||
if error_page_title {
|
||||
tokens["ErrMsg"] = "页面不存在"
|
||||
return
|
||||
}
|
||||
if not_found_flag {
|
||||
tokens["ErrMsg"] = "分享链接已失效"
|
||||
return
|
||||
} else {
|
||||
re, _ := regexp.Compile(`"bdstoken":"([A-Za-z0-9]+)",`)
|
||||
sub := re.FindSubmatch(body)
|
||||
if len(sub) < 2 {
|
||||
tokens["ErrMsg"] = "分享页面解析失败"
|
||||
return
|
||||
}
|
||||
tokens["bdstoken"] = string(sub[1])
|
||||
if first {
|
||||
return
|
||||
}
|
||||
re, _ = regexp.Compile(`"shareid":([0-9]+),`)
|
||||
sub = re.FindSubmatch(body)
|
||||
if len(sub) < 2 && !first {
|
||||
tokens["ErrMsg"] = "缺少提取码"
|
||||
return
|
||||
}
|
||||
tokens["shareid"] = string(sub[1])
|
||||
re, _ = regexp.Compile(`uk=([0-9]+)`)
|
||||
sub = re.FindSubmatch(body)
|
||||
tokens["uk"] = string(sub[1])
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) GenerateRequestQuery(mode, link string, params map[string]string) (res map[string]string) {
|
||||
res = make(map[string]string)
|
||||
headers := map[string]string{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
|
||||
"Referer": "https://pan.baidu.com/disk/home",
|
||||
}
|
||||
if mode == "POST" {
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
}
|
||||
dataReadCloser, panError := pcs.sendReqReturnReadCloser(reqTypePan, OperationShareFileSavetoLocal, mode, link, params, headers)
|
||||
if panError != nil {
|
||||
res["ErrMsg"] = "未知错误"
|
||||
return
|
||||
}
|
||||
defer dataReadCloser.Close()
|
||||
body, err := ioutil.ReadAll(dataReadCloser)
|
||||
|
||||
if err != nil {
|
||||
res["ErrMsg"] = "未知错误"
|
||||
return
|
||||
}
|
||||
if !gjson.Valid(string(body)) {
|
||||
res["ErrMsg"] = "返回值json解析错误"
|
||||
return
|
||||
}
|
||||
errno := gjson.Get(string(body), `errno`).Int()
|
||||
if errno != 0 {
|
||||
res["ErrMsg"] = "获取分享项元数据错误"
|
||||
if mode == "POST" && errno == 12 {
|
||||
path := gjson.Get(string(body), `info.0.path`).String()
|
||||
_, file := filepath.Split(path)
|
||||
res["ErrMsg"] = fmt.Sprintf("当前目录下已有%s同名文件/文件夹", file)
|
||||
}
|
||||
return
|
||||
}
|
||||
if mode != "POST" {
|
||||
res["title"] = gjson.Get(string(body), `title`).String()
|
||||
res["filename"] = gjson.Get(string(body), `list.0.server_filename`).String()
|
||||
var fids_str string = "["
|
||||
fsids := gjson.Get(string(body), `list.#.fs_id`).Array()
|
||||
if len(fsids) > 1 {
|
||||
res["filename"] += "等多个文件"
|
||||
}
|
||||
for _, sid := range fsids {
|
||||
fids_str += sid.String() + ","
|
||||
}
|
||||
res["fs_id"] = fids_str[:len(fids_str)-1] + "]"
|
||||
}
|
||||
res["ErrMsg"] = fmt.Sprintf("%d", errno)
|
||||
return
|
||||
}
|
||||
269
baidupcs/upload.go
Normal file
269
baidupcs/upload.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxUploadBlockSize 最大上传的文件分片大小
|
||||
MaxUploadBlockSize = 2 * converter.GB
|
||||
// MinUploadBlockSize 最小的上传的文件分片大小
|
||||
MinUploadBlockSize = 4 * converter.MB
|
||||
// MaxRapidUploadSize 秒传文件支持的最大文件大小
|
||||
MaxRapidUploadSize = 20 * converter.GB
|
||||
// RecommendUploadBlockSize 推荐的上传的文件分片大小
|
||||
RecommendUploadBlockSize = 1 * converter.GB
|
||||
// SliceMD5Size 计算 slice-md5 所需的长度
|
||||
SliceMD5Size = 256 * converter.KB
|
||||
// EmptyContentMD5 空串的md5
|
||||
EmptyContentMD5 = "d41d8cd98f00b204e9800998ecf8427e"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUploadMD5NotFound 未找到md5
|
||||
ErrUploadMD5NotFound = errors.New("unknown response data, md5 not found")
|
||||
// ErrUploadSavePathFound 未找到保存路径
|
||||
ErrUploadSavePathFound = errors.New("unknown response data, file saved path not found")
|
||||
// ErrUploadSeqNotMatch 服务器返回的上传队列不匹配
|
||||
ErrUploadSeqNotMatch = errors.New("服务器返回的上传队列不匹配")
|
||||
)
|
||||
|
||||
type (
|
||||
// UploadFunc 上传文件处理函数
|
||||
UploadFunc func(uploadURL string, jar http.CookieJar) (resp *http.Response, err error)
|
||||
|
||||
// RapidUploadInfo 文件秒传信息
|
||||
RapidUploadInfo struct {
|
||||
Filename string
|
||||
ContentLength int64
|
||||
ContentMD5 string
|
||||
SliceMD5 string
|
||||
ContentCrc32 string
|
||||
}
|
||||
|
||||
uploadJSON struct {
|
||||
*PathJSON
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
|
||||
uploadTmpFileJSON struct {
|
||||
MD5 string `json:"md5"`
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
|
||||
uploadPrecreateJSON struct {
|
||||
ReturnType int `json:"return_type"` // 1上传, 2秒传
|
||||
UploadID string `json:"uploadid"`
|
||||
BlockList []int `json:"block_list"`
|
||||
*pcserror.PanErrorInfo
|
||||
fdJSON `json:"info"`
|
||||
}
|
||||
|
||||
// UploadSeq 分片上传顺序
|
||||
UploadSeq struct {
|
||||
Seq int
|
||||
Block string
|
||||
}
|
||||
|
||||
// PrecreateInfo 预提交文件消息返回数据
|
||||
PrecreateInfo struct {
|
||||
IsRapidUpload bool
|
||||
UploadID string
|
||||
UploadSeqList []*UploadSeq
|
||||
}
|
||||
|
||||
uploadSuperfile2JSON struct {
|
||||
MD5 string `json:"md5"`
|
||||
*pcserror.PCSErrInfo
|
||||
}
|
||||
)
|
||||
|
||||
// RapidUpload 秒传文件
|
||||
func (pcs *BaiduPCS) RapidUpload(targetPath, contentMD5, sliceMD5, crc32 string, length int64) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareRapidUpload(targetPath, contentMD5, sliceMD5, crc32, length)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
pcsError = pcserror.DecodePCSJSONError(OperationRapidUpload, dataReadCloser)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache([]string{path.Dir(targetPath)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RapidUploadNoCheckDir 秒传文件, 不进行目录检查, 会覆盖掉同名的目录!
|
||||
func (pcs *BaiduPCS) RapidUploadNoCheckDir(targetPath, contentMD5, sliceMD5, crc32 string, length int64) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.prepareRapidUpload(targetPath, contentMD5, sliceMD5, crc32, length)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
pcsError = pcserror.DecodePCSJSONError(OperationRapidUpload, dataReadCloser)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload 上传单个文件
|
||||
func (pcs *BaiduPCS) Upload(targetPath string, uploadFunc UploadFunc) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUpload(targetPath, uploadFunc)
|
||||
if pcsError != nil {
|
||||
return pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
// 数据处理
|
||||
jsonData := uploadJSON{
|
||||
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationUpload),
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationUpload, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if jsonData.Path == "" {
|
||||
jsonData.PCSErrInfo.ErrType = pcserror.ErrTypeInternalError
|
||||
jsonData.PCSErrInfo.Err = ErrUploadSavePathFound
|
||||
return jsonData.PCSErrInfo
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache([]string{path.Dir(targetPath)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadTmpFile 分片上传—文件分片及上传
|
||||
func (pcs *BaiduPCS) UploadTmpFile(uploadFunc UploadFunc) (md5 string, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUploadTmpFile(uploadFunc)
|
||||
if pcsError != nil {
|
||||
return "", pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
// 数据处理
|
||||
jsonData := uploadTmpFileJSON{
|
||||
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationUploadTmpFile),
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationUploadTmpFile, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 未找到md5
|
||||
if jsonData.MD5 == "" {
|
||||
jsonData.PCSErrInfo.ErrType = pcserror.ErrTypeInternalError
|
||||
jsonData.PCSErrInfo.Err = ErrUploadMD5NotFound
|
||||
return "", jsonData.PCSErrInfo
|
||||
}
|
||||
|
||||
return jsonData.MD5, nil
|
||||
}
|
||||
|
||||
// UploadCreateSuperFile 分片上传—合并分片文件
|
||||
func (pcs *BaiduPCS) UploadCreateSuperFile(checkDir bool, targetPath string, blockList ...string) (pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUploadCreateSuperFile(checkDir, targetPath, blockList...)
|
||||
if pcsError != nil {
|
||||
return pcsError
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.DecodePCSJSONError(OperationUploadCreateSuperFile, dataReadCloser)
|
||||
if errInfo != nil {
|
||||
return errInfo
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
pcs.deleteCache([]string{path.Dir(targetPath)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadPrecreate 分片上传—Precreate,
|
||||
// 支持检验秒传
|
||||
func (pcs *BaiduPCS) UploadPrecreate(targetPath, contentMD5, sliceMD5, crc32 string, size int64, bolckList ...string) (precreateInfo *PrecreateInfo, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUploadPrecreate(targetPath, contentMD5, sliceMD5, crc32, size, bolckList...)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
errInfo := pcserror.NewPanErrorInfo(OperationUploadPrecreate)
|
||||
jsonData := uploadPrecreateJSON{
|
||||
PanErrorInfo: errInfo,
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationUploadPrecreate, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch jsonData.ReturnType {
|
||||
case 1: // 上传
|
||||
seqLen := len(jsonData.BlockList)
|
||||
if seqLen != len(bolckList) {
|
||||
errInfo.ErrType = pcserror.ErrTypeRemoteError
|
||||
errInfo.Err = ErrUploadSeqNotMatch
|
||||
return nil, errInfo
|
||||
}
|
||||
|
||||
seqList := make([]*UploadSeq, 0, seqLen)
|
||||
for k, seq := range jsonData.BlockList {
|
||||
seqList = append(seqList, &UploadSeq{
|
||||
Seq: seq,
|
||||
Block: bolckList[k],
|
||||
})
|
||||
}
|
||||
return &PrecreateInfo{
|
||||
UploadID: jsonData.UploadID,
|
||||
UploadSeqList: seqList,
|
||||
}, nil
|
||||
|
||||
case 2: // 秒传
|
||||
return &PrecreateInfo{
|
||||
IsRapidUpload: true,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
panic("unknown returntype")
|
||||
}
|
||||
}
|
||||
|
||||
// UploadSuperfile2 分片上传—Superfile2
|
||||
func (pcs *BaiduPCS) UploadSuperfile2(uploadid, targetPath string, partseq int, partOffset int64, uploadFunc UploadFunc) (md5sum string, pcsError pcserror.Error) {
|
||||
dataReadCloser, pcsError := pcs.PrepareUploadSuperfile2(uploadid, targetPath, partseq, partOffset, uploadFunc)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer dataReadCloser.Close()
|
||||
|
||||
jsonData := uploadSuperfile2JSON{
|
||||
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationUploadSuperfile2),
|
||||
}
|
||||
|
||||
pcsError = pcserror.HandleJSONParse(OperationUploadSuperfile2, dataReadCloser, &jsonData)
|
||||
if pcsError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return jsonData.MD5, nil
|
||||
}
|
||||
73
baidupcs/util.go
Normal file
73
baidupcs/util.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package baidupcs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Isdir 检查路径在网盘中是否为目录
|
||||
func (pcs *BaiduPCS) Isdir(pcspath string) (isdir bool, pcsError pcserror.Error) {
|
||||
if path.Clean(pcspath) == PathSeparator {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
f, pcsError := pcs.FilesDirectoriesMeta(pcspath)
|
||||
if pcsError != nil {
|
||||
return false, pcsError
|
||||
}
|
||||
|
||||
return f.Isdir, nil
|
||||
}
|
||||
|
||||
func (pcs *BaiduPCS) checkIsdir(op string, targetPath string) pcserror.Error {
|
||||
// 检测文件是否存在于网盘路径
|
||||
// 很重要, 如果文件存在会直接覆盖!!! 即使是根目录!
|
||||
isdir, pcsError := pcs.Isdir(targetPath)
|
||||
if pcsError != nil {
|
||||
// 忽略远程服务端返回的错误
|
||||
if pcsError.GetErrType() != pcserror.ErrTypeRemoteError {
|
||||
return pcsError
|
||||
}
|
||||
}
|
||||
|
||||
errInfo := pcserror.NewPCSErrorInfo(op)
|
||||
if isdir {
|
||||
errInfo.ErrType = pcserror.ErrTypeOthers
|
||||
errInfo.Err = errors.New("保存路径不可以覆盖目录")
|
||||
return errInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeStringList(a ...string) string {
|
||||
s := strings.Join(a, `","`)
|
||||
return `["` + s + `"]`
|
||||
}
|
||||
|
||||
func mergeInt64List(si ...int64) string {
|
||||
i := converter.SliceInt64ToString(si)
|
||||
s := strings.Join(i, ",")
|
||||
return "[" + s + "]"
|
||||
}
|
||||
|
||||
func allRelatedDir(pcspaths []string) (dirs []string) {
|
||||
for _, pcspath := range pcspaths {
|
||||
pathDir := path.Dir(pcspath)
|
||||
if !pcsutil.ContainsString(dirs, pathDir) {
|
||||
dirs = append(dirs, pathDir)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetHTTPScheme 获取 http 协议, https 或 http
|
||||
func GetHTTPScheme(https bool) (scheme string) {
|
||||
if https {
|
||||
return "https"
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
144
build.sh
Executable file
144
build.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/sh
|
||||
|
||||
name="BaiduPCS-Go"
|
||||
version=$1
|
||||
|
||||
if [ "$1" = "" ]; then
|
||||
version=v3.6.2
|
||||
fi
|
||||
|
||||
output="out/"
|
||||
|
||||
old_golang() {
|
||||
GOROOT=/usr/local/go1.10.8
|
||||
go=$GOROOT/bin/go
|
||||
}
|
||||
|
||||
new_golang() {
|
||||
GOROOT=/usr/local/go
|
||||
go=$GOROOT/bin/go
|
||||
}
|
||||
|
||||
Build() {
|
||||
old_golang
|
||||
goarm=$4
|
||||
if [ "$4" = "" ]; then
|
||||
goarm=7
|
||||
fi
|
||||
|
||||
echo "Building $1..."
|
||||
export GOOS=$2 GOARCH=$3 GO386=sse2 CGO_ENABLED=0 GOARM=$4
|
||||
if [ $2 = "windows" ]; then
|
||||
goversioninfo -o=resource_windows_386.syso
|
||||
goversioninfo -64 -o=resource_windows_amd64.syso
|
||||
$go build -ldflags "-X main.Version=$version -s -w" -o "$output/$1/$name.exe"
|
||||
RicePack $1 $name.exe
|
||||
else
|
||||
$go build -ldflags "-X main.Version=$version -s -w" -o "$output/$1/$name"
|
||||
RicePack $1 $name
|
||||
fi
|
||||
|
||||
Pack $1
|
||||
}
|
||||
|
||||
AndroidBuild() {
|
||||
new_golang
|
||||
echo "Building $1..."
|
||||
export GOOS=$2 GOARCH=$3 GOARM=$4 CGO_ENABLED=1
|
||||
go build -ldflags "-X main.Version=$version -s -w -linkmode=external -extldflags=-pie" -o "$output/$1/$name"
|
||||
|
||||
RicePack $1 $name
|
||||
Pack $1
|
||||
}
|
||||
|
||||
IOSBuild() {
|
||||
old_golang
|
||||
echo "Building $1..."
|
||||
mkdir -p "$output/$1"
|
||||
cd "$output/$1"
|
||||
export CC=/usr/local/go/misc/ios/clangwrap.sh GOOS=darwin GOARCH=arm GOARM=7 CGO_ENABLED=1
|
||||
$go build -ldflags "-X main.Version=$version -s -w" -o "armv7" github.com/iikira/BaiduPCS-Go
|
||||
jtool --sign --inplace --ent ../../entitlements.xml "armv7"
|
||||
export GOARCH=arm64
|
||||
$go build -ldflags "-X main.Version=$version -s -w" -o "arm64" github.com/iikira/BaiduPCS-Go
|
||||
jtool --sign --inplace --ent ../../entitlements.xml "arm64"
|
||||
lipo -create "armv7" "arm64" -output $name # merge
|
||||
rm "armv7" "arm64"
|
||||
cd ../..
|
||||
RicePack $1 $name
|
||||
Pack $1
|
||||
}
|
||||
|
||||
# zip 打包
|
||||
Pack() {
|
||||
cp README.md "$output/$1"
|
||||
|
||||
cd $output
|
||||
zip -q -r "$1.zip" "$1"
|
||||
|
||||
# 删除
|
||||
rm -rf "$1"
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
# rice 打包静态资源
|
||||
RicePack() {
|
||||
return # 已取消web功能
|
||||
rice -i github.com/iikira/BaiduPCS-Go/internal/pcsweb append --exec "$output/$1/$2"
|
||||
}
|
||||
|
||||
touch ./vendor/golang.org/x/sys/windows/windows.s
|
||||
|
||||
# Android
|
||||
export NDK_INSTALL=$ANDROID_NDK_ROOT/bin
|
||||
# CC=$NDK_INSTALL/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gcc AndroidBuild $name-$version"-android-16-armv5" android arm 5
|
||||
# CC=$NDK_INSTALL/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gcc AndroidBuild $name-$version"-android-16-armv6" android arm 6
|
||||
CC=$NDK_INSTALL/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gcc AndroidBuild $name-$version"-android-16-armv7" android arm 7
|
||||
CC=$NDK_INSTALL/aarch64-linux-android-4.9/bin/aarch64-linux-android-gcc AndroidBuild $name-$version"-android-21-arm64" android arm64 7
|
||||
CC=$NDK_INSTALL/i686-linux-android-4.9/bin/i686-linux-android-gcc AndroidBuild $name-$version"-android-16-386" android 386 7
|
||||
CC=$NDK_INSTALL/x86_64-linux-android-4.9/bin/x86_64-linux-android-gcc AndroidBuild $name-$version"-android-21-amd64" android amd64 7
|
||||
|
||||
# iOS
|
||||
IOSBuild $name-$version"-darwin-ios-arm"
|
||||
|
||||
# OS X / macOS
|
||||
Build $name-$version"-darwin-osx-amd64" darwin amd64
|
||||
# Build $name-$version"-darwin-osx-386" darwin 386
|
||||
|
||||
# Windows
|
||||
Build $name-$version"-windows-x86" windows 386
|
||||
Build $name-$version"-windows-x64" windows amd64
|
||||
|
||||
# Linux
|
||||
Build $name-$version"-linux-386" linux 386
|
||||
Build $name-$version"-linux-amd64" linux amd64
|
||||
Build $name-$version"-linux-armv5" linux arm 5
|
||||
Build $name-$version"-linux-armv7" linux arm 7
|
||||
Build $name-$version"-linux-arm64" linux arm64
|
||||
GOMIPS=softfloat Build $name-$version"-linux-mips" linux mips
|
||||
Build $name-$version"-linux-mips64" linux mips64
|
||||
GOMIPS=softfloat Build $name-$version"-linux-mipsle" linux mipsle
|
||||
Build $name-$version"-linux-mips64le" linux mips64le
|
||||
# Build $name-$version"-linux-ppc64" linux ppc64
|
||||
# Build $name-$version"-linux-ppc64le" linux ppc64le
|
||||
# Build $name-$version"-linux-s390x" linux s390x
|
||||
|
||||
# Others
|
||||
# Build $name-$version"-solaris-amd64" solaris amd64
|
||||
Build $name-$version"-freebsd-386" freebsd 386
|
||||
Build $name-$version"-freebsd-amd64" freebsd amd64
|
||||
# Build $name-$version"-freebsd-arm" freebsd arm
|
||||
# Build $name-$version"-netbsd-386" netbsd 386
|
||||
# Build $name-$version"-netbsd-amd64" netbsd amd64
|
||||
# Build $name-$version"-netbsd-arm" netbsd arm
|
||||
# Build $name-$version"-openbsd-386" openbsd 386
|
||||
# Build $name-$version"-openbsd-amd64" openbsd amd64
|
||||
# Build $name-$version"-openbsd-arm" openbsd arm
|
||||
# Build $name-$version"-plan9-386" plan9 386
|
||||
# Build $name-$version"-plan9-amd64" plan9 amd64
|
||||
# Build $name-$version"-plan9-arm" plan9 arm
|
||||
# Build $name-$version"-nacl-386" nacl 386
|
||||
# Build $name-$version"-nacl-amd64p32" nacl amd64p32
|
||||
# Build $name-$version"-nacl-arm" nacl arm
|
||||
# Build $name-$version"-dragonflybsd-amd64" dragonfly amd64
|
||||
130
cmd/AndroidNDKBuild/main.go
Normal file
130
cmd/AndroidNDKBuild/main.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// AndroidNDKBuild
|
||||
// go build -ldflags "-X main.APILevel=15 -X main.Arch=x86_64"
|
||||
// env ANDROID_API_LEVEL NDK ANDROID_NDK_ROOT GOARCH
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// NDKPath path to Android NDK
|
||||
NDKPath string
|
||||
// APILevel Android api level
|
||||
APILevel string
|
||||
// Arch arch
|
||||
Arch string
|
||||
)
|
||||
|
||||
func getNDKPath() string {
|
||||
ndkPath, ok := os.LookupEnv("NDK")
|
||||
if ok {
|
||||
return ndkPath
|
||||
}
|
||||
ndkPath, ok = os.LookupEnv("ANDROID_NDK_ROOT")
|
||||
if ok {
|
||||
return ndkPath
|
||||
}
|
||||
ndkPath, ok = os.LookupEnv("ANDROID_NDK_DIR")
|
||||
if ok {
|
||||
return ndkPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getAPILevel() string {
|
||||
apiLevelStr, ok := os.LookupEnv("ANDROID_API_LEVEL")
|
||||
if ok {
|
||||
return apiLevelStr
|
||||
}
|
||||
return "21"
|
||||
}
|
||||
|
||||
func getGoarch() string {
|
||||
arch, ok := os.LookupEnv("GOARCH")
|
||||
if ok {
|
||||
return arch
|
||||
}
|
||||
|
||||
return runtime.GOARCH
|
||||
}
|
||||
|
||||
func getArch() string {
|
||||
if Arch != "" {
|
||||
return Arch
|
||||
}
|
||||
goarch := getGoarch()
|
||||
switch goarch {
|
||||
case "386":
|
||||
return "x86"
|
||||
case "amd64":
|
||||
return "x86_64"
|
||||
case "arm64":
|
||||
return "aarch64"
|
||||
}
|
||||
return goarch
|
||||
}
|
||||
|
||||
func getPlatformsArch() string {
|
||||
arch := getArch()
|
||||
switch arch {
|
||||
case "aarch64":
|
||||
return "arm64"
|
||||
}
|
||||
return arch
|
||||
}
|
||||
|
||||
func main() {
|
||||
if NDKPath == "" {
|
||||
NDKPath = getNDKPath()
|
||||
}
|
||||
if APILevel == "" {
|
||||
APILevel = getAPILevel()
|
||||
}
|
||||
if Arch == "" {
|
||||
Arch = getArch()
|
||||
}
|
||||
|
||||
lastPattern := "*-gcc"
|
||||
if runtime.GOOS == "windows" {
|
||||
lastPattern += ".exe"
|
||||
}
|
||||
|
||||
gccPaths, err := filepath.Glob(filepath.Join(NDKPath, "toolchains", getArch()+"-*", "prebuilt", runtime.GOOS+"-*", "bin", lastPattern))
|
||||
checkErr(err)
|
||||
if len(gccPaths) == 0 {
|
||||
panic("no match gcc")
|
||||
}
|
||||
|
||||
args := make([]string, len(os.Args))
|
||||
copy(args[1:], os.Args[1:])
|
||||
args[0] = "--sysroot=" + filepath.Join(NDKPath, "platforms", "android-"+APILevel, "arch-"+getPlatformsArch())
|
||||
|
||||
gccExec := exec.Command(gccPaths[0], args...)
|
||||
gccExec.Stdout = os.Stdout
|
||||
gccExec.Stderr = os.Stderr
|
||||
|
||||
err = gccExec.Run()
|
||||
exitError, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
status := exitError.ProcessState.Sys().(syscall.WaitStatus)
|
||||
os.Exit(status.ExitStatus())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
1
debian/Packages.sh
vendored
Normal file
1
debian/Packages.sh
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dpkg-scanpackages . | gzip > Packages.gz
|
||||
209
debian/copyright
vendored
Normal file
209
debian/copyright
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
Format-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135
|
||||
Name: BaiduPCS-Go
|
||||
Maintainer: iikira <i@mail.iikira.com>
|
||||
Source: https://github.com/iikira/BaiduPCS-Go
|
||||
|
||||
Copyright: 2016-2019 iikira <i@mail.iikira.com>
|
||||
License: Apache-2.0+
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright iikira.
|
||||
|
||||
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.
|
||||
|
||||
9
debian/iphoneos-arm/control
vendored
Normal file
9
debian/iphoneos-arm/control
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Package: BaiduPCS-Go
|
||||
Version: 3.6
|
||||
Homepage: https://github.com/iikira/BaiduPCS-Go
|
||||
Section: 实用工具
|
||||
Priority: optional
|
||||
Architecture: iphoneos-arm
|
||||
Installed-Size: 4096
|
||||
Maintainer: iikira <i@mail.iikira.com>
|
||||
Description: BaiduPCS-Go 使用Go语言编写的百度网盘命令行客户端, 为操作百度网盘, 提供实用功能.
|
||||
9
debian/linux-amd64/control
vendored
Normal file
9
debian/linux-amd64/control
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Package: BaiduPCS-Go
|
||||
Version: 3.6
|
||||
Homepage: https://github.com/iikira/BaiduPCS-Go
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Installed-Size: 4096
|
||||
Maintainer: iikira <i@mail.iikira.com>
|
||||
Description: BaiduPCS-Go 使用Go语言编写的百度网盘命令行客户端, 为操作百度网盘, 提供实用功能.
|
||||
19
docs/README.md
Normal file
19
docs/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 文档目录
|
||||
|
||||
## 百度PCS文档
|
||||
|
||||
### 文件API
|
||||
|
||||
[综述](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/overview.md)
|
||||
|
||||
[文件API列表](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/file_data_apis_list.md)
|
||||
|
||||
[文件API错误码列表](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/file_data_apis_error.md)
|
||||
|
||||
### 结构化数据API
|
||||
|
||||
[综述](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/structured_data_apis_overview.md)
|
||||
|
||||
[结构化数据API列表](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/structured_data_api_list.md)
|
||||
|
||||
[结构化数据API错误码](https://github.com/iikira/BaiduPCS-Go/blob/master/docs/structured_data_apis_error.md)
|
||||
71
docs/file_data_apis_error.md
Normal file
71
docs/file_data_apis_error.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# 文件数据API错误码
|
||||
|
||||
| HTTP状态码 | 错误码 | 错误信息 | 备注 |
|
||||
| :- | -: | :- | :- |
|
||||
| 200 | 0 | no error | 没有错误 |
|
||||
| 400 | 3 | Unsupported open api | 不支持此接口 |
|
||||
| 403 | 4 | No permission to do this operation | 没有权限执行此操作 |
|
||||
| 403 | 5 | Unauthorized client IP address | IP未授权 |
|
||||
| 503 | 31001 | db query error | 数据库查询错误 |
|
||||
| 503 | 31002 | db connect error | 数据库连接错误 |
|
||||
| 503 | 31003 | db result set is empty | 数据库返回空结果 |
|
||||
| 503 | 31021 | network error | 网络错误 |
|
||||
| 503 | 31022 | can not access server | 暂时无法连接服务器 |
|
||||
| 400 | 31023 | param error | 输入参数错误 |
|
||||
| 400 | 31024 | app id is empty | app id为空 |
|
||||
| 503 | 31025 | bcs error | 后端存储错误 |
|
||||
| 403 | 31041 | bduss is invalid | 用户的cookie不是合法的百度cookie |
|
||||
| 403 | 31042 | user is not login | 用户未登陆 |
|
||||
| 403 | 31043 | user is not active | 用户未激活 |
|
||||
| 403 | 31044 | user is not authorized | 用户未授权 |
|
||||
| 403 | 31045 | user not exists | 用户不存在 |
|
||||
| 403 | 31046 | user already exists | 用户已经存在 |
|
||||
| 400 | 31061 | file already exists | 文件已经存在 |
|
||||
| 400 | 31062 | file name is invalid | 文件名非法 |
|
||||
| 400 | 31063 | file parent path does not exist | 文件父目录不存在 |
|
||||
| 403 | 31064 | file is not authorized | 无权访问此文件 |
|
||||
| 400 | 31065 | directory is full | 目录已满 |
|
||||
| 403 | 31066 | file does not exist | 文件不存在 |
|
||||
| 503 | 31067 | file deal failed | 文件处理出错 |
|
||||
| 503 | 31068 | file create failed | 文件创建失败 |
|
||||
| 503 | 31069 | file copy failed | 文件拷贝失败 |
|
||||
| 503 | 31070 | file delete failed | 文件删除失败 |
|
||||
| 503 | 31071 | get file meta failed | 不能读取文件元信息 |
|
||||
| 503 | 31072 | file move failed | 文件移动失败 |
|
||||
| 503 | 31073 | file rename failed | 文件重命名失败 |
|
||||
| 503 | 31081 | superfile create failed | superfile创建失败 |
|
||||
| 503 | 31082 | superfile block list is empty | superfile 块列表为空 |
|
||||
| 503 | 31083 | superfile update failed | superfile 更新失败 |
|
||||
| 503 | 31101 | tag internal error | tag系统内部错误 |
|
||||
| 503 | 31102 | tag param error | tag参数错误 |
|
||||
| 503 | 31103 | tag database error | tag系统错误 |
|
||||
| 403 | 31110 | access denied to set quota | 未授权设置此目录配额 |
|
||||
| 400 | 31111 | quota only sopport 2 level directories | 配额管理只支持两级目录 |
|
||||
| 400 | 31112 | exceed quota | 超出配额 |
|
||||
| 403 | 31113 | the quota is bigger than one of its parent directorys | 配额不能超出目录祖先的配额 |
|
||||
| 403 | 31114 | the quota is smaller than one of its sub directorys | 配额不能比子目录配额小 |
|
||||
| 503 | 31141 | thumbnail failed, internal error | 请求缩略图服务失败 |
|
||||
| 401 | 110 | Access token invalid or no longer valid | Access Token不正确或者已经过期 |
|
||||
| 400 | 31201 | signature error | 签名错误 |
|
||||
| 400 | 31203 | acl put error | 设置acl失败 |
|
||||
| 400 | 31204 | acl query error | 请求acl验证失败 |
|
||||
| 400 | 31205 | acl get error | 获取acl失败 |
|
||||
| 404 | 31079 | File md5 not found, you should use upload API to upload the whole file. | 未找到文件MD5 |,| 请使用上传API上传整个文件。 |
|
||||
| 404 | 31202 | object not exists | 文件不存在 |
|
||||
| 404 | 31206 | acl get error | acl不存在 |
|
||||
| 400 | 31207 | bucket already exists | bucket已存在 |
|
||||
| 400 | 31208 | bad request | 用户请求错误 |
|
||||
| 500 | 31209 | baidubs internal error | 服务器错误 |
|
||||
| 501 | 31210 | not implement | 服务器不支持 |
|
||||
| 403 | 31211 | access denied | 禁止访问 |
|
||||
| 503 | 31212 | service unavailable | 服务不可用 |
|
||||
| 503 | 31213 | service unavailable | 重试出错 |
|
||||
| 503 | 31214 | put object data error | 上传文件data失败 |
|
||||
| 503 | 31215 | put object meta error | 上传文件meta失败 |
|
||||
| 503 | 31216 | get object data error | 下载文件data失败 |
|
||||
| 503 | 31217 | get object meta error | 下载文件meta失败 |
|
||||
| 403 | 31218 | storage exceed limit | 容量超出限额 |
|
||||
| 403 | 31219 | request exceed limit | 请求数超出限额 |
|
||||
| 403 | 31220 | transfer exceed limit | 流量超出限额 |
|
||||
| 500 | 31298 | the value of KEY[VALUE] in pcs response headers is invalid | 服务器返回值KEY非法 |
|
||||
| 500 | 31299 | no KEY in pcs response headers | 服务器返回值KEY不存在 |
|
||||
1630
docs/file_data_apis_list.md
Normal file
1630
docs/file_data_apis_list.md
Normal file
File diff suppressed because it is too large
Load Diff
191
docs/overview.md
Normal file
191
docs/overview.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# 概述
|
||||
|
||||
百度开放云平台为广大开发者提供了访问PCS资源的系列接口,目前开放的接口主要分两个部分:
|
||||
|
||||
* 文件API:
|
||||
|
||||
主要提供文件上传、下载、拷贝、删除、搜索、断点续传及缩略图等功能。
|
||||
|
||||
* 结构化数据API:
|
||||
|
||||
主要提供结构数据存储、查询、删除及同步等功能。
|
||||
|
||||
通过对这些API的组合调用,开发者可以实现基本的用户文件操作以及结构数据存储和管理功能,也能够支持用户数据在多种不同终端上的同步,以提供更优质的用户体验。
|
||||
|
||||
除了原生的REST(Representational State Transfer,即“表述性状态转移”) API之外,百度开放云平台还提供了多种平台的SDK来帮助开发者缩短开发周期,具体请参考“SDK”部分相关内容。
|
||||
|
||||
## PCS REST API使用说明
|
||||
|
||||
### 开通PCS API权限
|
||||
|
||||
PCS所有REST API都必须经过开通权限才能正常使用。申请的方法请参考“开通PCS API权限”部分相关内容。
|
||||
|
||||
注意:PCS未提供分享接口,download等接口仅供个人获取数据使用。 access_token不能泄露,否则会直接封禁应用。
|
||||
|
||||
### API请求方式说明
|
||||
|
||||
目前所有的提交类接口仅支持POST方式,查询类接口同时支持POST方式和GET方式。
|
||||
|
||||
PCS REST API的所有参数在传入时应当使用:UTF-8编码。
|
||||
|
||||
#### HTTP 请求方式
|
||||
|
||||
GET | POST
|
||||
|
||||
#### URL
|
||||
|
||||
https://pcs.baidu.com/rest/2.0/pcs/{object_name}?{query_string}
|
||||
|
||||
#### 参数说明
|
||||
|
||||
| 参数名称 | 描述 |
|
||||
| :- | :- |
|
||||
| object_name | PCS REST API操作实体名称,如:quota、file、thumbnail。 |
|
||||
| query_string | 放在HTTP头部传入的参数,必须经过UrlEncode编码。 |
|
||||
|
||||
#### HTTP GET和POST方式使用说明
|
||||
|
||||
<table width="600" border="1" cellpadding="1" cellspacing="1">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" width="80">请求方式
|
||||
</th>
|
||||
<th scope="col">GET
|
||||
</th>
|
||||
<th scope="col">POST
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">URL
|
||||
</th>
|
||||
<td colspan="2">https://pcs.baidu.com/rest/2.0/pcs/{object_name}?{query_string}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">请求参数
|
||||
</th>
|
||||
<td> 全部携带在 HTTPS 请求头部的 query_string 中。
|
||||
</td>
|
||||
<td> 既可携带在 query_string 中,也可携带在 HTTP Body 中。
|
||||
<dl>
|
||||
<dd>
|
||||
<ul>
|
||||
<li> method 及 access token 等参数必须携带在 query_string 中进行传输,请参考各个API的具体说明;
|
||||
</li>
|
||||
<li> 携带在 query_string 中的参数的值,必须进行 UrlEncode 编码;
|
||||
</li>
|
||||
<li> 携带在 HTTP Body 中的参数,则不需要进行 UrlEncode 编码。
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
<div style="border:solid 1px #d7d7d7;padding:10px 16px 2px 16px; background-color:#fbfafb;">
|
||||
<div>注</div>
|
||||
HTTP URL 长度有限,若参数值长度过长,建议将参数放在 HTTP Body 中进行传输。</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"> HTTP BODY
|
||||
</th>
|
||||
<td> 不携带HTTP Body
|
||||
</td>
|
||||
<td> multipart/form-data
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">注意
|
||||
</th>
|
||||
<td colspan="2">如果 HTTP Body 和 query_string 存在相同的参数,则以 query_string 中的参数为准。
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
#### 使用示例
|
||||
|
||||
1. GET请求:
|
||||
|
||||
用HTTP GET请求方式发送两个参数:key1=value1和key2=value2。
|
||||
|
||||
https://pcs.baidu.com/rest/2.0/pcs/quota?key1=UrlEncode(value1)&key2=UrlEncode(value2)
|
||||
|
||||
2. POST请求:
|
||||
|
||||
分别用两种方式使用POST方式发送三个参数:key1=value1、key2=value2和key3=value3;方式一与方式二效果等同。
|
||||
|
||||
##### 方式一:
|
||||
|
||||
POST /rest/2.0/pcs/quota?key2=value2&key3=value3 HTTP/1.1
|
||||
|
||||
User-Agent: curl/7.12.1 (x86_64-redhat-linux-gnu) libcurl/7.12.1 OpenSSL/0.9.7a zlib/1.2.1.2 libidn/0.5.6
|
||||
Pragma: no-cache
|
||||
Accept: */*
|
||||
Host:pcs.baidu.com
|
||||
Content-Length:123
|
||||
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
Content-Disposition: form-data; name="key1"
|
||||
value1
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ—
|
||||
|
||||
##### 方式二:
|
||||
|
||||
POST /rest/2.0/pcs/quota HTTP/1.1
|
||||
User-Agent: curl/7.12.1 (x86_64-redhat-linux-gnu) libcurl/7.12.1 OpenSSL/0.9.7a zlib/1.2.1.2 libidn/0.5.6
|
||||
Pragma: no-cache
|
||||
Accept: */*
|
||||
Host:pcs.baidu.com
|
||||
Content-Length:123
|
||||
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
Content-Disposition: form-data; name="key1"
|
||||
|
||||
value1
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
Content-Disposition: form-data; name="key2"
|
||||
|
||||
value2
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ
|
||||
Content-Disposition: form-data; name="key3"
|
||||
|
||||
value3
|
||||
------WebKitFormBoundaryS0JIa4uHF7yHd8xJ--
|
||||
|
||||
### API响应格式说明
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
</th>
|
||||
<th scope="col">正常请求
|
||||
</th>
|
||||
<th scope="col">异常请求
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">HTTP状态码
|
||||
</th>
|
||||
<td> 200 OK
|
||||
</td>
|
||||
<td> 4** : 用户请求错误。<br>5** :server服务失败。
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">HTTP BODY
|
||||
</th>
|
||||
<td> API响应内容
|
||||
</td>
|
||||
<td> 异常请求的返回值为JSON字符串。<br>例如:<br>{<br>"error_code":110,<br>"error_msg":"Access token invalid or no longer valid",<br>"request_id":729562373<br>}<br>说明:<br>
|
||||
<dl>
|
||||
<dd>- error_code:错误码;<br>
|
||||
</dd>
|
||||
<dd>- error_msg: 错误描述信息;<br>
|
||||
</dd>
|
||||
<dd>- request_id: 请求ID。由server生成,用于追查和定位请求日志。<br>
|
||||
</dd>
|
||||
</dl>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
2318
docs/structured_data_api_list.md
Normal file
2318
docs/structured_data_api_list.md
Normal file
File diff suppressed because it is too large
Load Diff
90
docs/structured_data_apis_error.md
Normal file
90
docs/structured_data_apis_error.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 结构化数据API错误码
|
||||
|
||||
结构化数据API以HTTP提供,因此请求者应首先检查HTTP协议级别的响应状态码。 当请求有错误或执行失败时,HTTP协议的返回响应状态码不为“200”,且在Content-body的JSON格式数据中以error_code给出错误码,并可能以error_msg字段提示错误信息。错误码的详细信息,请参考以下错误码列表。 一般来说, 错误分为客户端错误和服务器端错误:
|
||||
|
||||
* 客户端错误:
|
||||
返回状态码4xx表示结构化数据平台认为客户端请求有错误,例如:auth失败,参数不对,超出quota等。这些情况,客户端需要先解决参数问题,再向服务器端重新发起请求。
|
||||
|
||||
* 服务器端错误:
|
||||
返回状态码为5xx,表示结构化数据平台内部发生错误,客户端需要重试。
|
||||
|
||||
## 注意
|
||||
|
||||
请求成功时,HTTP协议的返回响应状态码为“200”,不会设置error_code和error_msg。
|
||||
|
||||
## 结构化数据API错误码
|
||||
|
||||
| HTTP状态码 | 错误码 | 错误信息 | 备注 | 是否重试 |
|
||||
| - | :- | :- | :- | - |
|
||||
| 500 | 1 | Unknown error | 未知错误 | 是 |
|
||||
| 500 | 2 | Service temporarily unavailable | 服务暂不可用 | 是 |
|
||||
| 403 | 6 | No permission to access user data | 无权访问用户数据 | 否 |
|
||||
| 403 | 7 | No permission to access data for this referer | 无权访问数据 | 否 |
|
||||
| 400 | 100 | Invalid parameter | 无效参数 | 否 |
|
||||
| 401 | 101 | Invalid API key | 无效API Key | 否 |
|
||||
| 401 | 102 | Session key invalid or no longer valid | 会话密钥无效 | 否 |
|
||||
| 401 | 103 | Invalid/Used call_id parameter | call_id参数无效/已被使用 | 否 |
|
||||
| 400 | 104 | Incorrect signature | 签名错误 | 否 |
|
||||
| 400 | 105 | Too many parameters | 参数过多 | 否 |
|
||||
| 400 | 106 | Unsupported signature method | 不支持此签名方式 | 否 |
|
||||
| 400 | 107 | Invalid/Used timestamp parameter | 时间戳无效 | 否 |
|
||||
| 401 | 108 | Invalid user id | 用户ID无效 | 否 |
|
||||
| 400 | 109 | Invalid user info field | 用户信息字段无效 | 否 |
|
||||
| 401 | 110 | Access token invalid or no longer valid | Access token无效或已失效 | 否 |
|
||||
| 401 | 111 | Access token expired | Access token已过期 | 否 |
|
||||
| 401 | 112 | Session key expired | 会话密钥已过期 | 否 |
|
||||
| 400 | 114 | Invalid Ip | 无效IP | 否 |
|
||||
| 400 | 31400 | param error | 参数错误 | 否 |
|
||||
| 400 | 31401 | malformed json | JSON格式错误 | 否 |
|
||||
| 400 | 31402 | no "table" in request | 请求中没有“table”字段 | 否 |
|
||||
| 400 | 31403 | no "records" in request | 请求中没有“records”字段 | 否 |
|
||||
| 400 | 31405 | too many records in request | 请求中的records 过多,目前限制为500 | 否 |
|
||||
| 400 | 31406 | bad columnname | 列名非法,请参考API文档 | 否 |
|
||||
| 400 | 31407 | record too large | record过大,> 1M | 否 |
|
||||
| 400 | 31408 | bad table name | table名称不合法 | 否 |
|
||||
| 400 | 31409 | table not exist | table不存在,请先创建 | 否 |
|
||||
| 400 | 31410 | bad record | record格式错误,请检查JSON | 否 |
|
||||
| 400 | 31411 | no appid | 请求中没有“app_id”字段 | 否 |
|
||||
| 400 | 31412 | no userid | 请求中没有“user_id”字段 | 否 |
|
||||
| 400 | 31420 | bad condition | condition描述错误。 | 否 |
|
||||
| 400 | 31421 | bad projection | projection描述错误 | 否 |
|
||||
| 400 | 31422 | bad order_by | order_by描述错误 | 否 |
|
||||
| 400 | 31423 | bad operator | condition中的operation 非法 | 否 |
|
||||
| 400 | 31424 | bad start/limit | start/limit 错误 | 否 |
|
||||
| 400 | 31425 | unsupported operator | 操作符暂未支持,如:or、like、regex等 | 否 |
|
||||
| 400 | 31430 | no key in record | update/delete 请求,但是record 中没有_key 字段 | 否 |
|
||||
| 400 | 31431 | record not exist | 符合条件的record不存在,比如if-match不匹配、在回收站等 | 否 |
|
||||
| 400 | 31432 | unknown op | 参数op非法 | 否 |
|
||||
| 400 | 31433 | bad key | key非法 | 否 |
|
||||
| 400 | 31440 | param cursor not set | 参数cursor未设值 | 否 |
|
||||
| 400 | 31441 | param cursor format error | 参数cursor格式错误 | 否 |
|
||||
| 400 | 31442 | param cursor appid wrong | 参数cursor appid错误 | 否 |
|
||||
| 400 | 31443 | param cursor user_id wrong | 参数cursor user_id错误 | 否 |
|
||||
| 400 | 31450 | exceed quota | 超出配额 | 否 |
|
||||
| 400 | 31451 | quota size param not exist | 找不到参数quota size | 否 |
|
||||
| 503 | 31452 | quota info fail | quota info失败 | 是 |
|
||||
| 400 | 31453 | quota too big | quota过大 | 否 |
|
||||
| 400 | 31454 | quota size param not numberic | quota size 参数未数值化 | 否 |
|
||||
| 400 | 31460 | no permission | 未授权 | 否 |
|
||||
| 400 | 31461 | account not login | 账户为登录,使用bduss认证失败 | 否 |
|
||||
| 400 | 31462 | access token errro | access token校验失败 | 否 |
|
||||
| 400 | 31470 | index num too much | index num太多 | 否 |
|
||||
| 400 | 31472 | table already exist | table已存在 | 否 |
|
||||
| 400 | 31473 | abnormal table already exist | 异常table已存在 | 否 |
|
||||
| 400 | 31474 | table not drop, cannot restore | table不在回收站,无法恢复 | 否 |
|
||||
| 400 | 31475 | engine not support | 不支持此项操作 | 否 |
|
||||
| 400 | 31480 | param op wrong, should be recycled or permanent | 参数op错误,应为可回收或永久的 | 否 |
|
||||
| 400 | 31490 | api not support | 调用了错误的API | 否 |
|
||||
| 500 | 31500 | Internal error (Try Again Later) | 内部错误 | 是 |
|
||||
| 503 | 31501 | storeengine construct fail | construct失败 | 是 |
|
||||
| 503 | 31502 | storeengine select fail | 选择操作失败 | 是 |
|
||||
| 503 | 31503 | storeengine insert fail | 插入操作失败 | 是 |
|
||||
| 503 | 31504 | storeengine update fail | 更新操作失败 | 是 |
|
||||
| 503 | 31505 | storeengine delete fail | 删除操作失败 | 是 |
|
||||
| 503 | 31506 | storeengine count fail | count操作失败 | 是 |
|
||||
| 503 | 31507 | storeengine ensure index fail | 查询或创建索引失败 | 是 |
|
||||
| 503 | 31508 | storeengine delete index fail | 删除索引失败 | 是 |
|
||||
| 503 | 31509 | storeengine drop table fail | 删除table操作失败 | 是 |
|
||||
| 503 | 31530 | config set num match fail | 配置中num匹配失败 | 是 |
|
||||
| 503 | 31590 | db query error | db交互出错 | 是 |
|
||||
| 503 | 31591 | network error | 内部网络交互错误 | 是 |
|
||||
128
docs/structured_data_apis_overview.md
Normal file
128
docs/structured_data_apis_overview.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 结构化数据API说明
|
||||
|
||||
## 结构化数据API简要说明
|
||||
|
||||
结构化数据API包括table操作API和record操作API。
|
||||
|
||||
* Table操作API:
|
||||
|
||||
是由开发者在使用结构化数据API开发应用时调用,包括创建、修改、删除、查看表信息及恢复表等接口;
|
||||
|
||||
* Record操作API:
|
||||
|
||||
即数据操作API,是由最终用户在使用基于PCS结构化数据API开发的应用时触发,第三方应用通过使用用户身份(以Access Token认证)在服务器端对用户数据进行增加、删除、修改、查询等系列操作。
|
||||
|
||||
## 结构化数据基本概念
|
||||
|
||||
1. 表(table)
|
||||
|
||||
结构化数据以表为单位组织用户数据;表名和表中的索引由开发者定义。
|
||||
|
||||
2. 表名(tablename)
|
||||
|
||||
表的名字。表名可以由字母和下划线组成(a-z,A-Z,0-9,'_'), 长度为3-63个字符,必须字母开头;同一个应用中,不能重复使用同一表名。
|
||||
|
||||
3. 记录(record)
|
||||
|
||||
每一条结构化数据就是一个记录,一个表中可有多条记录。
|
||||
|
||||
4. 列(column)
|
||||
|
||||
结构中的每一个字段就是一列。列名可以由字母和下划线组成(a-z,A-Z,0-9,'_'), 长度为1-255个字符,必须字母开头。
|
||||
|
||||
5. 索引(index)
|
||||
|
||||
在表中的某些列上创建索引,可以提高查询速度。一个表中,最多可以定义5个索引,只能定义普通索引。
|
||||
|
||||
6. 限额(quota)
|
||||
|
||||
同一个表中同一个用户最多多少条数据,现在每个用户最多10000条数据。
|
||||
|
||||
## 结构化数据功能列表
|
||||
|
||||
使用PCS结构化数据功能,可以轻松的完成应用上结构化数据的存储及同步等功能。
|
||||
|
||||
PCS结构化数据具有如下的特性:
|
||||
|
||||
* 提供Restful API;
|
||||
|
||||
* 数据灵活性大:定义表时,不需要完全确定表中的所有列,可以增加一列或减少一列而无需重新定义表。
|
||||
|
||||
* 在数据上动态增减索引:定义表时,无需完全确定所有的索引;
|
||||
|
||||
* 数据多端同步:结构化数据提供接口,支持数据多端同步;
|
||||
|
||||
* 对数据进行增加、删除、修改及查询;
|
||||
|
||||
* 回收站:数据删除后,10天内可以还原;
|
||||
|
||||
* 支持应用数据隔离:不同应用的数据可以支持物理隔离;
|
||||
|
||||
* 支持超大数据量:
|
||||
* 一个应用可以创建5个表;
|
||||
* 单个表没有数据总量限制;
|
||||
* 单个表中,单个用户的数据量可以达到1万条;
|
||||
* 用户数无限制。
|
||||
|
||||
## 结构化数据存储限制
|
||||
|
||||
### (1)全局限制:
|
||||
|
||||
每个记录的大小限制为1MB (服务器端会把记录做json_encode, 得到的大小为记录大小);
|
||||
|
||||
每个应用最多只能创建5个表;
|
||||
|
||||
批量的insert/update/delete/restore每次最多500条;
|
||||
|
||||
批量的select每次最多返回10000条记录。
|
||||
|
||||
### (2)表级别限制(quota):
|
||||
|
||||
应用上的用户在表里的条目数限制(创建表的时候,每个表都有一个default_quota,以后用户在该表上都使用此default_quota,单个用户的quota可以通过quota接口设置)。
|
||||
|
||||
## Record操作API说明
|
||||
|
||||
### (1) insert、update、delete默认都支持批量操作,但是每次批量的record个数不能超过500;
|
||||
|
||||
### (2) 数据操作API对用户需要保持最终一致性,即:
|
||||
|
||||
insert、update、delete操作不保证返回后即可select 出来,通常延迟小于1s;
|
||||
|
||||
update、delete操作不支持条件操作,即where子句的操作,如有需要请先select出来。
|
||||
|
||||
## 字段说明
|
||||
|
||||
### 字段定义
|
||||
|
||||
#### PCS结构化数据平台中,表中的字段支持如下类型:
|
||||
|
||||
| 字段类型 | 描述 |
|
||||
| :- | :- |
|
||||
| string | 字符串。 |
|
||||
| int | 整型数字,64位有符号,范围为[-9223372036854775808, 9223372036854775807],超出范围自动转为float。 |
|
||||
| float | 带小数点的数字。 |
|
||||
| boolean | true 或者false。 |
|
||||
| array | 数组,元素可以是string/number。 |
|
||||
| object | 可以是一个json 对象。 |
|
||||
| null | null |
|
||||
|
||||
#### 说明:
|
||||
|
||||
字段名必须符合以下标准:
|
||||
|
||||
可以是a-z、A-Z、0-9、'_';
|
||||
|
||||
长度范围是[1, 255];
|
||||
|
||||
必须字母开头。
|
||||
|
||||
### 默认字段
|
||||
|
||||
#### 插入的记录将会有以下默认字段:
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
| :- | :- | :- |
|
||||
| _key | string | 全局唯一key i string(16)。 |
|
||||
| _ctime | int | record创建时间。 |
|
||||
| _mtime | int | record修改时间。 |
|
||||
| _isdelete | int | 是否删除。 |
|
||||
16
entitlements.xml
Normal file
16
entitlements.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>com.iikira.baidupcsgo</string>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<key>platform-application</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>com.iikira.baidupcsgo</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
28
go.mod
Normal file
28
go.mod
Normal file
@@ -0,0 +1,28 @@
|
||||
module github.com/iikira/BaiduPCS-Go
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/GeertJohan/go.incremental v1.0.0
|
||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da // indirect
|
||||
github.com/astaxie/beego v1.10.1 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/fatih/color v0.0.0-20150510220652-1b35f289c47d
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/iikira/Baidu-Login v1.2.2-0.20180427090606-ecf146973528
|
||||
github.com/iikira/baidu-tools v0.0.0-20190609113215-4dd64618064d
|
||||
github.com/json-iterator/go v1.1.7
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/oleiade/lane v0.0.0-20160817071224-3053869314bb
|
||||
github.com/olekukonko/tablewriter v0.0.2-0.20190618033246-cc27d85e17ce
|
||||
github.com/peterh/liner v1.1.1-0.20190305032635-6f820f8f90ce
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
|
||||
github.com/urfave/cli v1.21.1-0.20190817182405-23c83030263f
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||
golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339
|
||||
github.com/tidwall/gjson v1.6.0
|
||||
)
|
||||
96
go.sum
Normal file
96
go.sum
Normal file
@@ -0,0 +1,96 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da h1:UVU3a9pRUyLdnBtn60WjRl0s4SEyJc2ChCY56OAR6wI=
|
||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
|
||||
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/astaxie/beego v1.10.1 h1:M2ciUnyiZycuTpGEA+idJF0gX24h58EbPvGqjnO/DCg=
|
||||
github.com/astaxie/beego v1.10.1/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb h1:tUf55Po0vzOendQ7NWytcdK0VuzQmfAgvGBUOQvN0WA=
|
||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE=
|
||||
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/fatih/color v0.0.0-20150510220652-1b35f289c47d h1:wMOLsfJ92CRGk3+GtVk+aaUme+jWTH21jH3cVL1jzmI=
|
||||
github.com/fatih/color v0.0.0-20150510220652-1b35f289c47d/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/iikira/Baidu-Login v0.0.0-20180427090606-ecf146973528 h1:WaZCDfR+C0z17lO7syZFXpJ7d8YUxv5iG1/PeYzjKiM=
|
||||
github.com/iikira/Baidu-Login v0.0.0-20180427090606-ecf146973528/go.mod h1:oMWxZOoEMgQG+0rq8xhEIdxvXwkyG4aN3sZoOQ59R4Q=
|
||||
github.com/iikira/Baidu-Login v1.2.2-0.20180427090606-ecf146973528 h1:An+h6HX8d2omGXt69E5bsMjOmOgbuFQtrddp2clNCOU=
|
||||
github.com/iikira/Baidu-Login v1.2.2-0.20180427090606-ecf146973528/go.mod h1:oMWxZOoEMgQG+0rq8xhEIdxvXwkyG4aN3sZoOQ59R4Q=
|
||||
github.com/iikira/baidu-tools v0.0.0-20180922000818-79c93d8033cd h1:meH/UWnJi8GuWEjMFivCnwp2NWdqhDnTsIIkb5SptZQ=
|
||||
github.com/iikira/baidu-tools v0.0.0-20180922000818-79c93d8033cd/go.mod h1:UHD1R9RGXz5nDsKm3KbdD3yxCJZ18nmR+1ZAc5GkrNw=
|
||||
github.com/iikira/baidu-tools v0.0.0-20190609113215-4dd64618064d h1:nmsuPx1si1QI3boPuaQjxp536X7RAeLW26DBmcdRXO0=
|
||||
github.com/iikira/baidu-tools v0.0.0-20190609113215-4dd64618064d/go.mod h1:UHD1R9RGXz5nDsKm3KbdD3yxCJZ18nmR+1ZAc5GkrNw=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae h1:575usOHCDzxtuyWeSjPySew7uvoOuaCsM0uUesjgAo4=
|
||||
github.com/mattn/go-runewidth v0.0.5-0.20181218000649-703b5e6b11ae/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/oleiade/lane v0.0.0-20160817071224-3053869314bb h1:x0yCvYsspui5SAxSRvLd2zFg7PfFijzKdCo7QAtN92I=
|
||||
github.com/oleiade/lane v0.0.0-20160817071224-3053869314bb/go.mod h1:ym0w0flrmBtGvApLDgFLa0sfGJkWxDQqnm0/0ok5w3Y=
|
||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.2-0.20190618033246-cc27d85e17ce h1:RLmZmfx/K62HKpbwPqtW3tg+V2GgugN/XNNx+uiMH/Y=
|
||||
github.com/olekukonko/tablewriter v0.0.2-0.20190618033246-cc27d85e17ce/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
github.com/peterh/liner v1.1.0 h1:f+aAedNJA6uk7+6rXsYBnhdo4Xux7ESLe+kcuVUF5os=
|
||||
github.com/peterh/liner v1.1.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
||||
github.com/peterh/liner v1.1.1-0.20190305032635-6f820f8f90ce h1:Lz+a/i+oS4A7tb6J6IyH4ZFiWgqvNv2yslv0Qn79wok=
|
||||
github.com/peterh/liner v1.1.1-0.20190305032635-6f820f8f90ce/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.21.1-0.20190817182405-23c83030263f h1:xKDKjIsL76VUyHcA0G4Qe1cIAUB/nrq6Pt8D411bd1g=
|
||||
github.com/urfave/cli v1.21.1-0.20190817182405-23c83030263f/go.mod h1:qXyCeJubPqsgeiLd3kvHOGHHSrQcNdjZ2ScXIcVZK/I=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339 h1:zSqWKgm/o7HAnlAzBQ+aetp9fpuyytsXnKA8eiLHYQM=
|
||||
golang.org/x/sys v0.0.0-20191025090151-53bf42e6b339/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
37
internal/pcscommand/cd.go
Normal file
37
internal/pcscommand/cd.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
)
|
||||
|
||||
// RunChangeDirectory 执行更改工作目录
|
||||
func RunChangeDirectory(targetPath string, isList bool) {
|
||||
pcs := GetBaiduPCS()
|
||||
err := matchPathByShellPatternOnce(&targetPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := pcs.FilesDirectoriesMeta(targetPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if !data.Isdir {
|
||||
fmt.Printf("错误: %s 不是一个目录 (文件夹)\n", targetPath)
|
||||
return
|
||||
}
|
||||
|
||||
GetActiveUser().Workdir = targetPath
|
||||
pcsconfig.Config.Save()
|
||||
|
||||
fmt.Printf("改变工作目录: %s\n", targetPath)
|
||||
|
||||
if isList {
|
||||
RunLs(".", nil, baidupcs.DefaultOrderOptions)
|
||||
}
|
||||
}
|
||||
90
internal/pcscommand/cloud_dl.go
Normal file
90
internal/pcscommand/cloud_dl.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
)
|
||||
|
||||
// RunCloudDlAddTask 执行添加离线下载任务
|
||||
func RunCloudDlAddTask(sourceURLs []string, savePath string) {
|
||||
var (
|
||||
err error
|
||||
pcs = GetBaiduPCS()
|
||||
)
|
||||
err = matchPathByShellPatternOnce(&savePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var taskid int64
|
||||
for k := range sourceURLs {
|
||||
taskid, err = pcs.CloudDlAddTask(sourceURLs[k], savePath+baidupcs.PathSeparator)
|
||||
if err != nil {
|
||||
fmt.Printf("[%d] %s, 地址: %s\n", k+1, err, sourceURLs[k])
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("[%d] 添加离线任务成功, 任务ID(task_id): %d, 源地址: %s, 保存路径: %s\n", k+1, taskid, sourceURLs[k], savePath)
|
||||
}
|
||||
}
|
||||
|
||||
// RunCloudDlQueryTask 精确查询离线下载任务
|
||||
func RunCloudDlQueryTask(taskIDs []int64) {
|
||||
cl, err := GetBaiduPCS().CloudDlQueryTask(taskIDs)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(cl)
|
||||
}
|
||||
|
||||
// RunCloudDlListTask 查询离线下载任务列表
|
||||
func RunCloudDlListTask() {
|
||||
cl, err := GetBaiduPCS().CloudDlListTask()
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(cl)
|
||||
}
|
||||
|
||||
// RunCloudDlCancelTask 取消离线下载任务
|
||||
func RunCloudDlCancelTask(taskIDs []int64) {
|
||||
for _, id := range taskIDs {
|
||||
err := GetBaiduPCS().CloudDlCancelTask(id)
|
||||
if err != nil {
|
||||
fmt.Printf("[%d] %s\n", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("[%d] 取消成功\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// RunCloudDlDeleteTask 删除离线下载任务
|
||||
func RunCloudDlDeleteTask(taskIDs []int64) {
|
||||
for _, id := range taskIDs {
|
||||
err := GetBaiduPCS().CloudDlDeleteTask(id)
|
||||
if err != nil {
|
||||
fmt.Printf("[%d] %s\n", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("[%d] 删除成功\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// RunCloudDlClearTask 清空离线下载任务记录
|
||||
func RunCloudDlClearTask() {
|
||||
total, err := GetBaiduPCS().CloudDlClearTask()
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s成功, 共清除 %d 条记录\n", baidupcs.OperationCloudDlClearTask, total)
|
||||
return
|
||||
}
|
||||
155
internal/pcscommand/cp_mv.go
Normal file
155
internal/pcscommand/cp_mv.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RunCopy 执行 批量拷贝文件/目录
|
||||
func RunCopy(paths ...string) {
|
||||
runCpMvOp("copy", paths...)
|
||||
}
|
||||
|
||||
// RunMove 执行 批量 重命名/移动 文件/目录
|
||||
func RunMove(paths ...string) {
|
||||
runCpMvOp("move", paths...)
|
||||
}
|
||||
|
||||
func runCpMvOp(op string, paths ...string) {
|
||||
err := cpmvPathValid(paths...) // 检查路径的有效性, 目前只是判断数量
|
||||
if err != nil {
|
||||
fmt.Printf("%s path error, %s\n", op, err)
|
||||
return
|
||||
}
|
||||
|
||||
froms, to := cpmvParsePath(paths...) // 分割
|
||||
|
||||
froms, err = matchPathByShellPattern(froms...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
to = GetActiveUser().PathJoin(to)
|
||||
|
||||
// 尝试匹配
|
||||
if strings.ContainsAny(to, baidupcs.ShellPatternCharacters) {
|
||||
tos, _ := matchPathByShellPattern(to)
|
||||
|
||||
switch len(tos) {
|
||||
case 0:
|
||||
// do nothing
|
||||
case 1:
|
||||
to = tos[0]
|
||||
default:
|
||||
fmt.Printf("目标目录有 %d 条匹配结果, 请检查通配符\n", len(tos))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pcs := GetBaiduPCS()
|
||||
toInfo, pcsError := pcs.FilesDirectoriesMeta(to)
|
||||
switch {
|
||||
case toInfo != nil && toInfo.Path != to:
|
||||
fallthrough
|
||||
case pcsError != nil && pcsError.GetErrType() == pcserror.ErrTypeRemoteError:
|
||||
// 判断路径是否存在
|
||||
// 如果不存在, 则为重命名或同目录拷贝操作
|
||||
|
||||
// 如果 froms 数不是1, 则意义不明确.
|
||||
if len(froms) != 1 {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if op == "copy" { // 拷贝
|
||||
err = pcs.Copy(&baidupcs.CpMvJSON{
|
||||
From: froms[0],
|
||||
To: to,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("文件/目录拷贝失败: ")
|
||||
fmt.Printf("%s <-> %s\n", froms[0], to)
|
||||
return
|
||||
}
|
||||
fmt.Println("文件/目录拷贝成功: ")
|
||||
fmt.Printf("%s <-> %s\n", froms[0], to)
|
||||
} else { // 重命名
|
||||
err = pcs.Rename(froms[0], to)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("重命名失败: ")
|
||||
fmt.Printf("%s -> %s\n", froms[0], to)
|
||||
return
|
||||
}
|
||||
fmt.Println("重命名成功: ")
|
||||
fmt.Printf("%s -> %s\n", froms[0], to)
|
||||
}
|
||||
return
|
||||
case pcsError != nil && pcsError.GetErrType() != pcserror.ErrTypeRemoteError:
|
||||
fmt.Println(pcsError)
|
||||
return
|
||||
}
|
||||
|
||||
if !toInfo.Isdir {
|
||||
fmt.Printf("目标 %s 不是一个目录, 操作失败\n", toInfo.Path)
|
||||
return
|
||||
}
|
||||
|
||||
cj := new(baidupcs.CpMvListJSON)
|
||||
cj.List = make([]*baidupcs.CpMvJSON, len(froms))
|
||||
for k := range froms {
|
||||
cj.List[k] = &baidupcs.CpMvJSON{
|
||||
From: froms[k],
|
||||
To: path.Clean(to + baidupcs.PathSeparator + path.Base(froms[k])),
|
||||
}
|
||||
}
|
||||
|
||||
switch op {
|
||||
case "copy":
|
||||
err = pcs.Copy(cj.List...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("操作失败, 以下文件/目录拷贝失败: ")
|
||||
fmt.Println(cj)
|
||||
return
|
||||
}
|
||||
fmt.Println("操作成功, 以下文件/目录拷贝成功: ")
|
||||
fmt.Println(cj)
|
||||
case "move":
|
||||
err = pcs.Move(cj.List...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("操作失败, 以下文件/目录移动失败: ")
|
||||
fmt.Println(cj)
|
||||
return
|
||||
}
|
||||
fmt.Println("操作成功, 以下文件/目录移动成功: ")
|
||||
fmt.Println(cj)
|
||||
default:
|
||||
panic("Unknown operation:" + op)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cpmvPathValid 检查路径的有效性
|
||||
func cpmvPathValid(paths ...string) (err error) {
|
||||
if len(paths) <= 1 {
|
||||
return fmt.Errorf("参数不完整")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cpmvParsePath 解析路径
|
||||
func cpmvParsePath(paths ...string) (froms []string, to string) {
|
||||
if len(paths) == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
froms = paths[:len(paths)-1]
|
||||
to = paths[len(paths)-1]
|
||||
return
|
||||
}
|
||||
183
internal/pcscommand/download.go
Normal file
183
internal/pcscommand/download.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsfunctions/pcsdownload"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/taskframework"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/downloader"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/transfer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type (
|
||||
//DownloadOptions 下载可选参数
|
||||
DownloadOptions struct {
|
||||
IsTest bool
|
||||
IsPrintStatus bool
|
||||
IsExecutedPermission bool
|
||||
IsOverwrite bool
|
||||
DownloadMode pcsdownload.DownloadMode
|
||||
SaveTo string
|
||||
Parallel int
|
||||
Load int
|
||||
MaxRetry int
|
||||
NoCheck bool
|
||||
}
|
||||
|
||||
// LocateDownloadOption 获取下载链接可选参数
|
||||
LocateDownloadOption struct {
|
||||
FromPan bool
|
||||
}
|
||||
)
|
||||
|
||||
func downloadPrintFormat(load int) string {
|
||||
if load <= 1 {
|
||||
return pcsdownload.DefaultPrintFormat
|
||||
}
|
||||
return "[%d] ↓ %s/%s %s/s in %s, left %s ...\n"
|
||||
}
|
||||
|
||||
// RunDownload 执行下载网盘内文件
|
||||
func RunDownload(paths []string, options *DownloadOptions) {
|
||||
if options == nil {
|
||||
options = &DownloadOptions{}
|
||||
}
|
||||
|
||||
if options.Load <= 0 {
|
||||
options.Load = pcsconfig.Config.MaxDownloadLoad
|
||||
}
|
||||
|
||||
if options.MaxRetry < 0 {
|
||||
options.MaxRetry = pcsdownload.DefaultDownloadMaxRetry
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// windows下不加执行权限
|
||||
options.IsExecutedPermission = false
|
||||
}
|
||||
|
||||
// 设置下载配置
|
||||
cfg := &downloader.Config{
|
||||
Mode: transfer.RangeGenMode_BlockSize,
|
||||
CacheSize: pcsconfig.Config.CacheSize,
|
||||
BlockSize: baidupcs.MaxDownloadRangeSize,
|
||||
MaxRate: pcsconfig.Config.MaxDownloadRate,
|
||||
InstanceStateStorageFormat: downloader.InstanceStateStorageFormatProto3,
|
||||
IsTest: options.IsTest,
|
||||
TryHTTP: !pcsconfig.Config.EnableHTTPS,
|
||||
}
|
||||
|
||||
// 设置下载最大并发量
|
||||
if options.Parallel < 1 {
|
||||
options.Parallel = pcsconfig.Config.MaxParallel
|
||||
}
|
||||
|
||||
paths, err := matchPathByShellPattern(paths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print("\n")
|
||||
fmt.Printf("[0] 提示: 当前下载最大并发量为: %d, 下载缓存为: %d\n", options.Parallel, cfg.CacheSize)
|
||||
|
||||
var (
|
||||
pcs = GetBaiduPCS()
|
||||
loadCount = 0
|
||||
)
|
||||
|
||||
// 预测要下载的文件数量
|
||||
// TODO: pcscache
|
||||
for k := range paths {
|
||||
pcs.FilesDirectoriesRecurseList(paths[k], baidupcs.DefaultOrderOptions, func(depth int, _ string, fd *baidupcs.FileDirectory, pcsError pcserror.Error) bool {
|
||||
if pcsError != nil {
|
||||
pcsCommandVerbose.Warnf("%s\n", pcsError)
|
||||
return true
|
||||
}
|
||||
|
||||
// 忽略统计文件夹数量
|
||||
if !fd.Isdir {
|
||||
loadCount++
|
||||
if loadCount >= options.Load {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if loadCount >= options.Load {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 修改Load, 设置MaxParallel
|
||||
if loadCount > 0 {
|
||||
options.Load = loadCount
|
||||
// 取平均值
|
||||
cfg.MaxParallel = pcsconfig.AverageParallel(options.Parallel, loadCount)
|
||||
} else {
|
||||
cfg.MaxParallel = options.Parallel
|
||||
}
|
||||
|
||||
var (
|
||||
executor = taskframework.TaskExecutor{
|
||||
IsFailedDeque: true, // 统计失败的列表
|
||||
}
|
||||
statistic = &pcsdownload.DownloadStatistic{}
|
||||
)
|
||||
// 处理队列
|
||||
for k := range paths {
|
||||
newCfg := *cfg
|
||||
unit := pcsdownload.DownloadTaskUnit{
|
||||
Cfg: &newCfg, // 复制一份新的cfg
|
||||
PCS: pcs,
|
||||
VerbosePrinter: pcsCommandVerbose,
|
||||
PrintFormat: downloadPrintFormat(options.Load),
|
||||
ParentTaskExecutor: &executor,
|
||||
DownloadStatistic: statistic,
|
||||
IsPrintStatus: options.IsPrintStatus,
|
||||
IsExecutedPermission: options.IsExecutedPermission,
|
||||
IsOverwrite: options.IsOverwrite,
|
||||
NoCheck: options.NoCheck,
|
||||
DownloadMode: options.DownloadMode,
|
||||
PcsPath: paths[k],
|
||||
}
|
||||
|
||||
// 设置储存的路径
|
||||
if options.SaveTo != "" {
|
||||
unit.SavePath = filepath.Join(options.SaveTo, filepath.Base(paths[k]))
|
||||
} else {
|
||||
// 使用默认的保存路径
|
||||
unit.SavePath = GetActiveUser().GetSavePath(paths[k])
|
||||
}
|
||||
info := executor.Append(&unit, options.MaxRetry)
|
||||
fmt.Printf("[%s] 加入下载队列: %s\n", info.Id(), paths[k])
|
||||
}
|
||||
|
||||
// 开始计时
|
||||
statistic.StartTimer()
|
||||
|
||||
// 开始执行
|
||||
executor.Execute()
|
||||
|
||||
fmt.Printf("\n下载结束, 时间: %s, 数据总量: %s\n", statistic.Elapsed()/1e6*1e6, converter.ConvertFileSize(statistic.TotalSize()))
|
||||
|
||||
// 输出失败的文件列表
|
||||
failedList := executor.FailedDeque()
|
||||
if failedList.Size() != 0 {
|
||||
fmt.Printf("以下文件下载失败: \n")
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
for e := failedList.Shift(); e != nil; e = failedList.Shift() {
|
||||
item := e.(*taskframework.TaskInfoItem)
|
||||
tb.Append([]string{item.Info.Id(), item.Unit.(*pcsdownload.DownloadTaskUnit).PcsPath})
|
||||
}
|
||||
tb.Render()
|
||||
}
|
||||
}
|
||||
216
internal/pcscommand/export.go
Normal file
216
internal/pcscommand/export.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/pcstime"
|
||||
)
|
||||
|
||||
type (
|
||||
etask struct {
|
||||
*ListTask
|
||||
path string
|
||||
rootPath string
|
||||
fd *baidupcs.FileDirectory
|
||||
err pcserror.Error
|
||||
}
|
||||
|
||||
// ExportOptions 导出可选项
|
||||
ExportOptions struct {
|
||||
RootPath string // 根路径
|
||||
SavePath string // 输出路径
|
||||
MaxRetry int
|
||||
Recursive bool
|
||||
LinkFormat bool
|
||||
}
|
||||
)
|
||||
|
||||
func (task *etask) handleExportTaskError(l *list.List, failedList *list.List) {
|
||||
if task.err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 不重试
|
||||
switch task.err.GetError() {
|
||||
case baidupcs.ErrGetRapidUploadInfoMD5NotFound, baidupcs.ErrGetRapidUploadInfoCrc32NotFound:
|
||||
fmt.Printf("[%d] - [%s] 导出失败, 可能是服务器未刷新文件的md5, 请过一段时间再试一试\n", task.ID, task.path)
|
||||
failedList.PushBack(task)
|
||||
return
|
||||
case baidupcs.ErrFileTooLarge:
|
||||
fmt.Printf("[%d] - [%s] 导出失败, 文件大于20GB, 无法导出\n", task.ID, task.path)
|
||||
failedList.PushBack(task)
|
||||
return
|
||||
}
|
||||
|
||||
// 未达到失败重试最大次数, 将任务推送到队列末尾
|
||||
if task.retry < task.MaxRetry {
|
||||
task.retry++
|
||||
fmt.Printf("[%d] - [%s] 导出错误, %s, 重试 %d/%d\n", task.ID, task.path, task.err, task.retry, task.MaxRetry)
|
||||
l.PushBack(task)
|
||||
time.Sleep(3 * time.Duration(task.retry) * time.Second)
|
||||
} else {
|
||||
fmt.Printf("[%d] - [%s] 导出错误, %s\n", task.ID, task.path, task.err)
|
||||
failedList.PushBack(task)
|
||||
}
|
||||
}
|
||||
|
||||
func changeRootPath(dstRootPath, dstPath, srcRootPath string) string {
|
||||
if srcRootPath == "" {
|
||||
return dstPath
|
||||
}
|
||||
return path.Join(srcRootPath, strings.TrimPrefix(dstPath, dstRootPath))
|
||||
}
|
||||
|
||||
// GetExportFilename 获取导出路径
|
||||
func GetExportFilename() string {
|
||||
return "BaiduPCS-Go_export_" + pcstime.BeijingTimeOption("") + ".txt"
|
||||
}
|
||||
|
||||
// RunExport 执行导出文件和目录
|
||||
func RunExport(pcspaths []string, opt *ExportOptions) {
|
||||
if opt == nil {
|
||||
opt = &ExportOptions{}
|
||||
}
|
||||
|
||||
if opt.SavePath == "" {
|
||||
opt.SavePath = GetExportFilename()
|
||||
}
|
||||
|
||||
pcspaths, err := matchPathByShellPattern(pcspaths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
saveFile, err := os.OpenFile(opt.SavePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
|
||||
if err != nil { // 不可写
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
defer saveFile.Close()
|
||||
fmt.Printf("导出的信息将保存在: %s\n", opt.SavePath)
|
||||
|
||||
var (
|
||||
au = GetActiveUser()
|
||||
pcs = GetBaiduPCS()
|
||||
l = list.New()
|
||||
failedList = list.New()
|
||||
writeErr error
|
||||
id int
|
||||
)
|
||||
|
||||
for id = range pcspaths {
|
||||
var rootPath string
|
||||
if pcspaths[id] == au.Workdir {
|
||||
rootPath = pcspaths[id]
|
||||
} else {
|
||||
rootPath = path.Dir(pcspaths[id])
|
||||
}
|
||||
// 加入队列
|
||||
l.PushBack(&etask{
|
||||
ListTask: &ListTask{
|
||||
ID: id,
|
||||
MaxRetry: opt.MaxRetry,
|
||||
},
|
||||
path: pcspaths[id],
|
||||
rootPath: rootPath,
|
||||
})
|
||||
}
|
||||
|
||||
for {
|
||||
e := l.Front()
|
||||
if e == nil { // 结束
|
||||
break
|
||||
}
|
||||
|
||||
l.Remove(e) // 载入任务后, 移除队列
|
||||
|
||||
task := e.Value.(*etask)
|
||||
root := task.fd == nil
|
||||
|
||||
// 获取文件信息
|
||||
if task.fd == nil { // 第一次初始化
|
||||
fd, pcsError := pcs.FilesDirectoriesMeta(task.path)
|
||||
if pcsError != nil {
|
||||
task.err = pcsError
|
||||
task.handleExportTaskError(l, failedList)
|
||||
continue
|
||||
}
|
||||
task.fd = fd
|
||||
}
|
||||
|
||||
if task.fd.Isdir { // 导出目录
|
||||
if !root && !opt.Recursive { // 非递归
|
||||
continue
|
||||
}
|
||||
|
||||
fds, pcsError := pcs.FilesDirectoriesList(task.path, baidupcs.DefaultOrderOptions)
|
||||
if pcsError != nil {
|
||||
task.err = pcsError
|
||||
task.handleExportTaskError(l, failedList)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(fds) == 0 {
|
||||
_, writeErr = saveFile.Write(converter.ToBytes(fmt.Sprintf("BaiduPCS-Go mkdir \"%s\"\n", changeRootPath(task.rootPath, task.path, opt.RootPath))))
|
||||
if writeErr != nil {
|
||||
fmt.Printf("写入文件失败: %s\n", writeErr)
|
||||
return // 直接返回
|
||||
}
|
||||
fmt.Printf("[%d] - [%s] 导出成功\n", task.ID, task.path)
|
||||
continue
|
||||
}
|
||||
|
||||
// 加入队列
|
||||
for _, fd := range fds {
|
||||
// 加入队列
|
||||
id++
|
||||
l.PushBack(&etask{
|
||||
ListTask: &ListTask{
|
||||
ID: id,
|
||||
MaxRetry: opt.MaxRetry,
|
||||
},
|
||||
path: fd.Path,
|
||||
fd: fd,
|
||||
rootPath: task.rootPath,
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
rinfo, pcsError := pcs.ExportByFileInfo(task.fd)
|
||||
if pcsError != nil {
|
||||
task.err = pcsError
|
||||
task.handleExportTaskError(l, failedList)
|
||||
continue
|
||||
}
|
||||
var outTemplate string = fmt.Sprintf("BaiduPCS-Go rapidupload -length=%d -md5=%s -slicemd5=%s -crc32=%s \"%s\"\n", rinfo.ContentLength, rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentCrc32, changeRootPath(task.rootPath, task.path, opt.RootPath))
|
||||
if opt.LinkFormat {
|
||||
outTemplate = fmt.Sprintf("%s#%s#%d#%s\n", rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentLength, path.Base(task.path))
|
||||
}
|
||||
_, writeErr = saveFile.Write(converter.ToBytes(outTemplate))
|
||||
if writeErr != nil {
|
||||
fmt.Printf("写入文件失败: %s\n", writeErr)
|
||||
return // 直接返回
|
||||
}
|
||||
|
||||
fmt.Printf("[%d] - [%s] 导出成功\n", task.ID, task.path)
|
||||
}
|
||||
|
||||
if failedList.Len() > 0 {
|
||||
fmt.Printf("\n以下目录导出失败: \n")
|
||||
fmt.Printf("%s\n", strings.Repeat("-", 100))
|
||||
for e := failedList.Front(); e != nil; e = e.Next() {
|
||||
et := e.Value.(*etask)
|
||||
fmt.Printf("[%d] %s\n", et.ID, et.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
internal/pcscommand/fixmd5.go
Normal file
36
internal/pcscommand/fixmd5.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
)
|
||||
|
||||
// RunFixMD5 执行修复md5
|
||||
func RunFixMD5(pcspaths ...string) {
|
||||
absPaths, err := matchPathByShellPattern(pcspaths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
pcs := GetBaiduPCS()
|
||||
finfoList, err := pcs.FilesDirectoriesBatchMeta(absPaths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for k, finfo := range finfoList {
|
||||
err := pcs.FixMD5ByFileInfo(finfo)
|
||||
if err == nil {
|
||||
fmt.Printf("[%d] - [%s] 修复md5成功\n", k, finfo.Path)
|
||||
continue
|
||||
}
|
||||
|
||||
if err.GetError() == baidupcs.ErrFixMD5Failed {
|
||||
fmt.Printf("[%d] - [%s] 修复md5失败, 可能是服务器未刷新\n", k, finfo.Path)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("[%d] - [%s] 修复md5失败, 错误信息: %s\n", k, finfo.Path, err)
|
||||
}
|
||||
}
|
||||
82
internal/pcscommand/locatedonwload.go
Normal file
82
internal/pcscommand/locatedonwload.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RunLocateDownload 执行获取直链
|
||||
func RunLocateDownload(pcspaths []string, opt *LocateDownloadOption) {
|
||||
if opt == nil {
|
||||
opt = &LocateDownloadOption{}
|
||||
}
|
||||
|
||||
absPaths, err := matchPathByShellPattern(pcspaths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
pcs := GetBaiduPCS()
|
||||
|
||||
if opt.FromPan {
|
||||
fds, err := pcs.FilesDirectoriesBatchMeta(absPaths...)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fidList := make([]int64, 0, len(fds))
|
||||
for i := range fds {
|
||||
fidList = append(fidList, fds[i].FsID)
|
||||
}
|
||||
|
||||
list, err := pcs.LocatePanAPIDownload(fidList...)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"#", "fs_id", "路径", "链接"})
|
||||
|
||||
var (
|
||||
i int
|
||||
fidStrList = converter.SliceInt64ToString(fidList)
|
||||
)
|
||||
for k := range fidStrList {
|
||||
for i = range list {
|
||||
if fidStrList[k] == list[i].FsID {
|
||||
tb.Append([]string{strconv.Itoa(k), list[i].FsID, fds[k].Path, list[i].Dlink})
|
||||
list = append(list[:i], list[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
tb.Render()
|
||||
fmt.Printf("\n注意: 以上链接不能直接访问, 需要登录百度帐号才可以下载\n")
|
||||
return
|
||||
}
|
||||
|
||||
for i, pcspath := range absPaths {
|
||||
info, err := pcs.LocateDownload(pcspath)
|
||||
if err != nil {
|
||||
fmt.Printf("[%d] %s, 路径: %s\n", i, err, pcspath)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("[%d] %s: \n", i, pcspath)
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"#", "链接"})
|
||||
for k, u := range info.URLStrings(pcsconfig.Config.EnableHTTPS) {
|
||||
tb.Append([]string{strconv.Itoa(k), u.String()})
|
||||
}
|
||||
tb.Render()
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Printf("提示: 访问下载链接, 需将下载器的 User-Agent 设置为: %s\n", pcsconfig.Config.PanUA)
|
||||
}
|
||||
151
internal/pcscommand/login.go
Normal file
151
internal/pcscommand/login.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
|
||||
baidulogin "github.com/iikira/Baidu-Login"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsfunctions/pcscaptcha"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsliner"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
)
|
||||
|
||||
// handleVerifyImg 处理验证码, 下载到本地
|
||||
func handleVerifyImg(imgURL string) (savePath string, err error) {
|
||||
imgContents, err := requester.Fetch("GET", imgURL, nil, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取验证码失败, 错误: %s", err)
|
||||
}
|
||||
|
||||
_, err = png.Decode(bytes.NewReader(imgContents))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("验证码解析错误: %s", err)
|
||||
}
|
||||
|
||||
savePath = pcscaptcha.CaptchaPath()
|
||||
|
||||
return savePath, ioutil.WriteFile(savePath, imgContents, 0777)
|
||||
}
|
||||
|
||||
// RunLogin 登录百度帐号
|
||||
func RunLogin(username, password string) (bduss, ptoken, stoken string, cookies string, err error) {
|
||||
line := pcsliner.NewLiner()
|
||||
defer line.Close()
|
||||
|
||||
bc := baidulogin.NewBaiduClinet()
|
||||
|
||||
if username == "" {
|
||||
username, err = line.State.Prompt("请输入百度用户名(手机号/邮箱/用户名), 回车键提交 > ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
// liner 的 PasswordPrompt 不安全, 拆行之后密码就会显示出来了
|
||||
fmt.Printf("请输入密码(输入的密码无回显, 确认输入完成, 回车提交即可) > ")
|
||||
password, err = line.State.PasswordPrompt("")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var vcode, vcodestr string
|
||||
// 移除验证码文件
|
||||
defer func() {
|
||||
pcscaptcha.RemoveCaptchaPath()
|
||||
pcscaptcha.RemoveOldCaptchaPath()
|
||||
}()
|
||||
|
||||
for_1:
|
||||
for i := 0; i < 10; i++ {
|
||||
lj := bc.BaiduLogin(username, password, vcode, vcodestr)
|
||||
|
||||
switch lj.ErrInfo.No {
|
||||
case "0": // 登录成功, 退出循环
|
||||
return lj.Data.BDUSS, lj.Data.PToken, lj.Data.SToken, lj.Data.CookieString, nil
|
||||
case "400023", "400101": // 需要验证手机或邮箱
|
||||
fmt.Printf("\n需要验证手机或邮箱才能登录\n选择一种验证方式\n")
|
||||
fmt.Printf("1: 手机: %s\n", lj.Data.Phone)
|
||||
fmt.Printf("2: 邮箱: %s\n", lj.Data.Email)
|
||||
fmt.Printf("\n")
|
||||
|
||||
var verifyType string
|
||||
for et := 0; et < 3; et++ {
|
||||
verifyType, err = line.State.Prompt("请输入验证方式 (1 或 2) > ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch verifyType {
|
||||
case "1":
|
||||
verifyType = "mobile"
|
||||
case "2":
|
||||
verifyType = "email"
|
||||
default:
|
||||
fmt.Printf("[%d/3] 验证方式不合法\n", et+1)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if verifyType != "mobile" && verifyType != "email" {
|
||||
err = fmt.Errorf("验证方式不合法")
|
||||
return
|
||||
}
|
||||
|
||||
msg := bc.SendCodeToUser(verifyType, lj.Data.Token) // 发送验证码
|
||||
fmt.Printf("消息: %s\n\n", msg)
|
||||
|
||||
for et := 0; et < 5; et++ {
|
||||
vcode, err = line.State.Prompt("请输入接收到的验证码 > ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
nlj := bc.VerifyCode(verifyType, lj.Data.Token, vcode, lj.Data.U)
|
||||
if nlj.ErrInfo.No != "0" {
|
||||
fmt.Printf("[%d/5] 错误消息: %s\n\n", et+1, nlj.ErrInfo.Msg)
|
||||
continue
|
||||
}
|
||||
// 登录成功
|
||||
return nlj.Data.BDUSS, nlj.Data.PToken, nlj.Data.SToken, lj.Data.CookieString, nil
|
||||
}
|
||||
break for_1
|
||||
case "500001", "500002": // 验证码
|
||||
fmt.Printf("\n%s\n", lj.ErrInfo.Msg)
|
||||
vcodestr = lj.Data.CodeString
|
||||
if vcodestr == "" {
|
||||
err = fmt.Errorf("未找到codeString")
|
||||
return
|
||||
}
|
||||
|
||||
// 图片验证码
|
||||
var (
|
||||
verifyImgURL = "https://wappass.baidu.com/cgi-bin/genimage?" + vcodestr
|
||||
savePath string
|
||||
)
|
||||
|
||||
savePath, err = handleVerifyImg(verifyImgURL)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Printf("打开以下路径, 以查看验证码\n%s\n\n", savePath)
|
||||
}
|
||||
|
||||
fmt.Printf("或者打开以下的网址, 以查看验证码\n")
|
||||
fmt.Printf("%s\n\n", verifyImgURL)
|
||||
|
||||
vcode, err = line.State.Prompt("请输入验证码 > ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
default:
|
||||
err = fmt.Errorf("错误代码: %s, 消息: %s", lj.ErrInfo.No, lj.ErrInfo.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
144
internal/pcscommand/ls_search.go
Normal file
144
internal/pcscommand/ls_search.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/pcstime"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type (
|
||||
// LsOptions 列目录可选项
|
||||
LsOptions struct {
|
||||
Total bool
|
||||
}
|
||||
|
||||
// SearchOptions 搜索可选项
|
||||
SearchOptions struct {
|
||||
Total bool
|
||||
Recurse bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
opLs int = iota
|
||||
opSearch
|
||||
)
|
||||
|
||||
// RunLs 执行列目录
|
||||
func RunLs(pcspath string, lsOptions *LsOptions, orderOptions *baidupcs.OrderOptions) {
|
||||
err := matchPathByShellPatternOnce(&pcspath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := GetBaiduPCS().FilesDirectoriesList(pcspath, orderOptions)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n当前目录: %s\n----\n", pcspath)
|
||||
|
||||
if lsOptions == nil {
|
||||
lsOptions = &LsOptions{}
|
||||
}
|
||||
|
||||
renderTable(opLs, lsOptions.Total, pcspath, files)
|
||||
return
|
||||
}
|
||||
|
||||
// RunSearch 执行搜索
|
||||
func RunSearch(targetPath, keyword string, opt *SearchOptions) {
|
||||
err := matchPathByShellPatternOnce(&targetPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if opt == nil {
|
||||
opt = &SearchOptions{}
|
||||
}
|
||||
|
||||
files, err := GetBaiduPCS().Search(targetPath, keyword, opt.Recurse)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
renderTable(opSearch, opt.Total, targetPath, files)
|
||||
return
|
||||
}
|
||||
|
||||
func renderTable(op int, isTotal bool, path string, files baidupcs.FileDirectoryList) {
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
var (
|
||||
fN, dN int64
|
||||
showPath string
|
||||
)
|
||||
|
||||
switch op {
|
||||
case opLs:
|
||||
showPath = "文件(目录)"
|
||||
case opSearch:
|
||||
showPath = "路径"
|
||||
}
|
||||
|
||||
if isTotal {
|
||||
tb.SetHeader([]string{"#", "fs_id", "app_id", "文件大小", "创建日期", "修改日期", "md5(截图请打码)", showPath})
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||
for k, file := range files {
|
||||
if file.Isdir {
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(file.FsID, 10), strconv.FormatInt(file.AppID, 10), "-", pcstime.FormatTime(file.Ctime), pcstime.FormatTime(file.Mtime), file.MD5, file.Filename + baidupcs.PathSeparator})
|
||||
continue
|
||||
}
|
||||
|
||||
var md5 string
|
||||
if len(file.BlockList) > 1 {
|
||||
md5 = "(可能不正确)" + file.MD5
|
||||
} else {
|
||||
md5 = file.MD5
|
||||
}
|
||||
|
||||
switch op {
|
||||
case opLs:
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(file.FsID, 10), strconv.FormatInt(file.AppID, 10), converter.ConvertFileSize(file.Size, 2), pcstime.FormatTime(file.Ctime), pcstime.FormatTime(file.Mtime), md5, file.Filename})
|
||||
case opSearch:
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(file.FsID, 10), strconv.FormatInt(file.AppID, 10), converter.ConvertFileSize(file.Size, 2), pcstime.FormatTime(file.Ctime), pcstime.FormatTime(file.Mtime), md5, file.Path})
|
||||
}
|
||||
}
|
||||
fN, dN = files.Count()
|
||||
tb.Append([]string{"", "", "总: " + converter.ConvertFileSize(files.TotalSize(), 2), "", "", "", fmt.Sprintf("文件总数: %d, 目录总数: %d", fN, dN)})
|
||||
} else {
|
||||
tb.SetHeader([]string{"#", "文件大小", "修改日期", showPath})
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||
for k, file := range files {
|
||||
if file.Isdir {
|
||||
tb.Append([]string{strconv.Itoa(k), "-", pcstime.FormatTime(file.Mtime), file.Filename + baidupcs.PathSeparator})
|
||||
continue
|
||||
}
|
||||
|
||||
switch op {
|
||||
case opLs:
|
||||
tb.Append([]string{strconv.Itoa(k), converter.ConvertFileSize(file.Size, 2), pcstime.FormatTime(file.Mtime), file.Filename})
|
||||
case opSearch:
|
||||
tb.Append([]string{strconv.Itoa(k), converter.ConvertFileSize(file.Size, 2), pcstime.FormatTime(file.Mtime), file.Path})
|
||||
}
|
||||
}
|
||||
fN, dN = files.Count()
|
||||
tb.Append([]string{"", "总: " + converter.ConvertFileSize(files.TotalSize(), 2), "", fmt.Sprintf("文件总数: %d, 目录总数: %d", fN, dN)})
|
||||
}
|
||||
|
||||
tb.Render()
|
||||
|
||||
if fN+dN >= 50 {
|
||||
fmt.Printf("\n当前目录: %s\n", path)
|
||||
}
|
||||
|
||||
fmt.Printf("----\n")
|
||||
}
|
||||
25
internal/pcscommand/meta.go
Normal file
25
internal/pcscommand/meta.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RunGetMeta 执行 获取文件/目录的元信息
|
||||
func RunGetMeta(targetPaths ...string) {
|
||||
targetPaths, err := matchPathByShellPattern(targetPaths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for k, targetPath := range targetPaths {
|
||||
fmt.Printf("[%d] - [%s] --------------\n", k, targetPath)
|
||||
data, err := GetBaiduPCS().FilesDirectoriesMeta(targetPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println(data)
|
||||
}
|
||||
}
|
||||
22
internal/pcscommand/pcscommand.go
Normal file
22
internal/pcscommand/pcscommand.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Package pcscommand 命令包
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsverbose"
|
||||
)
|
||||
|
||||
var (
|
||||
pcsCommandVerbose = pcsverbose.New("PCSCOMMAND")
|
||||
)
|
||||
|
||||
// GetActiveUser 获取当前登录的百度帐号
|
||||
func GetActiveUser() *pcsconfig.Baidu {
|
||||
return pcsconfig.Config.ActiveUser()
|
||||
}
|
||||
|
||||
// GetBaiduPCS 从配置读取BaiduPCS
|
||||
func GetBaiduPCS() *baidupcs.BaiduPCS {
|
||||
return pcsconfig.Config.ActiveUserBaiduPCS()
|
||||
}
|
||||
21
internal/pcscommand/quota.go
Normal file
21
internal/pcscommand/quota.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
)
|
||||
|
||||
// RunGetQuota 执行 获取当前用户空间配额信息, 并输出
|
||||
func RunGetQuota() {
|
||||
quota, used, err := GetBaiduPCS().QuotaInfo()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("用户名: %s, 总空间: %s, 已用空间: %s, 比率: %f%%\n",
|
||||
GetActiveUser().Name,
|
||||
converter.ConvertFileSize(quota),
|
||||
converter.ConvertFileSize(used),
|
||||
100*float64(used)/float64(quota),
|
||||
)
|
||||
}
|
||||
86
internal/pcscommand/recycle.go
Normal file
86
internal/pcscommand/recycle.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/pcstime"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RunRecycleList 执行列出回收站文件列表
|
||||
func RunRecycleList(page int) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
pcs := GetBaiduPCS()
|
||||
fdl, err := pcs.RecycleList(page)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"#", "fs_id", "文件大小", "创建日期", "修改日期", "md5(截图请打码)", "剩余时间", "路径"})
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_LEFT})
|
||||
for k, file := range fdl {
|
||||
if file.Isdir == 1 {
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(file.FsID, 10), "-", pcstime.FormatTime(file.Ctime), pcstime.FormatTime(file.Mtime), file.MD5, strconv.Itoa(file.LeftTime), file.Path + baidupcs.PathSeparator})
|
||||
continue
|
||||
}
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(file.FsID, 10), converter.ConvertFileSize(file.Size, 2), pcstime.FormatTime(file.Ctime), pcstime.FormatTime(file.Mtime), file.MD5, strconv.Itoa(file.LeftTime), file.Path})
|
||||
}
|
||||
|
||||
tb.Render()
|
||||
}
|
||||
|
||||
// RunRecycleRestore 执行还原回收站文件或目录
|
||||
func RunRecycleRestore(fidStrList ...string) {
|
||||
var (
|
||||
fidList = converter.SliceStringToInt64(fidStrList)
|
||||
pcs = GetBaiduPCS()
|
||||
ex, err = pcs.RecycleRestore(fidList...)
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if len(ex) > 0 {
|
||||
fmt.Printf("\n以下的 fs_id 还原成功, 数量: %d\n", len(ex))
|
||||
for k := range ex {
|
||||
fmt.Println(ex[k].FsID)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("还原成功, 数量: %d\n", len(ex))
|
||||
}
|
||||
|
||||
// RunRecycleDelete 执行删除回收站文件或目录
|
||||
func RunRecycleDelete(fidStrList ...string) {
|
||||
var (
|
||||
fidList = converter.SliceStringToInt64(fidStrList)
|
||||
pcs = GetBaiduPCS()
|
||||
err = pcs.RecycleDelete(fidList...)
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("删除成功\n")
|
||||
}
|
||||
|
||||
// RunRecycleClear 清空回收站
|
||||
func RunRecycleClear() {
|
||||
pcs := GetBaiduPCS()
|
||||
sussNum, err := pcs.RecycleClear()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("清空回收站成功, 数量: %d\n", sussNum)
|
||||
}
|
||||
49
internal/pcscommand/rm_mkdir.go
Normal file
49
internal/pcscommand/rm_mkdir.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RunRemove 执行 批量删除文件/目录
|
||||
func RunRemove(paths ...string) {
|
||||
paths, err := matchPathByShellPattern(paths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
pnt := func() {
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"#", "文件/目录"})
|
||||
for k := range paths {
|
||||
tb.Append([]string{strconv.Itoa(k), paths[k]})
|
||||
}
|
||||
tb.Render()
|
||||
}
|
||||
|
||||
err = GetBaiduPCS().Remove(paths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("操作失败, 以下文件/目录删除失败: ")
|
||||
pnt()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("操作成功, 以下文件/目录已删除, 可在网盘文件回收站找回: ")
|
||||
pnt()
|
||||
}
|
||||
|
||||
// RunMkdir 执行 创建目录
|
||||
func RunMkdir(path string) {
|
||||
activeUser := GetActiveUser()
|
||||
err := GetBaiduPCS().Mkdir(activeUser.PathJoin(path))
|
||||
if err != nil {
|
||||
fmt.Printf("创建目录 %s 失败, %s\n", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("创建目录成功:", path)
|
||||
}
|
||||
21
internal/pcscommand/runner.go
Normal file
21
internal/pcscommand/runner.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRunner 默认 Runner
|
||||
DefaultRunner = Runner{
|
||||
Output: os.Stdout,
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// Runner 执行器
|
||||
Runner struct {
|
||||
Output io.Writer
|
||||
IsBackground bool
|
||||
}
|
||||
)
|
||||
76
internal/pcscommand/share.go
Normal file
76
internal/pcscommand/share.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// RunShareSet 执行分享
|
||||
func RunShareSet(paths []string, option *baidupcs.ShareOption) {
|
||||
pcspaths, err := matchPathByShellPattern(paths...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
shared, err := GetBaiduPCS().ShareSet(pcspaths, option)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareSet, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("shareID: %d, 链接: %s\n", shared.ShareID, shared.Link)
|
||||
}
|
||||
|
||||
// RunShareCancel 执行取消分享
|
||||
func RunShareCancel(shareIDs []int64) {
|
||||
if len(shareIDs) == 0 {
|
||||
fmt.Printf("%s失败, 没有任何 shareid\n", baidupcs.OperationShareCancel)
|
||||
return
|
||||
}
|
||||
|
||||
err := GetBaiduPCS().ShareCancel(shareIDs)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareCancel, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s成功\n", baidupcs.OperationShareCancel)
|
||||
}
|
||||
|
||||
// RunShareList 执行列出分享列表
|
||||
func RunShareList(page int) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
pcs := GetBaiduPCS()
|
||||
records, err := pcs.ShareList(page)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareList, err)
|
||||
return
|
||||
}
|
||||
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"#", "ShareID", "分享链接", "提取密码", "特征目录", "特征路径"})
|
||||
for k, record := range records {
|
||||
// 获取Passwd
|
||||
if record.Public == 0 {
|
||||
// 私密分享
|
||||
info, pcsError := pcs.ShareSURLInfo(record.ShareID)
|
||||
if pcsError != nil {
|
||||
// 获取错误
|
||||
fmt.Printf("[%d] 获取分享密码错误: %s\n", k, pcsError)
|
||||
} else {
|
||||
record.Passwd = info.Pwd
|
||||
}
|
||||
}
|
||||
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatInt(record.ShareID, 10), record.Shortlink, record.Passwd, path.Clean(path.Dir(record.TypicalPath)), record.TypicalPath})
|
||||
}
|
||||
tb.Render()
|
||||
}
|
||||
132
internal/pcscommand/transfer.go
Normal file
132
internal/pcscommand/transfer.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
)
|
||||
|
||||
// RunShareTransfer 执行分享链接转存到网盘
|
||||
func RunShareTransfer(params []string) {
|
||||
var link string
|
||||
var extracode string
|
||||
if len(params) == 1 {
|
||||
link = params[0]
|
||||
if strings.Contains(link, "bdlink=") || !strings.Contains(link, "pan.baidu.com/") {
|
||||
RunRapidTransfer(link)
|
||||
return
|
||||
}
|
||||
extracode = "none"
|
||||
} else if len(params) == 2 {
|
||||
link = params[0]
|
||||
extracode = params[1]
|
||||
}
|
||||
if link[len(link)-1:len(link)] == "/" {
|
||||
link = link[0 : len(link)-1]
|
||||
}
|
||||
featurestrs := strings.Split(link, "/")
|
||||
featurestr := featurestrs[len(featurestrs)-1]
|
||||
if strings.Contains(featurestr, "init?") {
|
||||
featurestr = "1" + strings.Split(featurestr, "=")[1]
|
||||
}
|
||||
if len(featurestr) != 23 || featurestr[0:1] != "1" || len(extracode) != 4 {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, "链接地址或提取码非法")
|
||||
return
|
||||
}
|
||||
// link = "pan.baidu.com/share/init?surl=" + featurestr[1:]
|
||||
link = "pan.baidu.com/s/" + featurestr
|
||||
pcs := GetBaiduPCS()
|
||||
tokens := pcs.AccessSharePage("https://"+link, true)
|
||||
if tokens["ErrMsg"] != "0" {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, tokens["ErrMsg"])
|
||||
return
|
||||
}
|
||||
var vefiryurl string
|
||||
featuremap := make(map[string]string)
|
||||
featuremap["surl"] = featurestr[1:]
|
||||
featuremap["bdstoken"] = tokens["bdstoken"]
|
||||
if extracode != "none" {
|
||||
|
||||
vefiryurl = pcs.GenerateShareQueryURL("verify", featuremap).String()
|
||||
res := pcs.PostShareQuery(vefiryurl, map[string]string{
|
||||
"pwd": extracode,
|
||||
})
|
||||
if res["ErrMsg"] != "0" {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, res["ErrMsg"])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
tokens = pcs.AccessSharePage("https://pan.baidu.com/s/"+featurestr, false)
|
||||
if tokens["ErrMsg"] != "0" {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, tokens["ErrMsg"])
|
||||
return
|
||||
}
|
||||
bdstoken := tokens["bdstoken"]
|
||||
delete(tokens, "bdstoken")
|
||||
tokens["desc"] = "1"
|
||||
tokens["num"] = "100"
|
||||
tokens["order"] = "time"
|
||||
tokens["page"] = "1"
|
||||
tokens["root"] = "1"
|
||||
tokens["showempty"] = "0"
|
||||
listurl := pcs.GenerateShareQueryURL("list", tokens).String()
|
||||
emptymap := make(map[string]string)
|
||||
metas := pcs.GenerateRequestQuery("GET", listurl, nil)
|
||||
if metas["ErrMsg"] != "0" {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, metas["ErrMsg"])
|
||||
return
|
||||
}
|
||||
emptymap["bdstoken"] = bdstoken
|
||||
emptymap["from"] = tokens["uk"]
|
||||
emptymap["shareid"] = tokens["shareid"]
|
||||
transferurl := pcs.GenerateShareQueryURL("transfer", emptymap).String()
|
||||
postdata := make(map[string]string)
|
||||
postdata["fsidlist"] = metas["fs_id"]
|
||||
postdata["path"] = GetActiveUser().Workdir
|
||||
resp := pcs.GenerateRequestQuery("POST", transferurl, postdata)
|
||||
if resp["ErrMsg"] != "0" {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationShareFileSavetoLocal, resp["ErrMsg"])
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s成功, 保存了%s到当前目录\n", baidupcs.OperationShareFileSavetoLocal, metas["filename"])
|
||||
}
|
||||
|
||||
// RunRapidTransfer 执行秒传链接解析及保存
|
||||
func RunRapidTransfer(link string) {
|
||||
if strings.Contains(link, "bdlink=") || strings.Contains(link, "bdpan://") {
|
||||
r, _ := regexp.Compile(`(bdlink=|bdpan://)([^\s]+)`)
|
||||
link1 := r.FindStringSubmatch(link)[2]
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(link1)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationRapidLinkSavetoLocal, "秒传链接格式错误")
|
||||
return
|
||||
}
|
||||
link = string(decodeBytes)
|
||||
}
|
||||
substrs := strings.Split(link, "#")
|
||||
if len(substrs) == 4 {
|
||||
md5 := strings.ToLower(substrs[0])
|
||||
slicemd5 := strings.ToLower(substrs[1])
|
||||
length, _ := strconv.ParseInt(substrs[2], 10, 64)
|
||||
filename := filepath.Join(GetActiveUser().Workdir, substrs[3])
|
||||
RunRapidUpload(filename, md5, slicemd5, "", length)
|
||||
return
|
||||
}
|
||||
substrs = strings.Split(link, "|")
|
||||
if len(substrs) == 4 {
|
||||
md5 := strings.ToLower(substrs[2])
|
||||
slicemd5 := strings.ToLower(substrs[3])
|
||||
length, _ := strconv.ParseInt(substrs[1], 10, 64)
|
||||
filename := filepath.Join(GetActiveUser().Workdir, substrs[0])
|
||||
RunRapidUpload(filename, md5, slicemd5, "", length)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s失败: %s\n", baidupcs.OperationRapidLinkSavetoLocal, "秒传链接格式错误")
|
||||
}
|
||||
59
internal/pcscommand/tree.go
Normal file
59
internal/pcscommand/tree.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
indentPrefix = "│ "
|
||||
pathPrefix = "├──"
|
||||
lastFilePrefix = "└──"
|
||||
)
|
||||
|
||||
func getTree(pcspath string, depth int) {
|
||||
var (
|
||||
err error
|
||||
files baidupcs.FileDirectoryList
|
||||
)
|
||||
if depth == 0 {
|
||||
err := matchPathByShellPatternOnce(&pcspath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
files, err = GetBaiduPCS().FilesDirectoriesList(pcspath, baidupcs.DefaultOrderOptions)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
prefix = pathPrefix
|
||||
fN = len(files)
|
||||
indentPrefixStr = strings.Repeat(indentPrefix, depth)
|
||||
)
|
||||
for i, file := range files {
|
||||
if file.Isdir {
|
||||
fmt.Printf("%v%v %v/\n", indentPrefixStr, pathPrefix, file.Filename)
|
||||
getTree(file.Path, depth+1)
|
||||
continue
|
||||
}
|
||||
|
||||
if i+1 == fN {
|
||||
prefix = lastFilePrefix
|
||||
}
|
||||
|
||||
fmt.Printf("%v%v %v\n", indentPrefixStr, prefix, file.Filename)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RunTree 列出树形图
|
||||
func RunTree(path string) {
|
||||
getTree(path, 0)
|
||||
}
|
||||
176
internal/pcscommand/upload.go
Normal file
176
internal/pcscommand/upload.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsfunctions/pcsupload"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/checksum"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/taskframework"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultUploadMaxRetry 默认上传失败最大重试次数
|
||||
DefaultUploadMaxRetry = 3
|
||||
)
|
||||
|
||||
type (
|
||||
// UploadOptions 上传可选项
|
||||
UploadOptions struct {
|
||||
Parallel int
|
||||
MaxRetry int
|
||||
NoRapidUpload bool
|
||||
NoSplitFile bool // 禁用分片上传
|
||||
}
|
||||
)
|
||||
|
||||
// RunRapidUpload 执行秒传文件, 前提是知道文件的大小, md5, 前256KB切片的 md5, crc32
|
||||
func RunRapidUpload(targetPath, contentMD5, sliceMD5, crc32 string, length int64) {
|
||||
err := matchPathByShellPatternOnce(&targetPath)
|
||||
if err != nil {
|
||||
fmt.Printf("警告: %s, 获取网盘路径 %s 错误, %s\n", baidupcs.OperationRapidUpload, targetPath, err)
|
||||
}
|
||||
|
||||
err = GetBaiduPCS().RapidUpload(targetPath, contentMD5, sliceMD5, crc32, length)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败, 消息: %s\n", baidupcs.OperationRapidUpload, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s成功, 保存到网盘路径: %s\n", baidupcs.OperationRapidUpload, targetPath)
|
||||
return
|
||||
}
|
||||
|
||||
// RunCreateSuperFile 执行分片上传—合并分片文件
|
||||
func RunCreateSuperFile(targetPath string, blockList ...string) {
|
||||
err := matchPathByShellPatternOnce(&targetPath)
|
||||
if err != nil {
|
||||
fmt.Printf("警告: %s, 获取网盘路径 %s 错误, %s\n", baidupcs.OperationUploadCreateSuperFile, targetPath, err)
|
||||
}
|
||||
|
||||
err = GetBaiduPCS().UploadCreateSuperFile(true, targetPath, blockList...)
|
||||
if err != nil {
|
||||
fmt.Printf("%s失败, 消息: %s\n", baidupcs.OperationUploadCreateSuperFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s成功, 保存到网盘路径: %s\n", baidupcs.OperationUploadCreateSuperFile, targetPath)
|
||||
return
|
||||
}
|
||||
|
||||
// RunUpload 执行文件上传
|
||||
func RunUpload(localPaths []string, savePath string, opt *UploadOptions) {
|
||||
if opt == nil {
|
||||
opt = &UploadOptions{}
|
||||
}
|
||||
|
||||
// 检测opt
|
||||
if opt.Parallel <= 0 {
|
||||
opt.Parallel = pcsconfig.Config.MaxUploadParallel
|
||||
}
|
||||
|
||||
if opt.MaxRetry < 0 {
|
||||
opt.MaxRetry = DefaultUploadMaxRetry
|
||||
}
|
||||
|
||||
err := matchPathByShellPatternOnce(&savePath)
|
||||
if err != nil {
|
||||
fmt.Printf("警告: 上传文件, 获取网盘路径 %s 错误, %s\n", savePath, err)
|
||||
}
|
||||
|
||||
switch len(localPaths) {
|
||||
case 0:
|
||||
fmt.Printf("本地路径为空\n")
|
||||
return
|
||||
}
|
||||
|
||||
// 打开上传状态
|
||||
uploadDatabase, err := pcsupload.NewUploadingDatabase()
|
||||
if err != nil {
|
||||
fmt.Printf("打开上传未完成数据库错误: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer uploadDatabase.Close()
|
||||
|
||||
var (
|
||||
pcs = GetBaiduPCS()
|
||||
// 使用 task framework
|
||||
executor = &taskframework.TaskExecutor{
|
||||
IsFailedDeque: true, // 失败统计
|
||||
}
|
||||
subSavePath string
|
||||
// 统计
|
||||
statistic = &pcsupload.UploadStatistic{}
|
||||
)
|
||||
|
||||
statistic.StartTimer() // 开始计时
|
||||
|
||||
for k := range localPaths {
|
||||
walkedFiles, err := pcsutil.WalkDir(localPaths[k], "")
|
||||
if err != nil {
|
||||
fmt.Printf("警告: 遍历错误: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for k3 := range walkedFiles {
|
||||
var localPathDir string
|
||||
// 针对 windows 的目录处理
|
||||
if os.PathSeparator == '\\' {
|
||||
walkedFiles[k3] = pcsutil.ConvertToUnixPathSeparator(walkedFiles[k3])
|
||||
localPathDir = pcsutil.ConvertToUnixPathSeparator(filepath.Dir(localPaths[k]))
|
||||
} else {
|
||||
localPathDir = filepath.Dir(localPaths[k])
|
||||
}
|
||||
|
||||
// 避免去除文件名开头的"."
|
||||
if localPathDir == "." {
|
||||
localPathDir = ""
|
||||
}
|
||||
|
||||
subSavePath = strings.TrimPrefix(walkedFiles[k3], localPathDir)
|
||||
|
||||
info := executor.Append(&pcsupload.UploadTaskUnit{
|
||||
LocalFileChecksum: checksum.NewLocalFileChecksum(walkedFiles[k3], int(baidupcs.SliceMD5Size)),
|
||||
SavePath: path.Clean(savePath + baidupcs.PathSeparator + subSavePath),
|
||||
PCS: pcs,
|
||||
UploadingDatabase: uploadDatabase,
|
||||
Parallel: opt.Parallel,
|
||||
NoRapidUpload: opt.NoRapidUpload,
|
||||
NoSplitFile: opt.NoSplitFile,
|
||||
UploadStatistic: statistic,
|
||||
}, opt.MaxRetry)
|
||||
fmt.Printf("[%s] 加入上传队列: %s\n", info.Id(), walkedFiles[k3])
|
||||
}
|
||||
}
|
||||
|
||||
// 没有添加任何任务
|
||||
if executor.Count() == 0 {
|
||||
fmt.Printf("未检测到上传的文件.\n")
|
||||
return
|
||||
}
|
||||
|
||||
// 执行上传任务
|
||||
executor.Execute()
|
||||
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("上传结束, 时间: %s, 总大小: %s\n", statistic.Elapsed()/1e6*1e6, converter.ConvertFileSize(statistic.TotalSize()))
|
||||
|
||||
// 输出上传失败的文件列表
|
||||
failedList := executor.FailedDeque()
|
||||
if failedList.Size() != 0 {
|
||||
fmt.Printf("以下文件上传失败: \n")
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
for e := failedList.Shift(); e != nil; e = failedList.Shift() {
|
||||
item := e.(*taskframework.TaskInfoItem)
|
||||
tb.Append([]string{item.Info.Id(), item.Unit.(*pcsupload.UploadTaskUnit).LocalFileChecksum.Path})
|
||||
}
|
||||
tb.Render()
|
||||
}
|
||||
}
|
||||
64
internal/pcscommand/util.go
Normal file
64
internal/pcscommand/util.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package pcscommand
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrShellPatternMultiRes 多条通配符匹配结果
|
||||
ErrShellPatternMultiRes = errors.New("多条通配符匹配结果")
|
||||
// ErrShellPatternNoHit 未匹配到路径
|
||||
ErrShellPatternNoHit = errors.New("未匹配到路径, 请检测通配符")
|
||||
)
|
||||
|
||||
// ListTask 队列状态 (基类)
|
||||
type ListTask struct {
|
||||
ID int // 任务id
|
||||
MaxRetry int // 最大重试次数
|
||||
retry int // 任务失败的重试次数
|
||||
}
|
||||
|
||||
// RunTestShellPattern 执行测试通配符
|
||||
func RunTestShellPattern(pattern string) {
|
||||
pcs := GetBaiduPCS()
|
||||
paths, err := pcs.MatchPathByShellPattern(GetActiveUser().PathJoin(pattern))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
for k := range paths {
|
||||
fmt.Printf("%s\n", paths[k])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func matchPathByShellPatternOnce(pattern *string) error {
|
||||
paths, err := GetBaiduPCS().MatchPathByShellPattern(GetActiveUser().PathJoin(*pattern))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch len(paths) {
|
||||
case 0:
|
||||
return ErrShellPatternNoHit
|
||||
case 1:
|
||||
*pattern = paths[0]
|
||||
default:
|
||||
return ErrShellPatternMultiRes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchPathByShellPattern(patterns ...string) (pcspaths []string, err error) {
|
||||
acUser, pcs := GetActiveUser(), GetBaiduPCS()
|
||||
for k := range patterns {
|
||||
ps, err := pcs.MatchPathByShellPattern(acUser.PathJoin(patterns[k]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pcspaths = append(pcspaths, ps...)
|
||||
}
|
||||
return pcspaths, nil
|
||||
}
|
||||
119
internal/pcsconfig/baidu.go
Normal file
119
internal/pcsconfig/baidu.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/baidu-tools/tieba"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrNoSuchBaiduUser 未登录任何百度帐号
|
||||
ErrNoSuchBaiduUser = errors.New("no such baidu user")
|
||||
//ErrBaiduUserNotFound 未找到百度帐号
|
||||
ErrBaiduUserNotFound = errors.New("baidu user not found")
|
||||
)
|
||||
|
||||
//BaiduBase Baidu基
|
||||
type BaiduBase struct {
|
||||
UID uint64 `json:"uid"` // 百度ID对应的uid
|
||||
Name string `json:"name"` // 真实ID
|
||||
}
|
||||
|
||||
// Baidu 百度帐号对象
|
||||
type Baidu struct {
|
||||
BaiduBase
|
||||
Sex string `json:"sex"` // 性别
|
||||
Age float64 `json:"age"` // 帐号年龄
|
||||
|
||||
BDUSS string `json:"bduss"`
|
||||
PTOKEN string `json:"ptoken"`
|
||||
STOKEN string `json:"stoken"`
|
||||
COOKIES string `json:"cookies"`
|
||||
|
||||
Workdir string `json:"workdir"` // 工作目录
|
||||
}
|
||||
|
||||
// BaiduPCS 初始化*baidupcs.BaiduPCS
|
||||
func (baidu *Baidu) BaiduPCS() *baidupcs.BaiduPCS {
|
||||
pcs := baidupcs.NewPCS(Config.AppID, baidu.BDUSS)
|
||||
pcs.SetStoken(baidu.STOKEN)
|
||||
if strings.Contains(baidu.COOKIES, "BAIDUID=") {
|
||||
// fmt.Println("已加载完整Cookies,可以使用转存功能")
|
||||
pcs = baidupcs.NewPCSWithCookieStr(Config.AppID, baidu.COOKIES)
|
||||
} else if baidu.BDUSS != "" {
|
||||
fmt.Println("注:分享链接转存功能无法使用,可使用-cookies参数重新登录以启用")
|
||||
}
|
||||
pcs.SetHTTPS(Config.EnableHTTPS)
|
||||
pcs.SetPCSUserAgent(Config.PCSUA)
|
||||
pcs.SetPanUserAgent(Config.PanUA)
|
||||
pcs.SetUID(baidu.UID)
|
||||
return pcs
|
||||
}
|
||||
|
||||
// GetSavePath 根据提供的网盘文件路径 pcspath, 返回本地储存路径,
|
||||
// 返回绝对路径, 获取绝对路径出错时才返回相对路径...
|
||||
func (baidu *Baidu) GetSavePath(pcspath string) string {
|
||||
dirStr := filepath.Join(Config.SaveDir, fmt.Sprintf("%d_%s", baidu.UID, converter.TrimPathInvalidChars(baidu.Name)), pcspath)
|
||||
dir, err := filepath.Abs(dirStr)
|
||||
if err != nil {
|
||||
dir = filepath.Clean(dirStr)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// PathJoin 合并工作目录和相对路径p, 若p为绝对路径则忽略
|
||||
func (baidu *Baidu) PathJoin(p string) string {
|
||||
if path.IsAbs(p) {
|
||||
return p
|
||||
}
|
||||
return path.Join(baidu.Workdir, p)
|
||||
}
|
||||
|
||||
// BaiduUserList 百度帐号列表
|
||||
type BaiduUserList []*Baidu
|
||||
|
||||
// NewUserInfoByBDUSS 检测BDUSS有效性, 同时获取百度详细信息 (无法获取 ptoken 和 stoken)
|
||||
func NewUserInfoByBDUSS(bduss string) (b *Baidu, err error) {
|
||||
t, err := tieba.NewUserInfoByBDUSS(bduss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b = &Baidu{
|
||||
BaiduBase: BaiduBase{
|
||||
UID: t.Baidu.UID,
|
||||
Name: t.Baidu.Name,
|
||||
},
|
||||
Sex: t.Baidu.Sex,
|
||||
Age: t.Baidu.Age,
|
||||
BDUSS: bduss,
|
||||
Workdir: "/",
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String 格式输出百度帐号列表
|
||||
func (bl *BaiduUserList) String() string {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
tb := pcstable.NewTable(builder)
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER, tablewriter.ALIGN_CENTER})
|
||||
tb.SetHeader([]string{"#", "uid", "用户名", "性别", "age"})
|
||||
|
||||
for k, baiduInfo := range *bl {
|
||||
tb.Append([]string{strconv.Itoa(k), strconv.FormatUint(baiduInfo.UID, 10), baiduInfo.Name, baiduInfo.Sex, fmt.Sprint(baiduInfo.Age)})
|
||||
}
|
||||
|
||||
tb.Render()
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
18
internal/pcsconfig/errors.go
Normal file
18
internal/pcsconfig/errors.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrNotLogin 未登录帐号错误
|
||||
ErrNotLogin = errors.New("baidu user not login")
|
||||
//ErrConfigFilePathNotSet 未设置配置文件
|
||||
ErrConfigFilePathNotSet = errors.New("config file not set")
|
||||
//ErrConfigFileNotExist 未设置Config, 未初始化
|
||||
ErrConfigFileNotExist = errors.New("config file not exist")
|
||||
//ErrConfigFileNoPermission Config文件无权限访问
|
||||
ErrConfigFileNoPermission = errors.New("config file permission denied")
|
||||
//ErrConfigContentsParseError 解析Config数据错误
|
||||
ErrConfigContentsParseError = errors.New("config contents parse error")
|
||||
)
|
||||
85
internal/pcsconfig/export.go
Normal file
85
internal/pcsconfig/export.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ActiveUser 获取当前登录的用户
|
||||
func (c *PCSConfig) ActiveUser() *Baidu {
|
||||
if c.activeUser == nil {
|
||||
return &Baidu{}
|
||||
}
|
||||
return c.activeUser
|
||||
}
|
||||
|
||||
// ActiveUserBaiduPCS 获取当前登录的用户的baidupcs.BaiduPCS
|
||||
func (c *PCSConfig) ActiveUserBaiduPCS() *baidupcs.BaiduPCS {
|
||||
if c.pcs == nil {
|
||||
c.pcs = c.ActiveUser().BaiduPCS()
|
||||
}
|
||||
return c.pcs
|
||||
}
|
||||
|
||||
func (c *PCSConfig) httpClientWithUA(ua string) *requester.HTTPClient {
|
||||
client := requester.NewHTTPClient()
|
||||
client.SetHTTPSecure(c.EnableHTTPS)
|
||||
client.SetUserAgent(ua)
|
||||
return client
|
||||
}
|
||||
|
||||
// HTTPClient 返回设置好的 HTTPClient
|
||||
func (c *PCSConfig) HTTPClient() *requester.HTTPClient {
|
||||
return c.httpClientWithUA(c.UserAgent)
|
||||
}
|
||||
|
||||
// PCSHTTPClient 返回设置好的 PCS HTTPClient
|
||||
func (c *PCSConfig) PCSHTTPClient() *requester.HTTPClient {
|
||||
return c.httpClientWithUA(c.PCSUA)
|
||||
}
|
||||
|
||||
// PanHTTPClient 返回设置好的 Pan HTTPClient
|
||||
func (c *PCSConfig) PanHTTPClient() *requester.HTTPClient {
|
||||
return c.httpClientWithUA(c.PanUA)
|
||||
}
|
||||
|
||||
// NumLogins 获取登录的用户数量
|
||||
func (c *PCSConfig) NumLogins() int {
|
||||
return len(c.BaiduUserList)
|
||||
}
|
||||
|
||||
// AverageParallel 返回平均的下载最大并发量
|
||||
func (c *PCSConfig) AverageParallel() int {
|
||||
return AverageParallel(c.MaxParallel, c.MaxDownloadLoad)
|
||||
}
|
||||
|
||||
// PrintTable 输出表格
|
||||
func (c *PCSConfig) PrintTable() {
|
||||
tb := pcstable.NewTable(os.Stdout)
|
||||
tb.SetHeader([]string{"名称", "值", "建议值", "描述"})
|
||||
tb.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
tb.SetColumnAlignment([]int{tablewriter.ALIGN_DEFAULT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
|
||||
tb.AppendBulk([][]string{
|
||||
[]string{"appid", fmt.Sprint(c.AppID), "", "百度 PCS 应用ID"},
|
||||
[]string{"cache_size", converter.ConvertFileSize(int64(c.CacheSize), 2), "1KB ~ 256KB", "下载缓存, 如果硬盘占用高或下载速度慢, 请尝试调大此值"},
|
||||
[]string{"max_parallel", strconv.Itoa(c.MaxParallel), "1 ~ 64", "下载最大并发量"},
|
||||
[]string{"max_upload_parallel", strconv.Itoa(c.MaxUploadParallel), "1 ~ 100", "上传最大并发量"},
|
||||
[]string{"max_download_load", strconv.Itoa(c.MaxDownloadLoad), "1 ~ 5", "同时进行下载文件的最大数量"},
|
||||
[]string{"max_download_rate", showMaxRate(c.MaxDownloadRate), "", "限制最大下载速度, 0代表不限制"},
|
||||
[]string{"max_upload_rate", showMaxRate(c.MaxUploadRate), "", "限制最大上传速度, 0代表不限制"},
|
||||
[]string{"savedir", c.SaveDir, "", "下载文件的储存目录"},
|
||||
[]string{"enable_https", fmt.Sprint(c.EnableHTTPS), "true", "启用 https"},
|
||||
[]string{"user_agent", c.UserAgent, requester.DefaultUserAgent, "浏览器标识"},
|
||||
[]string{"pcs_ua", c.PCSUA, "", "PCS 浏览器标识"},
|
||||
[]string{"pan_ua", c.PanUA, baidupcs.NetdiskUA, "Pan 浏览器标识"},
|
||||
[]string{"proxy", c.Proxy, "", "设置代理, 支持 http/socks5 代理"},
|
||||
[]string{"local_addrs", c.LocalAddrs, "", "设置本地网卡地址, 多个地址用逗号隔开"},
|
||||
})
|
||||
tb.Render()
|
||||
}
|
||||
222
internal/pcsconfig/maniper.go
Normal file
222
internal/pcsconfig/maniper.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
)
|
||||
|
||||
const (
|
||||
opDelete = "delete"
|
||||
opSwitch = "switch"
|
||||
opGet = "get"
|
||||
)
|
||||
|
||||
func (c *PCSConfig) manipUser(op string, baiduBase *BaiduBase) (*Baidu, error) {
|
||||
// empty baiduBase
|
||||
if baiduBase == nil || (baiduBase.UID == 0 && baiduBase.Name == "") {
|
||||
switch op {
|
||||
case opGet:
|
||||
return &Baidu{}, nil
|
||||
default:
|
||||
return nil, ErrBaiduUserNotFound
|
||||
}
|
||||
}
|
||||
if len(c.BaiduUserList) == 0 {
|
||||
return nil, ErrNoSuchBaiduUser
|
||||
}
|
||||
|
||||
for k, user := range c.BaiduUserList {
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case baiduBase.UID != 0 && baiduBase.Name != "":
|
||||
// 不区分大小写
|
||||
if user.UID == baiduBase.UID && strings.EqualFold(user.Name, baiduBase.Name) {
|
||||
goto handle
|
||||
}
|
||||
continue
|
||||
case baiduBase.UID == 0 && baiduBase.Name != "":
|
||||
// 不区分大小写
|
||||
if strings.EqualFold(user.Name, baiduBase.Name) {
|
||||
goto handle
|
||||
}
|
||||
continue
|
||||
case baiduBase.UID != 0 && baiduBase.Name == "":
|
||||
if user.UID == baiduBase.UID {
|
||||
goto handle
|
||||
}
|
||||
continue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// unreachable zone
|
||||
|
||||
handle:
|
||||
switch op {
|
||||
case opSwitch:
|
||||
c.setupNewUser(user)
|
||||
case opDelete:
|
||||
c.BaiduUserList = append(c.BaiduUserList[:k], c.BaiduUserList[k+1:]...)
|
||||
|
||||
// 修改 正在使用的 百度帐号
|
||||
// 如果要删除的帐号为当前登录的帐号, 则设置当前登录帐号为列表中第一个帐号
|
||||
if c.BaiduActiveUID == user.UID {
|
||||
if len(c.BaiduUserList) != 0 {
|
||||
c.setupNewUser(c.BaiduUserList[0])
|
||||
} else {
|
||||
c.BaiduActiveUID = 0
|
||||
}
|
||||
}
|
||||
case opGet:
|
||||
// do nothing
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, ErrBaiduUserNotFound
|
||||
}
|
||||
|
||||
//setupNewUser 从已有用户中, 设置新的当前登录用户
|
||||
func (c *PCSConfig) setupNewUser(user *Baidu) {
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
c.BaiduActiveUID = user.UID
|
||||
c.activeUser = user
|
||||
c.pcs = user.BaiduPCS()
|
||||
}
|
||||
|
||||
// SwitchUser 切换用户, 返回切换成功的用户
|
||||
func (c *PCSConfig) SwitchUser(baiduBase *BaiduBase) (*Baidu, error) {
|
||||
return c.manipUser(opSwitch, baiduBase)
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户, 返回删除成功的用户
|
||||
func (c *PCSConfig) DeleteUser(baiduBase *BaiduBase) (*Baidu, error) {
|
||||
return c.manipUser(opDelete, baiduBase)
|
||||
}
|
||||
|
||||
// GetBaiduUser 获取百度用户信息
|
||||
func (c *PCSConfig) GetBaiduUser(baidubase *BaiduBase) (*Baidu, error) {
|
||||
return c.manipUser(opGet, baidubase)
|
||||
}
|
||||
|
||||
// CheckBaiduUserExist 检查百度用户是否存在于已登录列表
|
||||
func (c *PCSConfig) CheckBaiduUserExist(baidubase *BaiduBase) bool {
|
||||
_, err := c.manipUser("", baidubase)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SetupUserByBDUSS 设置百度 bduss, ptoken, stoken, cookies 并保存
|
||||
func (c *PCSConfig) SetupUserByBDUSS(bduss, ptoken, stoken, cookies string) (baidu *Baidu, err error) {
|
||||
if cookies != "" {
|
||||
re, _ := regexp.Compile(`BDUSS=(.+?);`)
|
||||
sub := re.FindSubmatch([]byte(cookies))
|
||||
bduss = string(sub[1])
|
||||
}
|
||||
b, err := NewUserInfoByBDUSS(bduss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.DeleteUser(&BaiduBase{
|
||||
UID: b.UID,
|
||||
}) // 删除旧的信息
|
||||
|
||||
b.PTOKEN = ptoken
|
||||
b.STOKEN = stoken
|
||||
b.COOKIES = cookies
|
||||
|
||||
c.BaiduUserList = append(c.BaiduUserList, b)
|
||||
|
||||
// 自动切换用户
|
||||
c.setupNewUser(b)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// SetAppID 设置app_id
|
||||
func (c *PCSConfig) SetAppID(appID int) {
|
||||
c.AppID = appID
|
||||
if c.pcs != nil {
|
||||
c.pcs.SetAPPID(appID)
|
||||
}
|
||||
}
|
||||
|
||||
// SetCacheSizeByStr 设置cache_size
|
||||
func (c *PCSConfig) SetCacheSizeByStr(sizeStr string) error {
|
||||
size, err := converter.ParseFileSizeStr(sizeStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.CacheSize = int(size)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxDownloadRateByStr 设置 max_download_rate
|
||||
func (c *PCSConfig) SetMaxDownloadRateByStr(sizeStr string) error {
|
||||
size, err := converter.ParseFileSizeStr(stripPerSecond(sizeStr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.MaxDownloadRate = size
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxUploadRateByStr 设置 max_upload_rate
|
||||
func (c *PCSConfig) SetMaxUploadRateByStr(sizeStr string) error {
|
||||
size, err := converter.ParseFileSizeStr(stripPerSecond(sizeStr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.MaxUploadRate = size
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserAgent 设置User-Agent
|
||||
func (c *PCSConfig) SetUserAgent(userAgent string) {
|
||||
c.UserAgent = userAgent
|
||||
requester.UserAgent = userAgent
|
||||
}
|
||||
|
||||
// SetPCSUA 设置 PCS User-Agent
|
||||
func (c *PCSConfig) SetPCSUA(pcsUA string) {
|
||||
c.PCSUA = pcsUA
|
||||
if c.pcs != nil {
|
||||
c.pcs.SetPCSUserAgent(pcsUA)
|
||||
}
|
||||
}
|
||||
|
||||
// SetPanUA 设置 Pan User-Agent
|
||||
func (c *PCSConfig) SetPanUA(panUA string) {
|
||||
c.PanUA = panUA
|
||||
if c.pcs != nil {
|
||||
c.pcs.SetPanUserAgent(panUA)
|
||||
}
|
||||
}
|
||||
|
||||
// SetEnableHTTPS 设置是否启用https
|
||||
func (c *PCSConfig) SetEnableHTTPS(https bool) {
|
||||
c.EnableHTTPS = https
|
||||
if c.pcs != nil {
|
||||
c.pcs.SetHTTPS(https)
|
||||
}
|
||||
}
|
||||
|
||||
// SetProxy 设置代理
|
||||
func (c *PCSConfig) SetProxy(proxy string) {
|
||||
c.Proxy = proxy
|
||||
requester.SetGlobalProxy(proxy)
|
||||
}
|
||||
|
||||
// SetLocalAddrs 设置localAddrs
|
||||
func (c *PCSConfig) SetLocalAddrs(localAddrs string) {
|
||||
c.LocalAddrs = localAddrs
|
||||
requester.SetLocalTCPAddrList(strings.Split(localAddrs, ",")...)
|
||||
}
|
||||
308
internal/pcsconfig/pcsconfig.go
Normal file
308
internal/pcsconfig/pcsconfig.go
Normal file
@@ -0,0 +1,308 @@
|
||||
// Package pcsconfig 配置包
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/jsonhelper"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsverbose"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"github.com/json-iterator/go"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// EnvConfigDir 配置路径环境变量
|
||||
EnvConfigDir = "BAIDUPCS_GO_CONFIG_DIR"
|
||||
// ConfigName 配置文件名
|
||||
ConfigName = "pcs_config.json"
|
||||
)
|
||||
|
||||
var (
|
||||
pcsConfigVerbose = pcsverbose.New("PCSCONFIG")
|
||||
configFilePath = filepath.Join(GetConfigDir(), ConfigName)
|
||||
|
||||
// Config 配置信息, 由外部调用
|
||||
Config = NewConfig(configFilePath)
|
||||
)
|
||||
|
||||
// PCSConfig 配置详情
|
||||
type PCSConfig struct {
|
||||
BaiduActiveUID uint64 `json:"baidu_active_uid"`
|
||||
BaiduUserList BaiduUserList `json:"baidu_user_list"`
|
||||
|
||||
AppID int `json:"appid"` // appid
|
||||
|
||||
CacheSize int `json:"cache_size"` // 下载缓存
|
||||
MaxParallel int `json:"max_parallel"` // 最大下载并发量
|
||||
MaxUploadParallel int `json:"max_upload_parallel"` // 最大上传并发量
|
||||
MaxDownloadLoad int `json:"max_download_load"` // 同时进行下载文件的最大数量
|
||||
|
||||
MaxDownloadRate int64 `json:"max_download_rate"` // 限制最大下载速度
|
||||
MaxUploadRate int64 `json:"max_upload_rate"` // 限制最大上传速度
|
||||
|
||||
UserAgent string `json:"user_agent"` // 浏览器标识
|
||||
PCSUA string `json:"pcs_ua"` // PCS浏览器标识
|
||||
PanUA string `json:"pan_ua"` // PAN浏览器标识
|
||||
SaveDir string `json:"savedir"` // 下载储存路径
|
||||
EnableHTTPS bool `json:"enable_https"` // 启用https
|
||||
Proxy string `json:"proxy"` // 代理
|
||||
LocalAddrs string `json:"local_addrs"` // 本地网卡地址
|
||||
|
||||
configFilePath string
|
||||
configFile *os.File
|
||||
fileMu sync.Mutex
|
||||
activeUser *Baidu
|
||||
pcs *baidupcs.BaiduPCS
|
||||
}
|
||||
|
||||
// NewConfig 返回 PCSConfig 指针对象
|
||||
func NewConfig(configFilePath string) *PCSConfig {
|
||||
c := &PCSConfig{
|
||||
configFilePath: configFilePath,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Init 初始化配置
|
||||
func (c *PCSConfig) Init() error {
|
||||
return c.init()
|
||||
}
|
||||
|
||||
// Reload 从文件重载配置
|
||||
func (c *PCSConfig) Reload() error {
|
||||
return c.init()
|
||||
}
|
||||
|
||||
// Close 关闭配置文件
|
||||
func (c *PCSConfig) Close() error {
|
||||
if c.configFile != nil {
|
||||
err := c.configFile.Close()
|
||||
c.configFile = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save 保存配置信息到配置文件
|
||||
func (c *PCSConfig) Save() error {
|
||||
// 检测配置项是否合法, 不合法则自动修复
|
||||
c.fix()
|
||||
|
||||
err := c.lazyOpenConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.fileMu.Lock()
|
||||
defer c.fileMu.Unlock()
|
||||
|
||||
data, err := jsoniter.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
// json数据生成失败
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 减掉多余的部分
|
||||
err = c.configFile.Truncate(int64(len(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.configFile.Seek(0, os.SEEK_SET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.configFile.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PCSConfig) init() error {
|
||||
if c.configFilePath == "" {
|
||||
return ErrConfigFileNotExist
|
||||
}
|
||||
|
||||
c.initDefaultConfig()
|
||||
err := c.loadConfigFromFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 载入配置
|
||||
// 如果 activeUser 已初始化, 则跳过
|
||||
if c.activeUser != nil && c.activeUser.UID == c.BaiduActiveUID {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.activeUser, err = c.GetBaiduUser(&BaiduBase{
|
||||
UID: c.BaiduActiveUID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.pcs = c.activeUser.BaiduPCS()
|
||||
|
||||
// 设置全局User-Agent
|
||||
requester.UserAgent = c.UserAgent
|
||||
// 设置全局代理
|
||||
requester.SetGlobalProxy(c.Proxy)
|
||||
// 设置本地网卡地址
|
||||
requester.SetLocalTCPAddrList(strings.Split(c.LocalAddrs, ",")...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lazyOpenConfigFile 打开配置文件
|
||||
func (c *PCSConfig) lazyOpenConfigFile() (err error) {
|
||||
if c.configFile != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.fileMu.Lock()
|
||||
os.MkdirAll(filepath.Dir(c.configFilePath), 0700)
|
||||
c.configFile, err = os.OpenFile(c.configFilePath, os.O_CREATE|os.O_RDWR, 0600)
|
||||
c.fileMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return ErrConfigFileNoPermission
|
||||
}
|
||||
if os.IsExist(err) {
|
||||
return ErrConfigFileNotExist
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadConfigFromFile 载入配置
|
||||
func (c *PCSConfig) loadConfigFromFile() (err error) {
|
||||
err = c.lazyOpenConfigFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 未初始化
|
||||
info, err := c.configFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.Size() == 0 {
|
||||
err = c.Save()
|
||||
return err
|
||||
}
|
||||
|
||||
c.fileMu.Lock()
|
||||
defer c.fileMu.Unlock()
|
||||
|
||||
_, err = c.configFile.Seek(0, os.SEEK_SET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = jsonhelper.UnmarshalData(c.configFile, c)
|
||||
if err != nil {
|
||||
return ErrConfigContentsParseError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PCSConfig) initDefaultConfig() {
|
||||
c.AppID = 266719
|
||||
c.CacheSize = 65536
|
||||
c.MaxParallel = 1
|
||||
c.MaxUploadParallel = 8
|
||||
c.MaxDownloadLoad = 1
|
||||
c.UserAgent = requester.UserAgent
|
||||
c.PCSUA = ""
|
||||
c.PanUA = baidupcs.NetdiskUA
|
||||
c.EnableHTTPS = true
|
||||
|
||||
// 设置默认的下载路径
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
c.SaveDir = pcsutil.ExecutablePathJoin("Downloads")
|
||||
case "android":
|
||||
// TODO: 获取完整的的下载路径
|
||||
c.SaveDir = "/sdcard/Download"
|
||||
default:
|
||||
dataPath, ok := os.LookupEnv("HOME")
|
||||
if !ok {
|
||||
pcsConfigVerbose.Warn("Environment HOME not set")
|
||||
c.SaveDir = pcsutil.ExecutablePathJoin("Downloads")
|
||||
} else {
|
||||
c.SaveDir = filepath.Join(dataPath, "Downloads")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfigDir 获取配置路径
|
||||
func GetConfigDir() string {
|
||||
// 从环境变量读取
|
||||
configDir, ok := os.LookupEnv(EnvConfigDir)
|
||||
if ok {
|
||||
if filepath.IsAbs(configDir) {
|
||||
return configDir
|
||||
}
|
||||
// 如果不是绝对路径, 从程序目录寻找
|
||||
return pcsutil.ExecutablePathJoin(configDir)
|
||||
}
|
||||
|
||||
// 使用旧版
|
||||
// 如果旧版的配置文件存在, 则使用旧版
|
||||
oldConfigDir := pcsutil.ExecutablePath()
|
||||
_, err := os.Stat(filepath.Join(oldConfigDir, ConfigName))
|
||||
if err == nil {
|
||||
return oldConfigDir
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
dataPath, ok := os.LookupEnv("APPDATA")
|
||||
if !ok {
|
||||
pcsConfigVerbose.Warn("Environment APPDATA not set")
|
||||
return oldConfigDir
|
||||
}
|
||||
return filepath.Join(dataPath, "BaiduPCS-Go")
|
||||
default:
|
||||
dataPath, ok := os.LookupEnv("HOME")
|
||||
if !ok {
|
||||
pcsConfigVerbose.Warn("Environment HOME not set")
|
||||
return oldConfigDir
|
||||
}
|
||||
configDir = filepath.Join(dataPath, ".config", "BaiduPCS-Go")
|
||||
|
||||
// 检测是否可写
|
||||
err = os.MkdirAll(configDir, 0700)
|
||||
if err != nil {
|
||||
pcsConfigVerbose.Warnf("check config dir error: %s\n", err)
|
||||
return oldConfigDir
|
||||
}
|
||||
return configDir
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PCSConfig) fix() {
|
||||
if c.CacheSize < 1024 {
|
||||
c.CacheSize = 1024
|
||||
}
|
||||
if c.MaxParallel < 1 {
|
||||
c.MaxParallel = 1
|
||||
}
|
||||
if c.MaxUploadParallel < 1 {
|
||||
c.MaxUploadParallel = 1
|
||||
}
|
||||
if c.MaxDownloadLoad < 1 {
|
||||
c.MaxDownloadLoad = 1
|
||||
}
|
||||
}
|
||||
34
internal/pcsconfig/util.go
Normal file
34
internal/pcsconfig/util.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package pcsconfig
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AverageParallel 返回平均的下载最大并发量
|
||||
func AverageParallel(parallel, downloadLoad int) int {
|
||||
if downloadLoad < 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
p := parallel / downloadLoad
|
||||
if p < 1 {
|
||||
return 1
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func stripPerSecond(sizeStr string) string {
|
||||
i := strings.LastIndex(sizeStr, "/")
|
||||
if i < 0 {
|
||||
return sizeStr
|
||||
}
|
||||
return sizeStr[:i]
|
||||
}
|
||||
|
||||
func showMaxRate(size int64) string {
|
||||
if size <= 0 {
|
||||
return "不限制"
|
||||
}
|
||||
return converter.ConvertFileSize(size, 2) + "/s"
|
||||
}
|
||||
11
internal/pcsfunctions/common.go
Normal file
11
internal/pcsfunctions/common.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package pcsfunctions
|
||||
|
||||
import "time"
|
||||
|
||||
// RetryWait 失败重试等待事件
|
||||
func RetryWait(retry int) time.Duration {
|
||||
if retry < 3 {
|
||||
return 2 * time.Duration(retry) * time.Second
|
||||
}
|
||||
return 6 * time.Second
|
||||
}
|
||||
11
internal/pcsfunctions/pcscaptcha/captchapath.go
Normal file
11
internal/pcsfunctions/pcscaptcha/captchapath.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package pcscaptcha
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CaptchaPath 返回验证码存放路径
|
||||
func CaptchaPath() string {
|
||||
return filepath.Join(os.TempDir(), CaptchaName)
|
||||
}
|
||||
24
internal/pcsfunctions/pcscaptcha/pcscaptcha.go
Normal file
24
internal/pcsfunctions/pcscaptcha/pcscaptcha.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Package pcscaptcha 验证码处理包
|
||||
// TODO: 直接打开验证码
|
||||
package pcscaptcha
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
// CaptchaName 验证码文件名称
|
||||
CaptchaName = "captcha.png"
|
||||
)
|
||||
|
||||
// RemoveOldCaptchaPath 移除旧的验证码路径
|
||||
func RemoveOldCaptchaPath() error {
|
||||
return os.Remove(filepath.Join(pcsconfig.GetConfigDir(), CaptchaName))
|
||||
}
|
||||
|
||||
// RemoveCaptchaPath 移除验证码路径
|
||||
func RemoveCaptchaPath() error {
|
||||
return os.Remove(CaptchaPath())
|
||||
}
|
||||
21
internal/pcsfunctions/pcsdownload/download_link.go
Normal file
21
internal/pcsfunctions/pcsdownload/download_link.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package pcsdownload
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func GetLocateDownloadLinks(pcs *baidupcs.BaiduPCS, pcspath string) (dlinks []*url.URL, err error) {
|
||||
dInfo, pcsError := pcs.LocateDownload(pcspath)
|
||||
if pcsError != nil {
|
||||
return nil, pcsError
|
||||
}
|
||||
|
||||
us := dInfo.URLStrings(pcsconfig.Config.EnableHTTPS)
|
||||
if len(us) == 0 {
|
||||
return nil, ErrDlinkNotFound
|
||||
}
|
||||
|
||||
return us, nil
|
||||
}
|
||||
11
internal/pcsfunctions/pcsdownload/download_statistic.go
Normal file
11
internal/pcsfunctions/pcsdownload/download_statistic.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package pcsdownload
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsfunctions"
|
||||
)
|
||||
|
||||
type (
|
||||
DownloadStatistic struct {
|
||||
pcsfunctions.Statistic
|
||||
}
|
||||
)
|
||||
499
internal/pcsfunctions/pcsdownload/download_task_unit.go
Normal file
499
internal/pcsfunctions/pcsdownload/download_task_unit.go
Normal file
@@ -0,0 +1,499 @@
|
||||
package pcsdownload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsfunctions"
|
||||
"github.com/iikira/BaiduPCS-Go/pcstable"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/taskframework"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsverbose"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/downloader"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/transfer"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// DownloadMode 下载模式
|
||||
DownloadMode int
|
||||
|
||||
// DownloadTaskUnit 下载的任务单元
|
||||
DownloadTaskUnit struct {
|
||||
taskInfo *taskframework.TaskInfo // 任务信息
|
||||
|
||||
Cfg *downloader.Config
|
||||
PCS *baidupcs.BaiduPCS
|
||||
ParentTaskExecutor *taskframework.TaskExecutor
|
||||
|
||||
DownloadStatistic *DownloadStatistic // 下载统计
|
||||
|
||||
// 可选项
|
||||
VerbosePrinter *pcsverbose.PCSVerbose
|
||||
PrintFormat string
|
||||
IsPrintStatus bool // 是否输出各个下载线程的详细信息
|
||||
IsExecutedPermission bool // 下载成功后是否加上执行权限
|
||||
IsOverwrite bool // 是否覆盖已存在的文件
|
||||
NoCheck bool // 不校验文件
|
||||
|
||||
DownloadMode DownloadMode // 下载模式
|
||||
|
||||
PcsPath string // 要下载的网盘文件路径
|
||||
SavePath string // 保存的路径
|
||||
|
||||
fileInfo *baidupcs.FileDirectory // 文件或目录详情
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPrintFormat 默认的下载进度输出格式
|
||||
DefaultPrintFormat = "\r[%s] ↓ %s/%s %s/s in %s, left %s ............"
|
||||
//DownloadSuffix 文件下载后缀
|
||||
DownloadSuffix = ".BaiduPCS-Go-downloading"
|
||||
//StrDownloadInitError 初始化下载发生错误
|
||||
StrDownloadInitError = "初始化下载发生错误"
|
||||
// StrDownloadFailed 下载文件失败
|
||||
StrDownloadFailed = "下载文件失败"
|
||||
// StrDownloadGetDlinkFailed 获取下载链接失败
|
||||
StrDownloadGetDlinkFailed = "获取下载链接失败"
|
||||
// StrDownloadChecksumFailed 检测文件有效性失败
|
||||
StrDownloadChecksumFailed = "检测文件有效性失败"
|
||||
// DefaultDownloadMaxRetry 默认下载失败最大重试次数
|
||||
DefaultDownloadMaxRetry = 3
|
||||
)
|
||||
|
||||
const (
|
||||
DownloadModeLocate DownloadMode = iota
|
||||
DownloadModePCS
|
||||
DownloadModeStreaming
|
||||
)
|
||||
|
||||
func (dtu *DownloadTaskUnit) SetTaskInfo(info *taskframework.TaskInfo) {
|
||||
dtu.taskInfo = info
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) verboseInfof(format string, a ...interface{}) {
|
||||
if dtu.VerbosePrinter != nil {
|
||||
dtu.VerbosePrinter.Infof(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// download 执行下载
|
||||
func (dtu *DownloadTaskUnit) download(downloadURL string, client *requester.HTTPClient) (err error) {
|
||||
var (
|
||||
writer downloader.Writer
|
||||
file *os.File
|
||||
)
|
||||
|
||||
if !dtu.Cfg.IsTest {
|
||||
// 非测试下载
|
||||
dtu.Cfg.InstanceStatePath = dtu.SavePath + DownloadSuffix
|
||||
|
||||
// 创建下载的目录
|
||||
// 获取SavePath所在的目录
|
||||
dir := filepath.Dir(dtu.SavePath)
|
||||
fileInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
// 目录不存在, 创建
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !fileInfo.IsDir() {
|
||||
// SavePath所在的目录不是目录
|
||||
return fmt.Errorf("%s, path %s: not a directory", StrDownloadInitError, dir)
|
||||
}
|
||||
|
||||
// 打开文件
|
||||
writer, file, err = downloader.NewDownloaderWriterByFilename(dtu.SavePath, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s, %s", StrDownloadInitError, err)
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
der := downloader.NewDownloader(downloadURL, writer, dtu.Cfg)
|
||||
der.SetClient(client)
|
||||
der.SetDURLCheckFunc(BaiduPCSURLCheckFunc)
|
||||
der.SetStatusCodeBodyCheckFunc(func(respBody io.Reader) error {
|
||||
// 返回的错误可能是pcs的json
|
||||
// 解析错误
|
||||
return pcserror.DecodePCSJSONError(baidupcs.OperationDownloadFile, respBody)
|
||||
})
|
||||
|
||||
// 检查输出格式
|
||||
if dtu.PrintFormat == "" {
|
||||
dtu.PrintFormat = DefaultPrintFormat
|
||||
}
|
||||
|
||||
// 这里用共享变量的方式
|
||||
isComplete := false
|
||||
der.OnDownloadStatusEvent(func(status transfer.DownloadStatuser, workersCallback func(downloader.RangeWorkerFunc)) {
|
||||
// 这里可能会下载结束了, 还会输出内容
|
||||
builder := &strings.Builder{}
|
||||
if dtu.IsPrintStatus {
|
||||
// 输出所有的worker状态
|
||||
var (
|
||||
tb = pcstable.NewTable(builder)
|
||||
)
|
||||
tb.SetHeader([]string{"#", "status", "range", "left", "speeds", "error"})
|
||||
workersCallback(func(key int, worker *downloader.Worker) bool {
|
||||
wrange := worker.GetRange()
|
||||
tb.Append([]string{fmt.Sprint(worker.ID()), worker.GetStatus().StatusText(), wrange.ShowDetails(), strconv.FormatInt(wrange.Len(), 10), strconv.FormatInt(worker.GetSpeedsPerSecond(), 10), fmt.Sprint(worker.Err())})
|
||||
return true
|
||||
})
|
||||
|
||||
// 先空两行
|
||||
builder.WriteString("\n\n")
|
||||
tb.Render()
|
||||
}
|
||||
|
||||
// 如果下载速度为0, 剩余下载时间未知, 则用 - 代替
|
||||
var leftStr string
|
||||
left := status.TimeLeft()
|
||||
if left < 0 {
|
||||
leftStr = "-"
|
||||
} else {
|
||||
leftStr = left.String()
|
||||
}
|
||||
|
||||
fmt.Fprintf(builder,dtu.PrintFormat, dtu.taskInfo.Id(),
|
||||
converter.ConvertFileSize(status.Downloaded(), 2),
|
||||
converter.ConvertFileSize(status.TotalSize(), 2),
|
||||
converter.ConvertFileSize(status.SpeedsPerSecond(), 2),
|
||||
status.TimeElapsed()/1e7*1e7, leftStr,
|
||||
)
|
||||
|
||||
if !isComplete {
|
||||
// 如果未完成下载, 就输出
|
||||
fmt.Print(builder.String())
|
||||
}
|
||||
})
|
||||
|
||||
der.OnExecute(func() {
|
||||
if dtu.Cfg.IsTest {
|
||||
fmt.Printf("[%s] 测试下载开始\n\n", dtu.taskInfo.Id())
|
||||
}
|
||||
})
|
||||
|
||||
err = der.Execute()
|
||||
isComplete = true
|
||||
fmt.Print("\n")
|
||||
|
||||
if err != nil {
|
||||
// 下载发生错误
|
||||
if !dtu.Cfg.IsTest {
|
||||
// 下载失败, 删去空文件
|
||||
if info, infoErr := file.Stat(); infoErr == nil {
|
||||
if info.Size() == 0 {
|
||||
// 空文件, 应该删除
|
||||
dtu.verboseInfof("[%s] remove empty file: %s\n", dtu.taskInfo.Id(), dtu.SavePath)
|
||||
removeErr := os.Remove(dtu.SavePath)
|
||||
if removeErr != nil {
|
||||
dtu.verboseInfof("[%s] remove file error: %s\n", dtu.taskInfo.Id(), removeErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 下载成功
|
||||
if !dtu.Cfg.IsTest {
|
||||
if dtu.IsExecutedPermission {
|
||||
err = file.Chmod(0766)
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] 警告, 加执行权限错误: %s\n", dtu.taskInfo.Id(), err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] 下载完成, 保存位置: %s\n", dtu.taskInfo.Id(), dtu.SavePath)
|
||||
} else {
|
||||
fmt.Printf("[%s] 测试下载结束\n", dtu.taskInfo.Id())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//panHTTPClient 获取包含特定User-Agent的HTTPClient
|
||||
func (dtu *DownloadTaskUnit) panHTTPClient() (client *requester.HTTPClient) {
|
||||
client = pcsconfig.Config.PanHTTPClient()
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
// 去掉 Referer
|
||||
if !pcsconfig.Config.EnableHTTPS {
|
||||
req.Header.Del("Referer")
|
||||
}
|
||||
if len(via) >= 10 {
|
||||
return errors.New("stopped after 10 redirects")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
client.SetTimeout(20 * time.Minute)
|
||||
client.SetKeepAlive(true)
|
||||
return client
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) handleError(result *taskframework.TaskUnitRunResult) {
|
||||
switch value := result.Err.(type) {
|
||||
case pcserror.Error: // pcserror 接口
|
||||
switch value.GetErrType() {
|
||||
case pcserror.ErrTypeRemoteError:
|
||||
// 远程服务器错误
|
||||
case 31045: // user not exists
|
||||
fallthrough
|
||||
case 31066: // file does not exist
|
||||
result.NeedRetry = false
|
||||
case 31626: // user is not authorized
|
||||
//可能是User-Agent不对
|
||||
//重试
|
||||
fallthrough
|
||||
default:
|
||||
result.NeedRetry = true
|
||||
}
|
||||
case *os.PathError:
|
||||
// 系统级别的错误, 可能是权限问题
|
||||
result.NeedRetry = false
|
||||
default:
|
||||
// 其他错误, 需要重试
|
||||
result.NeedRetry = true
|
||||
}
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) execPanDownload(dlink string, result *taskframework.TaskUnitRunResult, okPtr *bool) {
|
||||
dtu.verboseInfof("[%s] 获取到下载链接: %s\n", dtu.taskInfo.Id(), dlink)
|
||||
|
||||
client := dtu.panHTTPClient()
|
||||
err := dtu.download(dlink, client)
|
||||
if err != nil {
|
||||
result.ResultMessage = StrDownloadFailed
|
||||
result.Err = err
|
||||
dtu.handleError(result)
|
||||
return
|
||||
}
|
||||
*okPtr = true
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) locateDownload(result *taskframework.TaskUnitRunResult) (ok bool) {
|
||||
rawDlinks, err := GetLocateDownloadLinks(dtu.PCS, dtu.PcsPath)
|
||||
if err != nil {
|
||||
result.ResultMessage = StrDownloadGetDlinkFailed
|
||||
result.Err = err
|
||||
dtu.handleError(result)
|
||||
return
|
||||
}
|
||||
|
||||
// 更新链接的协议
|
||||
FixHTTPLinkURL(rawDlinks[0])
|
||||
dlink := rawDlinks[0].String()
|
||||
|
||||
dtu.execPanDownload(dlink, result, &ok)
|
||||
return
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) pcsOrStreamingDownload(mode DownloadMode, result *taskframework.TaskUnitRunResult) (ok bool) {
|
||||
dfunc := func(downloadURL string, jar http.CookieJar) error {
|
||||
client := pcsconfig.Config.PCSHTTPClient()
|
||||
client.SetCookiejar(jar)
|
||||
client.SetKeepAlive(true)
|
||||
client.SetTimeout(10 * time.Minute)
|
||||
|
||||
return dtu.download(downloadURL, client)
|
||||
}
|
||||
|
||||
var err error
|
||||
switch mode {
|
||||
case DownloadModePCS:
|
||||
err = dtu.PCS.DownloadFile(dtu.PcsPath, dfunc)
|
||||
case DownloadModeStreaming:
|
||||
err = dtu.PCS.DownloadStreamFile(dtu.PcsPath, dfunc)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.ResultMessage = StrDownloadFailed
|
||||
result.Err = err
|
||||
dtu.handleError(result)
|
||||
return
|
||||
}
|
||||
return true // 下载成功
|
||||
}
|
||||
|
||||
//checkFileValid 检测文件有效性
|
||||
func (dtu *DownloadTaskUnit) checkFileValid(result *taskframework.TaskUnitRunResult) (ok bool) {
|
||||
if dtu.Cfg.IsTest || dtu.NoCheck {
|
||||
// 不检测文件有效性
|
||||
return
|
||||
}
|
||||
|
||||
if dtu.fileInfo.Size >= 128*converter.MB {
|
||||
// 大文件, 输出一句提示消息
|
||||
fmt.Printf("[%s] 开始检验文件有效性, 请稍候...\n", dtu.taskInfo.Id())
|
||||
}
|
||||
|
||||
// 就在这里处理校验出错
|
||||
err := CheckFileValid(dtu.SavePath, dtu.fileInfo)
|
||||
if err != nil {
|
||||
result.ResultMessage = StrDownloadChecksumFailed
|
||||
result.Err = err
|
||||
switch err {
|
||||
case ErrDownloadNotSupportChecksum:
|
||||
// 文件不支持校验
|
||||
result.ResultMessage = "检验文件有效性"
|
||||
result.Err = err
|
||||
fmt.Printf("[%s] 检验文件有效性: %s\n", dtu.taskInfo.Id(), err)
|
||||
return true
|
||||
case ErrDownloadFileBanned:
|
||||
// 违规文件
|
||||
result.NeedRetry = false
|
||||
return
|
||||
case ErrDownloadChecksumFailed:
|
||||
// 校验失败, 需要重新下载
|
||||
result.NeedRetry = true
|
||||
// 设置允许覆盖
|
||||
dtu.IsOverwrite = true
|
||||
return
|
||||
default:
|
||||
result.NeedRetry = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] 检验文件有效性成功: %s\n", dtu.taskInfo.Id(), dtu.SavePath)
|
||||
return true
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) OnRetry(lastRunResult *taskframework.TaskUnitRunResult) {
|
||||
// 输出错误信息
|
||||
if lastRunResult.Err == nil {
|
||||
// result中不包含Err, 忽略输出
|
||||
fmt.Printf("[%s] %s, 重试 %d/%d\n", dtu.taskInfo.Id(), lastRunResult.ResultMessage, dtu.taskInfo.Retry(), dtu.taskInfo.MaxRetry())
|
||||
return
|
||||
}
|
||||
fmt.Printf("[%s] %s, %s, 重试 %d/%d\n", dtu.taskInfo.Id(), lastRunResult.ResultMessage, lastRunResult.Err, dtu.taskInfo.Retry(), dtu.taskInfo.MaxRetry())
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) OnSuccess(lastRunResult *taskframework.TaskUnitRunResult) {
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) OnFailed(lastRunResult *taskframework.TaskUnitRunResult) {
|
||||
// 失败
|
||||
if lastRunResult.Err == nil {
|
||||
// result中不包含Err, 忽略输出
|
||||
fmt.Printf("[%s] %s\n", dtu.taskInfo.Id(), lastRunResult.ResultMessage)
|
||||
return
|
||||
}
|
||||
fmt.Printf("[%s] %s, %s\n", dtu.taskInfo.Id(), lastRunResult.ResultMessage, lastRunResult.Err)
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) OnComplete(lastRunResult *taskframework.TaskUnitRunResult) {
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) RetryWait() time.Duration {
|
||||
return pcsfunctions.RetryWait(dtu.taskInfo.Retry())
|
||||
}
|
||||
|
||||
func (dtu *DownloadTaskUnit) Run() (result *taskframework.TaskUnitRunResult) {
|
||||
result = &taskframework.TaskUnitRunResult{}
|
||||
// 获取文件信息
|
||||
var err error
|
||||
if dtu.fileInfo == nil || dtu.taskInfo.Retry() > 0 {
|
||||
// 没有获取文件信息
|
||||
// 如果是动态添加的下载任务, 是会写入文件信息的
|
||||
// 如果该任务重试过, 则应该再获取一次文件信息
|
||||
dtu.fileInfo, err = dtu.PCS.FilesDirectoriesMeta(dtu.PcsPath)
|
||||
if err != nil {
|
||||
// 如果不是未登录或文件不存在, 则不重试
|
||||
result.ResultMessage = "获取下载路径信息错误"
|
||||
result.Err = err
|
||||
dtu.handleError(result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 输出文件信息
|
||||
fmt.Print("\n")
|
||||
fmt.Printf("[%s] ----\n%s\n", dtu.taskInfo.Id(), dtu.fileInfo.String())
|
||||
|
||||
// 如果是一个目录, 将子文件和子目录加入队列
|
||||
if dtu.fileInfo.Isdir {
|
||||
if !dtu.Cfg.IsTest { // 测试下载, 不建立空目录
|
||||
os.MkdirAll(dtu.SavePath, 0777) // 首先在本地创建目录, 保证空目录也能被保存
|
||||
}
|
||||
|
||||
// 获取该目录下的文件列表
|
||||
fileList, err := dtu.PCS.FilesDirectoriesList(dtu.PcsPath, baidupcs.DefaultOrderOptions)
|
||||
if err != nil {
|
||||
result.ResultMessage = "获取目录信息错误"
|
||||
result.Err = err
|
||||
result.NeedRetry = true
|
||||
return
|
||||
}
|
||||
|
||||
for k := range fileList {
|
||||
// 添加子任务
|
||||
subUnit := *dtu
|
||||
newCfg := *dtu.Cfg
|
||||
subUnit.Cfg = &newCfg
|
||||
subUnit.fileInfo = fileList[k] // 保存文件信息
|
||||
subUnit.PcsPath = fileList[k].Path
|
||||
subUnit.SavePath = filepath.Join(dtu.SavePath, fileList[k].Filename) // 保存位置
|
||||
|
||||
// 加入父队列
|
||||
info := dtu.ParentTaskExecutor.Append(&subUnit, dtu.taskInfo.MaxRetry())
|
||||
fmt.Printf("[%s] 加入下载队列: %s\n", info.Id(), fileList[k].Path)
|
||||
}
|
||||
|
||||
result.Succeed = true // 执行成功
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] 准备下载: %s\n", dtu.taskInfo.Id(), dtu.PcsPath)
|
||||
|
||||
if !dtu.Cfg.IsTest && !dtu.IsOverwrite && FileExist(dtu.SavePath) {
|
||||
fmt.Printf("[%s] 文件已经存在: %s, 跳过...\n", dtu.taskInfo.Id(), dtu.SavePath)
|
||||
result.Succeed = true // 执行成功
|
||||
return
|
||||
}
|
||||
|
||||
if !dtu.Cfg.IsTest {
|
||||
// 不是测试下载, 输出下载路径
|
||||
fmt.Printf("[%s] 将会下载到路径: %s\n\n", dtu.taskInfo.Id(), dtu.SavePath)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
// 获取下载链接
|
||||
switch dtu.DownloadMode {
|
||||
case DownloadModeLocate:
|
||||
ok = dtu.locateDownload(result)
|
||||
case DownloadModePCS, DownloadModeStreaming:
|
||||
ok = dtu.pcsOrStreamingDownload(dtu.DownloadMode, result)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// 以上执行不成功, 返回
|
||||
return result
|
||||
}
|
||||
|
||||
// 检测文件有效性
|
||||
ok = dtu.checkFileValid(result)
|
||||
if !ok {
|
||||
// 校验不成功, 返回结果
|
||||
return result
|
||||
}
|
||||
|
||||
// 统计下载
|
||||
dtu.DownloadStatistic.AddTotalSize(dtu.fileInfo.Size)
|
||||
// 下载成功
|
||||
result.Succeed = true
|
||||
return
|
||||
}
|
||||
16
internal/pcsfunctions/pcsdownload/errors.go
Normal file
16
internal/pcsfunctions/pcsdownload/errors.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package pcsdownload
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrDownloadNotSupportChecksum 文件不支持校验
|
||||
ErrDownloadNotSupportChecksum = errors.New("该文件不支持校验")
|
||||
// ErrDownloadChecksumFailed 文件校验失败
|
||||
ErrDownloadChecksumFailed = errors.New("该文件校验失败, 文件md5值与服务器记录的不匹配")
|
||||
// ErrDownloadFileBanned 违规文件
|
||||
ErrDownloadFileBanned = errors.New("该文件可能是违规文件, 不支持校验")
|
||||
// ErrDlinkNotFound 未取得下载链接
|
||||
ErrDlinkNotFound = errors.New("未取得下载链接")
|
||||
// ErrShareInfoNotFound 未在已分享列表中找到分享信息
|
||||
ErrShareInfoNotFound = errors.New("未在已分享列表中找到分享信息")
|
||||
)
|
||||
36
internal/pcsfunctions/pcsdownload/pcsdownload.go
Normal file
36
internal/pcsfunctions/pcsdownload/pcsdownload.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package pcsdownload
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// IsSkipMd5Checksum 是否忽略某些校验
|
||||
func IsSkipMd5Checksum(size int64, md5Str string) bool {
|
||||
switch {
|
||||
case size == 1749504 && md5Str == "48bb9b0361dc9c672f3dc7b3ffcfde97": //8秒温馨提示
|
||||
fallthrough
|
||||
case size == 120 && md5Str == "6c1b84914588d09a6e5ec43605557457": //温馨提示文字版
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BaiduPCSURLCheckFunc downloader 首次检查下载地址要执行的函数
|
||||
func BaiduPCSURLCheckFunc(client *requester.HTTPClient, durl string) (contentLength int64, resp *http.Response, err error) {
|
||||
resp, err = client.Req(http.MethodGet, durl, nil, map[string]string{
|
||||
"Range": "bytes=0-" + strconv.FormatInt(baidupcs.MaxDownloadRangeSize-1, 10),
|
||||
})
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
contentLengthStr := resp.Header.Get("x-bs-file-size")
|
||||
contentLength, _ = strconv.ParseInt(contentLengthStr, 10, 64)
|
||||
return contentLength, resp, nil
|
||||
}
|
||||
63
internal/pcsfunctions/pcsdownload/utils.go
Normal file
63
internal/pcsfunctions/pcsdownload/utils.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package pcsdownload
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/checksum"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CheckFileValid 检测文件有效性
|
||||
func CheckFileValid(filePath string, fileInfo *baidupcs.FileDirectory) error {
|
||||
if len(fileInfo.BlockList) != 1 {
|
||||
return ErrDownloadNotSupportChecksum
|
||||
}
|
||||
|
||||
f := checksum.NewLocalFileChecksum(filePath, int(baidupcs.SliceMD5Size))
|
||||
err := f.OpenPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = f.Sum(checksum.CHECKSUM_MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md5Str := hex.EncodeToString(f.MD5)
|
||||
|
||||
if md5Str != fileInfo.MD5 { // md5不一致
|
||||
// 检测是否为违规文件
|
||||
if IsSkipMd5Checksum(f.Length, md5Str) {
|
||||
return ErrDownloadFileBanned
|
||||
}
|
||||
return ErrDownloadChecksumFailed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileExist 检查文件是否存在,
|
||||
// 只有当文件存在, 文件大小不为0或断点续传文件不存在时, 才判断为存在
|
||||
func FileExist(path string) bool {
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
if info.Size() == 0 {
|
||||
return false
|
||||
}
|
||||
if _, err = os.Stat(path + DownloadSuffix); err != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//FixHTTPLinkURL 通过配置, 确定链接使用的协议(http,https)
|
||||
func FixHTTPLinkURL(linkURL *url.URL) {
|
||||
if pcsconfig.Config.EnableHTTPS {
|
||||
if linkURL.Scheme == "http" {
|
||||
linkURL.Scheme = "https"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
internal/pcsfunctions/pcsupload/pcsupload.go
Normal file
14
internal/pcsfunctions/pcsupload/pcsupload.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Package pcsupload 上传包
|
||||
package pcsupload
|
||||
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/pcsverbose"
|
||||
)
|
||||
|
||||
const (
|
||||
UploadingFileName = "pcs_uploading.json"
|
||||
)
|
||||
|
||||
var (
|
||||
pcsUploadVerbose = pcsverbose.New("PCSUPLOAD")
|
||||
)
|
||||
119
internal/pcsfunctions/pcsupload/upload.go
Normal file
119
internal/pcsfunctions/pcsupload/upload.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package pcsupload
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs/pcserror"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/requester"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/multipartreader"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/rio"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/uploader"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
PCSUpload struct {
|
||||
pcs *baidupcs.BaiduPCS
|
||||
targetPath string
|
||||
}
|
||||
|
||||
EmptyReaderLen64 struct {
|
||||
}
|
||||
)
|
||||
|
||||
func (e EmptyReaderLen64) Read(p []byte) (n int, err error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (e EmptyReaderLen64) Len() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewPCSUpload(pcs *baidupcs.BaiduPCS, targetPath string) uploader.MultiUpload {
|
||||
return &PCSUpload{
|
||||
pcs: pcs,
|
||||
targetPath: targetPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (pu *PCSUpload) lazyInit() {
|
||||
if pu.pcs == nil {
|
||||
pu.pcs = &baidupcs.BaiduPCS{}
|
||||
}
|
||||
}
|
||||
|
||||
// Precreate do nothing
|
||||
func (pu *PCSUpload) Precreate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pu *PCSUpload) TmpFile(ctx context.Context, partseq int, partOffset int64, r rio.ReaderLen64) (checksum string, uperr error) {
|
||||
pu.lazyInit()
|
||||
|
||||
var respErr *uploader.MultiError
|
||||
checksum, pcsError := pu.pcs.UploadTmpFile(func(uploadURL string, jar http.CookieJar) (resp *http.Response, err error) {
|
||||
client := pcsconfig.Config.PCSHTTPClient()
|
||||
client.SetCookiejar(jar)
|
||||
client.SetTimeout(0)
|
||||
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFile("uploadedfile", "", r)
|
||||
mr.CloseMultipart()
|
||||
|
||||
doneChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
resp, err = client.Req(http.MethodPost, uploadURL, mr, nil)
|
||||
doneChan <- struct{}{}
|
||||
|
||||
if resp != nil {
|
||||
// 不可恢复的错误
|
||||
switch resp.StatusCode {
|
||||
case 400, 401, 403, 413:
|
||||
respErr = &uploader.MultiError{
|
||||
Terminated: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done(): // 取消
|
||||
// 返回, 让那边关闭连接
|
||||
return resp, ctx.Err()
|
||||
case <-doneChan:
|
||||
// return
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if respErr != nil {
|
||||
respErr.Err = pcsError
|
||||
return checksum, respErr
|
||||
}
|
||||
|
||||
return checksum, pcsError
|
||||
}
|
||||
|
||||
func (pu *PCSUpload) CreateSuperFile(checksumList ...string) (err error) {
|
||||
pu.lazyInit()
|
||||
|
||||
// 先在网盘目标位置, 上传一个空文件
|
||||
// 防止出现file does not exist
|
||||
pcsError := pu.pcs.Upload(pu.targetPath, func(uploadURL string, jar http.CookieJar) (resp *http.Response, err error) {
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFile("file", "file", &EmptyReaderLen64{})
|
||||
mr.CloseMultipart()
|
||||
|
||||
c := requester.NewHTTPClient()
|
||||
c.SetCookiejar(jar)
|
||||
return c.Req(http.MethodPost, uploadURL, mr, nil)
|
||||
})
|
||||
if pcsError != nil {
|
||||
// 修改操作
|
||||
pcsError.(*pcserror.PCSErrInfo).Operation = baidupcs.OperationUploadCreateSuperFile
|
||||
return pcsError
|
||||
}
|
||||
|
||||
return pu.pcs.UploadCreateSuperFile(false, pu.targetPath, checksumList...)
|
||||
}
|
||||
65
internal/pcsfunctions/pcsupload/upload2.go
Normal file
65
internal/pcsfunctions/pcsupload/upload2.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package pcsupload
|
||||
|
||||
/*
|
||||
import (
|
||||
"github.com/iikira/BaiduPCS-Go/baidupcs"
|
||||
)
|
||||
|
||||
type (
|
||||
// PCSUpload2 新的上传方式
|
||||
// TODO
|
||||
PCSUpload2 struct {
|
||||
pcs *baidupcs.BaiduPCS
|
||||
targetPath string
|
||||
uploadid string
|
||||
}
|
||||
)
|
||||
|
||||
func NewPCSUpload2(pcs *baidupcs.BaiduPCS, targetPath string) uploader.MultiUpload {
|
||||
return &PCSUpload{
|
||||
pcs: pcs,
|
||||
targetPath: targetPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (pu2 *PCSUpload2) lazyInit() {
|
||||
if pu2.pcs == nil {
|
||||
pu2.pcs = &baidupcs.BaiduPCS{}
|
||||
}
|
||||
}
|
||||
|
||||
// Precreate
|
||||
func (pu2 *PCSUpload2) Precreate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pu2 *PCSUpload2) TmpFile(ctx context.Context, partseq int, partOffset int64, r rio.ReaderLen64) (checksum string, uperr error) {
|
||||
pu2.lazyInit()
|
||||
return pu.pcs.UploadTmpFile(func(uploadURL string, jar http.CookieJar) (resp *http.Response, err error) {
|
||||
client := pcsconfig.Config.HTTPClient()
|
||||
client.SetCookiejar(jar)
|
||||
client.SetTimeout(0)
|
||||
|
||||
mr := multipartreader.NewMultipartReader()
|
||||
mr.AddFormFile("uploadedfile", "", r)
|
||||
mr.CloseMultipart()
|
||||
|
||||
doneChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
resp, err = client.Req("POST", uploadURL, mr, nil)
|
||||
doneChan <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return resp, ctx.Err()
|
||||
case <-doneChan:
|
||||
// return
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (pu2 *PCSUpload2) CreateSuperFile(checksumList ...string) (err error) {
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
198
internal/pcsfunctions/pcsupload/upload_database.go
Normal file
198
internal/pcsfunctions/pcsupload/upload_database.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package pcsupload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iikira/BaiduPCS-Go/internal/pcsconfig"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/checksum"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/converter"
|
||||
"github.com/iikira/BaiduPCS-Go/pcsutil/jsonhelper"
|
||||
"github.com/iikira/BaiduPCS-Go/requester/uploader"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Uploading 未完成上传的信息
|
||||
Uploading struct {
|
||||
*checksum.LocalFileMeta
|
||||
State *uploader.InstanceState `json:"state"`
|
||||
}
|
||||
|
||||
// UploadingDatabase 未完成上传的数据库
|
||||
UploadingDatabase struct {
|
||||
UploadingList []*Uploading `json:"upload_state"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
dataFile *os.File
|
||||
}
|
||||
)
|
||||
|
||||
// NewUploadingDatabase 初始化未完成上传的数据库, 从库中读取内容
|
||||
func NewUploadingDatabase() (ud *UploadingDatabase, err error) {
|
||||
file, err := os.OpenFile(filepath.Join(pcsconfig.GetConfigDir(), UploadingFileName), os.O_CREATE|os.O_RDWR, 0777)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ud = &UploadingDatabase{
|
||||
dataFile: file,
|
||||
}
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if info.Size() <= 0 {
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
err = jsonhelper.UnmarshalData(file, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
// Save 保存内容
|
||||
func (ud *UploadingDatabase) Save() error {
|
||||
if ud.dataFile == nil {
|
||||
return errors.New("dataFile is nil")
|
||||
}
|
||||
|
||||
ud.Timestamp = time.Now().Unix()
|
||||
|
||||
var (
|
||||
builder = &strings.Builder{}
|
||||
err = jsonhelper.MarshalData(builder, ud)
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ud.dataFile.Truncate(int64(builder.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str := builder.String()
|
||||
_, err = ud.dataFile.WriteAt(converter.ToBytes(str), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUploading 更新正在上传
|
||||
func (ud *UploadingDatabase) UpdateUploading(meta *checksum.LocalFileMeta, state *uploader.InstanceState) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta.CompleteAbsPath()
|
||||
for k, uploading := range ud.UploadingList {
|
||||
if uploading.LocalFileMeta == nil {
|
||||
continue
|
||||
}
|
||||
if uploading.LocalFileMeta.EqualLengthMD5(meta) || uploading.LocalFileMeta.Path == meta.Path {
|
||||
ud.UploadingList[k].State = state
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ud.UploadingList = append(ud.UploadingList, &Uploading{
|
||||
LocalFileMeta: meta,
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
|
||||
func (ud *UploadingDatabase) deleteIndex(k int) {
|
||||
ud.UploadingList = append(ud.UploadingList[:k], ud.UploadingList[k+1:]...)
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
func (ud *UploadingDatabase) Delete(meta *checksum.LocalFileMeta) bool {
|
||||
if meta == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
meta.CompleteAbsPath()
|
||||
for k, uploading := range ud.UploadingList {
|
||||
if uploading.LocalFileMeta == nil {
|
||||
continue
|
||||
}
|
||||
if uploading.LocalFileMeta.EqualLengthMD5(meta) || uploading.LocalFileMeta.Path == meta.Path {
|
||||
ud.deleteIndex(k)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Search 搜索
|
||||
func (ud *UploadingDatabase) Search(meta *checksum.LocalFileMeta) *uploader.InstanceState {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
meta.CompleteAbsPath()
|
||||
ud.clearModTimeChange()
|
||||
for _, uploading := range ud.UploadingList {
|
||||
if uploading.LocalFileMeta == nil {
|
||||
continue
|
||||
}
|
||||
if uploading.LocalFileMeta.EqualLengthMD5(meta) {
|
||||
return uploading.State
|
||||
}
|
||||
if uploading.LocalFileMeta.Path == meta.Path {
|
||||
// 移除旧的信息
|
||||
// 目前只是比较了文件大小
|
||||
if meta.Length != uploading.LocalFileMeta.Length {
|
||||
ud.Delete(meta)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 覆盖数据
|
||||
meta.MD5 = uploading.LocalFileMeta.MD5
|
||||
meta.SliceMD5 = uploading.LocalFileMeta.SliceMD5
|
||||
return uploading.State
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ud *UploadingDatabase) clearModTimeChange() {
|
||||
for i := 0; i < len(ud.UploadingList); i++ {
|
||||
uploading := ud.UploadingList[i]
|
||||
if uploading.LocalFileMeta == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if uploading.ModTime == -1 { // 忽略
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(uploading.LocalFileMeta.Path)
|
||||
if err != nil {
|
||||
ud.deleteIndex(i)
|
||||
i--
|
||||
pcsUploadVerbose.Warnf("clear invalid file path: %s, err: %s\n", uploading.LocalFileMeta.Path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if uploading.LocalFileMeta.ModTime != info.ModTime().Unix() {
|
||||
ud.deleteIndex(i)
|
||||
i--
|
||||
pcsUploadVerbose.Infof("clear modified file path: %s\n", uploading.LocalFileMeta.Path)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close 关闭数据库
|
||||
func (ud *UploadingDatabase) Close() error {
|
||||
return ud.dataFile.Close()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user