Skip to content

Commit 20b8b79

Browse files
committed
first commit
0 parents  commit 20b8b79

File tree

347 files changed

+169645
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

347 files changed

+169645
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.history
2+
config.json
3+
temp.torrent
4+
.goreleaser.yml
5+
dist
6+
.idea
7+
info
8+
*.json
9+
*.exe

Readme.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# OneDriveUploader
2+
3+
萌咖大佬写了一个[非常好的版本](https://github.com/MoeClub/OneList/tree/master/OneDriveUploader),可惜并没有开源,而且已经好久都没有更新了。这个项目作为从[DownloadBot](https://github.com/gaowanliang/DownloadBot)中独立出来的一个简易上传工具,使得上传更加方便。同时会逐渐向萌咖的版本完善相关的功能,目前版本仅仅是从[DownloadBot](https://github.com/gaowanliang/DownloadBot)中独立的,只做了简单的功能。
4+
5+
6+
- 支持 国际版, 个人版(家庭版), ~~中国版(世纪互联)~~.
7+
- ~~支持上传文件和文件夹到指定目录,并保持上传前的目录结构.~~
8+
- 支持命令参数使用, 方便外部程序调用.
9+
- 支持自定义上传分块大小.
10+
- 支持多线程上传(多文件同时上传).
11+
- ~~支持根据文件大小动态调整重试次数~~
12+
- ~~支持跳过网盘中已存在的同名文件.~~
13+
- 支持通过Telegram Bot实时监控上传进度,方便使用全自动下载脚本时对上传的实时监控
14+
15+
## 授权
16+
### 通过下面URL登录 (右键新标签打开)
17+
#### 国际版, 个人版(家庭版)
18+
[https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=ad5e65fd-856d-4356-aefc-537a9700c137&response_type=code&redirect_uri=http://localhost/onedrive-login&response_mode=query&scope=offline_access%20User.Read%20Files.ReadWrite.All](https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=ad5e65fd-856d-4356-aefc-537a9700c137&response_type=code&redirect_uri=http://localhost/onedrive-login&response_mode=query&scope=offline_access%20User.Read%20Files.ReadWrite.All)
19+
#### 中国版(世纪互联)
20+
21+
22+
### 初始化配置文件
23+
```bash
24+
# 国际版
25+
OneDriveUploader -a "url"
26+
27+
# 个人版(家庭版)
28+
OneDriveUploader -a "url" -v 1
29+
# 中国版(世纪互联) 目前设计中,暂不可用
30+
OneDriveUploader -a "url" -v 2
31+
32+
# 在浏览器地址栏中获取以 http://loaclhost 开头的整个url内容
33+
# 将获取的完整url内容替换命令中的 url 三个字母
34+
# 每次产生的 url 只能用一次, 重试请重新获取 url
35+
# 此操作将会自动初始化的配置文件
36+
```
37+
38+
## 使用
39+
```bash
40+
Usage of OneDriveUploader:
41+
-a string
42+
// 初始化授权
43+
Setup and Init auth.json.
44+
-b string
45+
// 自定义上传分块大小, 可以提高网络吞吐量, 受限于磁盘性能和网络速度.
46+
Set block size. [Unit: M; 5<=b<=60;] (default "10")
47+
-c string
48+
// 配置文件路径
49+
Config file. (default "auth.json")
50+
51+
//此参数未设计,暂不可用
52+
-n string
53+
// 上传单个文件时,在网盘中重命名
54+
Rename file on upload to remote.
55+
56+
//此参数未设计,暂不可用
57+
-r string
58+
// 上传到网盘中的某个目录, 默认: 根目录
59+
Upload to reomte path.
60+
61+
-f string
62+
// *必要参数, 要上传的文件或文件夹
63+
Upload item.
64+
-t string
65+
// 线程数, 同时上传文件的个数. 默认: 3
66+
Set thread num. (default "3")
67+
68+
//此参数未设计,暂不可用
69+
-force
70+
// 开关(推荐)
71+
// 加上 -f 参数,强制读取 auth.json 中的块大小配置和多线程配置.
72+
// 不加 -f 参数, 每次覆盖保存当前使用参数到 auth.json 配置文件中.
73+
Force Read config form config file. [BlockSize, ThreadNum]
74+
75+
//此参数未设计,暂不可用
76+
-skip
77+
// 开关
78+
// 跳过上传网盘中已存在的同名文件. (默认不跳过)
79+
Skip exist file on remote.
80+
81+
```
82+
83+
## 配置
84+
```json
85+
{
86+
// 授权令牌
87+
"RefreshToken": "1234564567890ABCDEF",
88+
// 最大线程数.(同时上传文件的数量)
89+
"ThreadNum": "2",
90+
// 最大上传分块大小.(每次上传文件的最大分块大小,网络不好建议调低. 单位:MB)
91+
"BlockSize": "10",
92+
// 最大单文件大小.(目前: 个人版(家庭版)单文件限制为100GB; 其他版本单文件限制为15GB,微软将逐步更新为100GB. 单位:GB)
93+
"SigleFile": "100",
94+
// 缓存刷新间隔.
95+
"RefreshInterval": 1500,
96+
// 如果是中国版(世纪互联), 此项应为 true.
97+
"MainLand": false
98+
}
99+
```
100+
101+
## 示例
102+
```bash
103+
# 一些示例:
104+
105+
# 将同目录下的 mm00.jpg 文件上传到 OneDrive 网盘根目录
106+
OneDriveUploader -c xxx.json -f "mm00.jpg"
107+
108+
# 将同目录下的 mm00.jpg 文件上传到 OneDrive 网盘根目录,并改名为 mm01.jpg(暂不可用)
109+
OneDriveUploader -c xxx.json -s "mm00.jpg" -n "mm01.jpg"
110+
111+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘根目录
112+
OneDriveUploader -c xxx.json -s "Download"
113+
114+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘Test目录中(暂不可用)
115+
OneDriveUploader -c xxx.json -s "Download" -r "Test"
116+
117+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘根目录中, 使用 10 线程
118+
OneDriveUploader -c xxx.json -t 10 -s "Download"
119+
120+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘根目录中, 使用 10 线程,同时使用 Telegram Bot 实时监控上传进度
121+
OneDriveUploader -c xxx.json -t 10 -s "Download" -tgbot "bot123456:xxxxxxxx" -uid 123456789
122+
123+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘根目录中, 使用 15 线程, 并设置分块大小为 20M
124+
OneDriveUploader -c xxx.json -t 15 -b 20 -s "Download"
125+
126+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘Test目录中, 使用配置文件中的线程参数和分块大小参数(暂不可用)
127+
OneDriveUploader -f -c "/urs/local/auth.json" -s "Download" -r "Test"
128+
129+
# 将同目录下的 Download 文件夹上传到 OneDrive 网盘Test目录中, 使用配置文件中的线程参数和分块大小参数,并跳过上传网盘中已存在的同名文件(暂不可用)
130+
OneDriveUploader -f -c "/urs/local/auth.json" -skip -s "Download" -r "Test"
131+
```
132+
133+
## 注意
134+
- ~~多次尝试后, 无失败的上传文件. 退出码为 0 .~~
135+
- ~~最终还有失败的上传文件会详细列出上传失败项. 退出码为 1.~~
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package upload
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"main/fileutil"
9+
"runtime/debug"
10+
"strconv"
11+
"time"
12+
)
13+
14+
const (
15+
uploadSessionPath = "/users/%s/drive/root:/%s:/createUploadSession"
16+
uploadURLKey = "uploadUrl"
17+
)
18+
19+
func (rs *RestoreService) recoverableUpload(userID string, bearerToken string, conflictOption string, filePath string, fileInfo fileutil.FileInfo, sendMsg func(text string), locText func(text string) string, username string) ([]map[string]interface{}, error) {
20+
//1. Get recoverable upload session for the current file path 获取当前文件路径的可压缩上载会话
21+
uploadSessionData, err := rs.getUploadSession(userID, bearerToken, conflictOption, filePath)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
//2. Get the upload url returned as a response from the recoverable upload session above. 从上面的可压缩上载会话获取作为响应返回的上载url。
27+
uploadURL := uploadSessionData[uploadURLKey].(string)
28+
29+
//3. Get the startOffset list for the file 获取文件的startOffset列表
30+
startOffsetLst, err := fileutil.GetFileOffsetStash(filePath)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
//4. Loop over the file start offset list to read files in chunk and upload in onedrive 在文件开始偏移量列表上循环以读取块中的文件并在onedrive中上载
36+
var uploadResp []map[string]interface{}
37+
lastChunkIndex := len(startOffsetLst) - 1
38+
var isLastChunk bool
39+
timeUnix := time.Now().UnixNano()
40+
var buffer = make([]byte, fileutil.GetDefaultChunkSize())
41+
startTime := time.Now().Unix()
42+
for i, sOffset := range startOffsetLst {
43+
if i == lastChunkIndex {
44+
lastChunkSize, err := fileutil.GetLatsChunkSizeInBytes(filePath)
45+
if err != nil {
46+
log.Panic(err)
47+
}
48+
buffer = make([]byte, lastChunkSize)
49+
isLastChunk = true
50+
}
51+
filePartInBytes := &buffer
52+
//4a. Get the bytes for the file based on the offset 根据偏移量获取文件的字节数
53+
err := fileutil.GetFilePartInBytes(filePartInBytes, filePath, sOffset)
54+
if err != nil {
55+
return nil, err
56+
}
57+
if i != 0 {
58+
sendMsg(fmt.Sprintf("正在向OneDrive账户 `%s` 上传 `%s` *『%d/%d』* 速度:`%s/s` 已耗时: `%d s`", username, filePath, i, len(startOffsetLst), byte2Readable(float64(fileutil.GetDefaultChunkSize())/float64(time.Now().UnixNano()-timeUnix)*float64(1000000000)), time.Now().Unix()-startTime))
59+
} else {
60+
sendMsg(fmt.Sprintf("正在向OneDrive账户 `%s` 上传 `%s` *『%d/%d』* 速度:`----` 已耗时: `%d s`", username, filePath, i, len(startOffsetLst), time.Now().Unix()-startTime))
61+
}
62+
63+
timeUnix = time.Now().UnixNano()
64+
//3b. make a call to the upload url with the file part based on the offset. 使用基于偏移量的文件部分调用上载url。
65+
resp, err := rs.uploadFilePart(uploadURL, filePath, bearerToken, *filePartInBytes, sOffset, isLastChunk)
66+
67+
if err != nil {
68+
return nil, err
69+
}
70+
respMap := make(map[string]interface{})
71+
err = json.NewDecoder(resp.Body).Decode(&respMap)
72+
if err != nil {
73+
fmt.Println(err)
74+
}
75+
if resp.Body != nil {
76+
defer resp.Body.Close()
77+
}
78+
//fmt.Printf("%+v, status code: %s", respMap, resp.Status)
79+
uploadResp = append(uploadResp, respMap)
80+
debug.FreeOSMemory()
81+
}
82+
sendMsg("close")
83+
return uploadResp, nil
84+
}
85+
86+
//Returns the restore session url for part file upload
87+
func (rs *RestoreService) getUploadSession(userID string, bearerToken string, conflictOption string, filePath string) (map[string]interface{}, error) {
88+
uploadSessionPath := fmt.Sprintf(uploadSessionPath, userID, filePath)
89+
uploadSessionData := make(map[string]interface{})
90+
//Get the body for resemble upload session call.
91+
body, err := getRessumableSessionBody(filePath, conflictOption)
92+
if err != nil {
93+
return nil, err
94+
}
95+
96+
//Create request instance
97+
req, err := rs.NewRequest("POST", uploadSessionPath, getRessumableUploadSessionHeader(bearerToken), body)
98+
if err != nil {
99+
return nil, err
100+
}
101+
//Execute the request
102+
resp, err := rs.Do(req)
103+
if err != nil {
104+
//Need to return a generic object from onedrive upload instead of response directly
105+
return nil, err
106+
}
107+
108+
//convert http.Response to map
109+
err = json.NewDecoder(resp.Body).Decode(&uploadSessionData)
110+
if err != nil {
111+
return nil, err
112+
}
113+
return uploadSessionData, nil
114+
}
115+
116+
//Uploads the file part to Onedrive
117+
func (rs *RestoreService) uploadFilePart(uploadURL string, filePath string, bearerToken string, filePart []byte, startOffset int64, isLastPart bool) (*http.Response, error) {
118+
//This is required for Content-Range header key
119+
fileSizeInBytes, err := fileutil.GetFileSize(filePath)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
//Fetch Last chunklength -- will be needed in Content_length header
125+
lastChunkLength, err := fileutil.GetLatsChunkSizeInBytes(filePath)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
//Create upload part file request
131+
req, err := rs.NewRequest("PUT", uploadURL, getRessumableUploadHeader(fileSizeInBytes, bearerToken, startOffset, isLastPart, lastChunkLength), filePart)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
//Execute the request
137+
resp, err := rs.Do(req)
138+
if err != nil {
139+
//Need to return a generic object from onedrive upload instead of response directly
140+
return nil, err
141+
}
142+
return resp, nil
143+
}
144+
145+
//Returns header for upload session API
146+
func getRessumableUploadSessionHeader(accessToken string) map[string]string {
147+
//As a work around for now, ultimately this will be recived as a part of restore xml
148+
bearerToken := fmt.Sprintf("bearer %s", accessToken)
149+
return map[string]string{
150+
"Content-Type": "application/json",
151+
"Authorization": bearerToken,
152+
}
153+
}
154+
155+
//Returns headers for recoverable actual upload as file parts
156+
func getRessumableUploadHeader(fileSizeInBytes int64, accessToken string, startOffset int64, isLastChunk bool, lastChunkSize int64) map[string]string {
157+
var cRange string
158+
var cLength string
159+
160+
if isLastChunk {
161+
cRange = fmt.Sprintf("bytes %d-%d/%d", startOffset, fileSizeInBytes-2, fileSizeInBytes-1)
162+
cLength = fmt.Sprintf("%d", lastChunkSize)
163+
} else {
164+
cRange = fmt.Sprintf("bytes %d-%d/%d", startOffset, startOffset+fileutil.GetDefaultChunkSize()-1, fileSizeInBytes-1)
165+
cLength = fmt.Sprintf("%d", fileutil.GetDefaultChunkSize())
166+
}
167+
168+
// fmt.Printf("\nCLength: %s , cRange: %s\n", cLength, cRange)
169+
bearerToken := fmt.Sprintf("bearer %s", accessToken)
170+
return map[string]string{
171+
"Content-Length": cLength,
172+
"Content-Range": cRange,
173+
"Authorization": bearerToken,
174+
}
175+
}
176+
177+
//Returns the expected body for creating file upload session to onedrive
178+
func getRessumableSessionBody(filePath string, conflictOption string) (string, error) {
179+
bodyMap := map[string]string{"@microsoft.graph.conflictBehavior": conflictOption, "description": "", "name": filePath}
180+
jsonBody, err := json.Marshal(bodyMap)
181+
return string(jsonBody), err
182+
}
183+
184+
func byte2Readable(bytes float64) string {
185+
const kb float64 = 1024
186+
const mb float64 = kb * 1024
187+
const gb float64 = mb * 1024
188+
var readable float64
189+
var unit string
190+
_bytes := bytes
191+
192+
if _bytes >= gb {
193+
// xx GB
194+
readable = _bytes / gb
195+
unit = "GB"
196+
} else if _bytes < gb && _bytes >= mb {
197+
// xx MB
198+
readable = _bytes / mb
199+
unit = "MB"
200+
} else {
201+
// xx KB
202+
readable = _bytes / kb
203+
unit = "KB"
204+
}
205+
return strconv.FormatFloat(readable, 'f', 2, 64) + " " + unit
206+
}

0 commit comments

Comments
 (0)