Compare commits

..

47 Commits

Author SHA1 Message Date
a295e7024a 🎨 change link interface 2021-12-09 19:24:34 +08:00
b36eaf08f0 🎨 abstract cache method 2021-12-08 22:58:44 +08:00
bb6e520ab5 🎨 delete useless code 2021-12-08 22:44:45 +08:00
9b64e2e045 🐛 fix cache bug 2021-12-08 22:41:30 +08:00
ee7c12c30f cf workers proxy 2021-12-08 21:40:20 +08:00
96d6d58910 🐛 fix alist driver cache 2021-12-08 21:39:35 +08:00
2bf235a5ac 🐛 fix link req nil 2021-12-08 20:27:39 +08:00
236f9969c0 🚧 support proxy 2021-12-08 20:00:52 +08:00
09e63027d9 🎇 allow delete deprecated setting 2021-12-08 19:36:07 +08:00
a15dae291e support webm video preview 2021-12-08 14:43:38 +08:00
efaaeedfb8 🐛 fix can't get index.html 2021-12-08 10:35:18 +08:00
190c8001a5 🚧 support proxy url 2021-12-08 10:33:26 +08:00
b8698700ef support add another alist 2021-12-08 09:10:00 +08:00
985b81826f 🎇 support add another alist 2021-12-07 20:16:34 +08:00
74d8fa3919 🔥 remove useless remark 2021-12-07 16:34:20 +08:00
43e4928bb9 🐛 fix aliyundrive webdav delete and upload 2021-12-07 16:32:59 +08:00
03580fd76c 🎨 Improve the code structure 2021-12-07 15:56:43 +08:00
6e8d551420 🚧 aliyundrive webdav write 2021-12-06 22:14:32 +08:00
28998d6f8c 🚧 aliyundrive webdav write 2021-12-06 17:49:20 +08:00
1779617cb9 🎨 Improve the code structure 2021-12-06 15:55:05 +08:00
7dfe48339c 🚧 native webdav write 2021-12-05 16:09:39 +08:00
9c5627a382 🚧 webdav write interface 2021-12-05 15:22:19 +08:00
809850321a 🔧 change customize settings 2021-12-05 13:43:08 +08:00
bdc1f68746 📝 update readme 2021-12-04 18:37:26 +08:00
9aaef6c3a3 📝 update readme image 2021-12-03 11:24:43 +08:00
bb50c52d0e 📝 update readme 2021-12-02 23:01:54 +08:00
6041e5a0fa 🎇 Add ISSUE_TEMPLATE 2021-12-02 22:55:01 +08:00
308a86c36e 📝 update readme 2021-12-02 22:38:18 +08:00
ba7c4fc230 🎇 Create LICENSE 2021-12-02 22:37:56 +08:00
d81ec0637d 🎇 md5 and filename 2021-12-02 18:57:29 +08:00
1d7d37e642 🐛 fix onedrive sort 2021-12-01 11:25:33 +08:00
b62a716267 remove onedrive empty cache 2021-12-01 11:25:04 +08:00
944941db10 🎇 support https 2021-12-01 00:19:06 +08:00
bd91acc5d0 🐛 fix lanzou webdav time error 2021-12-01 00:15:34 +08:00
cd50227835 🎇 support lanzou 2021-11-30 21:30:50 +08:00
50226f66e3 google default root folder 2021-11-30 19:10:27 +08:00
9dcaa9b07a 🎨 add types 2021-11-30 18:53:37 +08:00
fa6c0f78bc 🐛 fix windows sharepoint path error 2021-11-30 16:45:08 +08:00
7f35dc6ade add onedrive thumbnail 2021-11-30 16:27:23 +08:00
5d6463b75a 🎨 move code 2021-11-30 10:00:06 +08:00
733b38b435 🎨 improve code structure 2021-11-30 09:37:51 +08:00
50a02a7af7 🔥 remove useless code 2021-11-29 21:30:52 +08:00
71b1517de7 🐛 fix windows check parent password 2021-11-29 21:09:16 +08:00
ffdd88ec66 webdav proxy 2021-11-29 16:42:46 +08:00
4ff2756572 🐛 fix 123pan webdav can't get 2021-11-29 16:06:49 +08:00
d955038ebc delete useless path unescape 2021-11-29 16:05:58 +08:00
72d5e4e691 delete useless log 2021-11-29 16:05:14 +08:00
61 changed files with 2714 additions and 814 deletions

39
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: "Bug report"
description: Bug report
labels: [pending triage]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: input
id: version
attributes:
label: Alist Version / Alist 版本
description: What version of our software are you running?
placeholder: v2.0.0
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Describe the bug / 问题描述
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction / 复现链接
description: |
Please provide a link to a repo that can reproduce the problem you ran into.
请提供能复现此问题的链接
validations:
required: false
- type: textarea
id: logs
attributes:
label: 日志 / Logs
description: |
Please copy and paste any relevant log output.
请复制粘贴错误日志,或者截图
render: shell

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Discussions & Feature request
url: https://github.com/Xhofe/alist/discussions
about: Use GitHub discussions for message-board style questions and discussions or feature request.

2
.gitignore vendored
View File

@ -21,7 +21,7 @@ dist/
# Dependency directories (remove the comment below to include it)
# vendor/
bin/*
alist
/alist
*.json
public/index.html
public/assets/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Xhofe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

67
README.md Normal file → Executable file
View File

@ -1,46 +1,61 @@
<h2 align="center">Alist</h2>
<p align="center">
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="Release version"></a>
<div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3>
<p><em>A file list program that supports multiple storage, powered by gin and React.</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square" alt="Downloads"></a>
<a href="https://github.com/Xhofe/alist/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
<a href="https://pay.xhofe.top">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
</a>
</p>
</div>
---
### 这是什么?
English | [中文](./README_cn.md)
一款支持多种存储的目录文件列表程序,后端基于`gin`,前端使用`react`
## Features
### 前端项目地址
- [x] multiple storage
- [x] Local storage
- [x] [aliyundrive](https://www.aliyundrive.com/)
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us
- [x] [189cloud](https://cloud.189.cn)
- [x] [GoogleDrive](https://drive.google.com/)
- [x] [123pan](https://www.123pan.com/)
- [x] [lanzou](https://pc.woozooo.com/)
- [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode
- [x] Video and audio preview (mp4, mp3, ...)
- [x] Office documents preview (docx, pptx, xlsx, ...)
- [x] `README.md` preview rendering
- [x] File permalink copy and direct file download
- [x] Dark mode
- [x] I18n
- [x] Protected routes (password protection and authentication)
- [x] WebDav (readonly)
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
- https://github.com/Xhofe/alist-web
## Discussion
### 演示地址
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports only.**
- https://alist.nn.ci
## Demo
### 预览
Available at: <https://alist.nn.ci>.
<a href="https://alist.nn.ci/"><img src="https://store.heytapimage.com/cdo-portal/feedback/202111/03/695ef77854a144e928518efde38db97a.png"></a>
![demo](https://inews.gtimg.com/newsapp_ls/0/14256614096/0)
### 支持的存储
## Document
- 本地存储
- 阿里云盘
- Onedrive/世纪互联
- 天翼云盘
- GoogleDrive
- 123pan
- ...
<https://alist-doc.nn.ci/en/>
### 如何使用
## License
- https://alist-doc.nn.ci/
The `AList` is open-source software licensed under the MIT license.
### License
---
The `AList` is open-source software licensed under the MIT license.
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)

60
README_cn.md Normal file
View File

@ -0,0 +1,60 @@
<div align="center">
<h3><a href="https://alist.nn.ci">Alist</a></h3>
<p><em>一个支持多存储的文件列表程序,使用 gin 和 React 。</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
<a href="https://pay.xhofe.top">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
</a>
</div>
---
[English](./README.md) | 中文
## 支持
- [x] 多种存储
- [x] 本地存储
- [x] [阿里云盘](https://www.aliyundrive.com/)
- [x] OneDrive / Sharepoint[国际版](https://www.office.com/), [世纪互联](https://portal.partner.microsoftonline.cn),de,us
- [x] [天翼云盘](https://cloud.189.cn)
- [x] [GoogleDrive](https://drive.google.com/)
- [x] [123云盘](https://www.123pan.com/)
- [x] [蓝奏云](https://pc.woozooo.com/)
- [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览
- [x] 视频和音频预览mp4、mp3 等)
- [x] Office 文档预览docx、pptx、xlsx、...
- [x] `README.md` 预览渲染
- [x] 文件永久链接复制和直接文件下载
- [x] 黑暗模式
- [x] 国际化
- [x] 受保护的路由(密码保护和身份验证)
- [x] WebDav只读
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告。**
## 演示
<https://alist.nn.ci>
![演示](https://inews.gtimg.com/newsapp_ls/0/14256614096/0)
## 文档
<https://alist-doc.nn.ci/>
## 许可
`AList` 是在 MIT 许可下许可的开源软件。
---
> [@Blog](https://www.nn.ci/) · [@GitHub](https://github.com/Xhofe)

125
alist-proxy.js Normal file
View File

@ -0,0 +1,125 @@
const HOST = "YOUR_HOST";
const TOKEN = "YOUR_TOKEN";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
"Access-Control-Max-Age": "86400",
};
!function(a){"use strict";function b(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c}function c(a,b){return a<<b|a>>>32-b}function d(a,d,e,f,g,h){return b(c(b(b(d,a),b(f,h)),g),e)}function e(a,b,c,e,f,g,h){return d(b&c|~b&e,a,b,f,g,h)}function f(a,b,c,e,f,g,h){return d(b&e|c&~e,a,b,f,g,h)}function g(a,b,c,e,f,g,h){return d(b^c^e,a,b,f,g,h)}function h(a,b,c,e,f,g,h){return d(c^(b|~e),a,b,f,g,h)}function i(a,c){a[c>>5]|=128<<c%32,a[(c+64>>>9<<4)+14]=c;var d,i,j,k,l,m=1732584193,n=-271733879,o=-1732584194,p=271733878;for(d=0;d<a.length;d+=16)i=m,j=n,k=o,l=p,m=e(m,n,o,p,a[d],7,-680876936),p=e(p,m,n,o,a[d+1],12,-389564586),o=e(o,p,m,n,a[d+2],17,606105819),n=e(n,o,p,m,a[d+3],22,-1044525330),m=e(m,n,o,p,a[d+4],7,-176418897),p=e(p,m,n,o,a[d+5],12,1200080426),o=e(o,p,m,n,a[d+6],17,-1473231341),n=e(n,o,p,m,a[d+7],22,-45705983),m=e(m,n,o,p,a[d+8],7,1770035416),p=e(p,m,n,o,a[d+9],12,-1958414417),o=e(o,p,m,n,a[d+10],17,-42063),n=e(n,o,p,m,a[d+11],22,-1990404162),m=e(m,n,o,p,a[d+12],7,1804603682),p=e(p,m,n,o,a[d+13],12,-40341101),o=e(o,p,m,n,a[d+14],17,-1502002290),n=e(n,o,p,m,a[d+15],22,1236535329),m=f(m,n,o,p,a[d+1],5,-165796510),p=f(p,m,n,o,a[d+6],9,-1069501632),o=f(o,p,m,n,a[d+11],14,643717713),n=f(n,o,p,m,a[d],20,-373897302),m=f(m,n,o,p,a[d+5],5,-701558691),p=f(p,m,n,o,a[d+10],9,38016083),o=f(o,p,m,n,a[d+15],14,-660478335),n=f(n,o,p,m,a[d+4],20,-405537848),m=f(m,n,o,p,a[d+9],5,568446438),p=f(p,m,n,o,a[d+14],9,-1019803690),o=f(o,p,m,n,a[d+3],14,-187363961),n=f(n,o,p,m,a[d+8],20,1163531501),m=f(m,n,o,p,a[d+13],5,-1444681467),p=f(p,m,n,o,a[d+2],9,-51403784),o=f(o,p,m,n,a[d+7],14,1735328473),n=f(n,o,p,m,a[d+12],20,-1926607734),m=g(m,n,o,p,a[d+5],4,-378558),p=g(p,m,n,o,a[d+8],11,-2022574463),o=g(o,p,m,n,a[d+11],16,1839030562),n=g(n,o,p,m,a[d+14],23,-35309556),m=g(m,n,o,p,a[d+1],4,-1530992060),p=g(p,m,n,o,a[d+4],11,1272893353),o=g(o,p,m,n,a[d+7],16,-155497632),n=g(n,o,p,m,a[d+10],23,-1094730640),m=g(m,n,o,p,a[d+13],4,681279174),p=g(p,m,n,o,a[d],11,-358537222),o=g(o,p,m,n,a[d+3],16,-722521979),n=g(n,o,p,m,a[d+6],23,76029189),m=g(m,n,o,p,a[d+9],4,-640364487),p=g(p,m,n,o,a[d+12],11,-421815835),o=g(o,p,m,n,a[d+15],16,530742520),n=g(n,o,p,m,a[d+2],23,-995338651),m=h(m,n,o,p,a[d],6,-198630844),p=h(p,m,n,o,a[d+7],10,1126891415),o=h(o,p,m,n,a[d+14],15,-1416354905),n=h(n,o,p,m,a[d+5],21,-57434055),m=h(m,n,o,p,a[d+12],6,1700485571),p=h(p,m,n,o,a[d+3],10,-1894986606),o=h(o,p,m,n,a[d+10],15,-1051523),n=h(n,o,p,m,a[d+1],21,-2054922799),m=h(m,n,o,p,a[d+8],6,1873313359),p=h(p,m,n,o,a[d+15],10,-30611744),o=h(o,p,m,n,a[d+6],15,-1560198380),n=h(n,o,p,m,a[d+13],21,1309151649),m=h(m,n,o,p,a[d+4],6,-145523070),p=h(p,m,n,o,a[d+11],10,-1120210379),o=h(o,p,m,n,a[d+2],15,718787259),n=h(n,o,p,m,a[d+9],21,-343485551),m=b(m,i),n=b(n,j),o=b(o,k),p=b(p,l);return[m,n,o,p]}function j(a){var b,c="";for(b=0;b<32*a.length;b+=8)c+=String.fromCharCode(a[b>>5]>>>b%32&255);return c}function k(a){var b,c=[];for(c[(a.length>>2)-1]=void 0,b=0;b<c.length;b+=1)c[b]=0;for(b=0;b<8*a.length;b+=8)c[b>>5]|=(255&a.charCodeAt(b/8))<<b%32;return c}function l(a){return j(i(k(a),8*a.length))}function m(a,b){var c,d,e=k(a),f=[],g=[];for(f[15]=g[15]=void 0,e.length>16&&(e=i(e,8*a.length)),c=0;16>c;c+=1)f[c]=909522486^e[c],g[c]=1549556828^e[c];return d=i(f.concat(k(b)),512+8*b.length),j(i(g.concat(d),640))}function n(a){var b,c,d="0123456789abcdef",e="";for(c=0;c<a.length;c+=1)b=a.charCodeAt(c),e+=d.charAt(b>>>4&15)+d.charAt(15&b);return e}function o(a){return unescape(encodeURIComponent(a))}function p(a){return l(o(a))}function q(a){return n(p(a))}function r(a,b){return m(o(a),o(b))}function s(a,b){return n(r(a,b))}function t(a,b,c){return b?c?r(b,a):s(b,a):c?p(a):q(a)}"function"==typeof define&&define.amd?define(function(){return t}):a.md5=t}(this);
async function handleRequest(request) {
const origin = request.headers.get("origin");
const url = new URL(request.url);
const path = url.pathname;
const sign = url.searchParams.get("sign");
const name = path.split("/").pop();
const right = md5(`alist-${TOKEN}-${name}`).slice(8, 24);
if (sign !== right){
const resp = new Response(
JSON.stringify({
code: 401,
message: `sign mismatch`,
}),
{
headers: {
"content-type": "application/json;charset=UTF-8",
},
}
);
resp.headers.set("Access-Control-Allow-Origin", origin);
return resp;
}
let resp = await fetch(`${HOST}/api/admin/link`, {
method: "POST",
headers: {
"content-type": "application/json;charset=UTF-8",
Authorization: TOKEN,
},
body: JSON.stringify({
path: decodeURI(path),
}),
});
let res = await resp.json();
if (res.code !== 200) {
return new Response(JSON.stringify(res));
}
request = new Request(res.data.url, request);
if (res.data.headers) {
for(const header of res.data.headers){
request.headers.set(header.name, header.value);
}
}
let response = await fetch(request);
// Recreate the response so we can modify the headers
response = new Response(response.body, response);
// Set CORS headers
response.headers.set("Access-Control-Allow-Origin", origin);
// Append to/Add Vary header so browser will cache response correctly
response.headers.append("Vary", "Origin");
return response;
}
function handleOptions(request) {
// Make sure the necessary headers are present
// for this to be a valid pre-flight request
let headers = request.headers;
if (
headers.get("Origin") !== null &&
headers.get("Access-Control-Request-Method") !== null
// && headers.get("Access-Control-Request-Headers") !== null
) {
// Handle CORS pre-flight request.
// If you want to check or reject the requested method + headers
// you can do that here.
let respHeaders = {
...corsHeaders,
// Allow all future content Request headers to go back to browser
// such as Authorization (Bearer) or X-Client-Name-Version
"Access-Control-Allow-Headers": request.headers.get(
"Access-Control-Request-Headers"
),
};
return new Response(null, {
headers: respHeaders,
});
} else {
// Handle standard OPTIONS request.
// If you want to allow other HTTP Methods, you can do that here.
return new Response(null, {
headers: {
Allow: "GET, HEAD, POST, OPTIONS",
},
});
}
}
addEventListener("fetch", (event) => {
const request = event.request;
// const url = new URL(request.url)
if (request.method === "OPTIONS") {
// Handle CORS preflight requests
event.respondWith(handleOptions(request));
} else if (
request.method === "GET" ||
request.method === "HEAD" ||
request.method === "POST"
) {
// Handle requests to the API server
event.respondWith(handleRequest(request));
} else {
event.respondWith(
new Response(null, {
status: 405,
statusText: "Method Not Allowed",
})
);
}
});

View File

@ -1,26 +1,18 @@
package main
import (
"flag"
"fmt"
"github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf"
_ "github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func init() {
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
flag.BoolVar(&conf.Version, "version", false, "print version info")
flag.BoolVar(&conf.Password, "password", false, "print current password")
flag.Parse()
}
func Init() bool {
bootstrap.InitLog()
//bootstrap.InitLog()
bootstrap.InitConf()
bootstrap.InitCron()
bootstrap.InitModel()
@ -54,7 +46,12 @@ func main() {
server.InitApiRouter(r)
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
log.Infof("start server @ %s", base)
err := r.Run(base)
var err error
if conf.Conf.Https {
err = r.RunTLS(base, conf.Conf.CertFile, conf.Conf.KeyFile)
} else {
err = r.Run(base)
}
if err != nil {
log.Errorf("failed to start: %s", err.Error())
}

View File

@ -2,7 +2,7 @@ package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
)
@ -15,7 +15,7 @@ func InitAccounts() {
}
for i, account := range accounts {
model.RegisterAccount(account)
driver, ok := drivers.GetDriver(account.Type)
driver, ok := base.GetDriver(account.Type)
if !ok {
log.Errorf("no [%s] driver", account.Type)
} else {

View File

@ -1,10 +0,0 @@
package bootstrap
import (
_ "github.com/Xhofe/alist/drivers/123pan"
_ "github.com/Xhofe/alist/drivers/189cloud"
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/googledrive"
_ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive"
)

View File

@ -1,11 +1,12 @@
package bootstrap
import (
"flag"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)
// initLog init log
// InitLog init log
func InitLog() {
if conf.Debug {
log.SetLevel(log.DebugLevel)
@ -18,4 +19,14 @@ func InitLog() {
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true,
})
log.Infof("init log...")
}
func init() {
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
flag.BoolVar(&conf.Version, "version", false, "print version info")
flag.BoolVar(&conf.Password, "password", false, "print current password")
flag.Parse()
InitLog()
}

View File

@ -15,9 +15,13 @@ func InitSettings() {
Description: "version",
Type: "string",
Group: model.CONST,
Version: conf.GitTag,
}
_ = model.SaveSetting(version)
err := model.SaveSetting(version)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
settings := []model.SettingItem{
{
@ -36,14 +40,14 @@ func InitSettings() {
},
{
Key: "logo",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
Description: "logo",
Type: "string",
Group: model.PUBLIC,
},
{
Key: "favicon",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202110/30/d43c41c5d257c9bc36366e310374fb19.png",
Value: "https://store.heytapimage.com/cdo-portal/feedback/202112/05/1542f45f86b8609495b69c5380753135.png",
Description: "favicon",
Type: "string",
Group: model.PUBLIC,
@ -114,17 +118,34 @@ func InitSettings() {
Group: model.PRIVATE,
},
{
Key: "customize style",
Value: "",
Key: "customize head",
Value: `<style>
.chakra-ui-light{
background-image: linear-gradient(120deg,#e0c3fc 0%,#8ec5fc 100%) !important;
background-attachment: fixed;
}
.main-box {
border-radius: 15px !important;
}
.chakra-ui-light .main-box {
background-color: white !important;
}
.chakra-ui-light .readme-box {
background-color: white !important;
}
.readme-box {
border-radius: 15px !important;
}
</style>`,
Type: "text",
Description: "customize style, don't need add <style></style>",
Description: "Customize head, placed at the beginning of the head",
Group: model.PRIVATE,
},
{
Key: "customize script",
Key: "customize body",
Value: "",
Type: "text",
Description: "customize script, don't need add <script></script>",
Description: "Customize script, placed at the end of the body",
Group: model.PRIVATE,
},
{
@ -156,10 +177,22 @@ func InitSettings() {
Group: model.PRIVATE,
},
}
for _, v := range settings {
_, err := model.GetSettingByKey(v.Key)
if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v)
for i, _ := range settings {
v := settings[i]
v.Version = conf.GitTag
o, err := model.GetSettingByKey(v.Key)
if err != nil {
if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
} else {
log.Fatal("can't get setting: %s", err.Error())
}
} else {
o.Version = conf.GitTag
err = model.SaveSetting(*o)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}

View File

@ -14,6 +14,9 @@ type Config struct {
Address string `json:"address"`
Port int `json:"port"`
Database Database `json:"database"`
Https bool `json:"https"`
CertFile string `json:"cert_file"`
KeyFile string `json:"key_file"`
}
func DefaultConfig() *Config {
@ -22,11 +25,7 @@ func DefaultConfig() *Config {
Port: 5244,
Database: Database{
Type: "sqlite3",
User: "",
Password: "",
Host: "",
Port: 0,
Name: "",
TablePrefix: "x_",
DBFile: "data/data.db",
},

View File

@ -12,7 +12,7 @@ var (
GoVersion string
GitAuthor string
GitCommit string
GitTag string
GitTag string = "dev"
)
var (
@ -31,7 +31,7 @@ var (
var (
TextTypes = []string{"txt", "go", "md"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg"}
)
@ -41,11 +41,9 @@ var (
RawIndexHtml string
IndexHtml string
CheckParent bool
//CustomizeStyle string
//CustomizeScript string
//Favicon string
CheckDown bool
CheckDown bool
Token string
DavUsername string
DavPassword string
)

View File

@ -1,13 +1,12 @@
package _23pan
package _23
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
"time"
@ -54,7 +53,7 @@ func (driver Pan123) Login(account *model.Account) error {
var resp Pan123TokenResp
_, err := pan123Client.R().
SetResult(&resp).
SetBody(drivers.Json{
SetBody(base.Json{
"passport": account.Username,
"password": account.Password,
}).Post("https://www.123pan.com/api/user/sign_in")
@ -77,7 +76,7 @@ func (driver Pan123) FormatFile(file *Pan123File) *model.File {
Id: strconv.FormatInt(file.FileId, 10),
Name: file.FileName,
Size: file.Size,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: file.UpdateAt,
}
if file.Type == 1 {
@ -107,7 +106,6 @@ func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123
if err != nil {
return nil, err
}
log.Debugf("%+v", resp)
if resp.Code != 0 {
if resp.Code == 401 {
err := driver.Login(account)
@ -131,21 +129,21 @@ func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File,
if err != nil {
return nil, err
}
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]Pan123File)
for _, file := range parentFiles {
if file.FileName == name {
if file.Type != conf.FOLDER {
return &file, err
} else {
return nil, drivers.NotFile
return nil, base.ErrNotFile
}
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func init() {
drivers.RegisterDriver(driverName, &Pan123{})
base.RegisterDriver(&Pan123{})
pan123Client.SetRetryCount(3)
}

View File

@ -1,60 +1,59 @@
package _23pan
package _23
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
url "net/url"
"path/filepath"
)
type Pan123 struct {}
type Pan123 struct{}
var driverName = "123Pan"
func (driver Pan123) Config() base.DriverConfig {
return base.DriverConfig{
Name: "123Pan",
OnlyProxy: false,
}
}
func (driver Pan123) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
func (driver Pan123) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: "string",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: "string",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt",
Required: true,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Type: base.TypeSelect,
Values: "asc,desc",
Required: true,
},
@ -77,7 +76,7 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
@ -91,13 +90,13 @@ func (driver Pan123) File(path string, account *model.Account) (*model.File, err
return &file, nil
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Pan123File
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]Pan123File)
} else {
@ -110,7 +109,7 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
@ -120,14 +119,14 @@ func (driver Pan123) Files(path string, account *model.Account) ([]model.File, e
return files, nil
}
func (driver Pan123) Link(path string, account *model.Account) (string, error) {
func (driver Pan123) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.GetFile(utils.ParsePath(path), account)
if err != nil {
return "", err
return nil, err
}
var resp Pan123DownResp
_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
SetBody(drivers.Json{
SetBody(base.Json{
"driveId": 0,
"etag": file.Etag,
"fileId": file.FileId,
@ -137,19 +136,35 @@ func (driver Pan123) Link(path string, account *model.Account) (string, error) {
"type": file.Type,
}).Post("https://www.123pan.com/api/file/download_info")
if err != nil {
return "", err
return nil, err
}
if resp.Code != 0 {
if resp.Code == 401 {
err := driver.Login(account)
if err != nil {
return "", err
return nil, err
}
return driver.Link(path, account)
}
return "", fmt.Errorf(resp.Message)
return nil, fmt.Errorf(resp.Message)
}
return resp.Data.DownloadUrl, nil
u, err := url.Parse(resp.Data.DownloadUrl)
if err != nil {
return nil, err
}
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
if err != nil {
return nil, err
}
log.Debug(res.String())
link := base.Link{}
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
}else {
link.Url = resp.Data.DownloadUrl
}
return &link, nil
}
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -159,8 +174,12 @@ func (driver Pan123) Path(path string, account *model.Account) (*model.File, []m
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
if !file.IsDir() {
link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil
}
files, err := driver.Files(path, account)
@ -175,7 +194,27 @@ func (driver Pan123) Proxy(c *gin.Context, account *model.Account) {
}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
return nil, base.ErrNotSupport
}
var _ drivers.Driver = (*Pan123)(nil)
func (driver Pan123) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Pan123) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Pan123) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Pan123) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Pan123)(nil)

View File

@ -1,4 +1,4 @@
package _89cloud
package _89
import (
"crypto/rand"
@ -9,7 +9,7 @@ import (
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
@ -30,7 +30,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
Id: strconv.FormatInt(file.Id, 10),
Name: file.Name,
Size: file.Size,
Driver: "189Cloud",
Driver: driver.Config().Name,
UpdatedAt: nil,
Thumbnail: file.Icon.SmallUrl,
Url: file.Url,
@ -52,7 +52,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
// dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir)
// _, _, err := c.Path(dir, account)
// _, _, err := c.ParentPath(dir, account)
// if err != nil {
// return nil, err
// }
@ -63,11 +63,11 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
// if file.Size != -1 {
// return &file, err
// } else {
// return nil, NotFile
// return nil, ErrNotFile
// }
// }
// }
// return nil, PathNotFound
// return nil, ErrPathNotFound
//}
type Cloud189Down struct {
@ -313,6 +313,6 @@ func b64tohex(a string) string {
}
func init() {
drivers.RegisterDriver(driverName, &Cloud189{})
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client, 0)
}

View File

@ -1,9 +1,9 @@
package _89cloud
package _89
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
@ -13,48 +13,46 @@ import (
type Cloud189 struct {}
var driverName = "189Cloud"
func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{
Name: "189Cloud",
OnlyProxy: false,
}
}
func (driver Cloud189) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
func (driver Cloud189) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: "string",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: "string",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Type: base.TypeSelect,
Values: "name,size,lastOpTime,createdDate",
Required: true,
},
{
Name: "order_direction",
Label: "desc",
Type: "select",
Type: base.TypeSelect,
Values: "true,false",
Required: true,
},
@ -86,7 +84,7 @@ func (driver Cloud189) File(path string, account *model.Account) (*model.File, e
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
@ -100,13 +98,13 @@ func (driver Cloud189) File(path string, account *model.Account) (*model.File, e
return &file, nil
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Cloud189File
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]Cloud189File)
} else {
@ -119,7 +117,7 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
@ -129,17 +127,17 @@ func (driver Cloud189) Files(path string, account *model.Account) ([]model.File,
return files, nil
}
func (driver Cloud189) Link(path string, account *model.Account) (string, error) {
func (driver Cloud189) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(path), account)
if err != nil {
return "", err
return nil, err
}
if file.Type == conf.FOLDER {
return "", drivers.NotFile
return nil, base.ErrNotFile
}
client, ok := client189Map[account.Name]
if !ok {
return "", fmt.Errorf("can't find [%s] client", account.Name)
return nil, fmt.Errorf("can't find [%s] client", account.Name)
}
var e Cloud189Error
var resp Cloud189Down
@ -150,28 +148,31 @@ func (driver Cloud189) Link(path string, account *model.Account) (string, error)
"fileId": file.Id,
}).Get("https://cloud.189.cn/api/open/file/getFileDownloadUrl.action")
if err != nil {
return "", err
return nil, err
}
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account)
if err != nil {
return "", err
return nil, err
}
return driver.Link(path, account)
}
}
if resp.ResCode != 0 {
return "", fmt.Errorf(resp.ResMessage)
return nil, fmt.Errorf(resp.ResMessage)
}
res, err := drivers.NoRedirectClient.R().Get(resp.FileDownloadUrl)
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
if err != nil {
return "", err
return nil, err
}
link := base.Link{}
if res.StatusCode() == 302 {
return res.Header().Get("location"), nil
link.Url = res.Header().Get("location")
}else {
link.Url = resp.FileDownloadUrl
}
return resp.FileDownloadUrl, nil
return &link, nil
}
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -181,8 +182,12 @@ func (driver Cloud189) Path(path string, account *model.Account) (*model.File, [
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
if !file.IsDir() {
link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil
}
files, err := driver.Files(path, account)
@ -197,7 +202,28 @@ func (driver Cloud189) Proxy(ctx *gin.Context, account *model.Account) {
}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
return nil, base.ErrNotSupport
}
var _ drivers.Driver = (*Cloud189)(nil)
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Cloud189) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Cloud189)(nil)

View File

@ -1,14 +1,16 @@
package alidrive
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
"time"
)
@ -46,7 +48,7 @@ func (driver AliDrive) FormatFile(file *AliFile) *model.File {
Size: file.Size,
UpdatedAt: file.UpdatedAt,
Thumbnail: file.Thumbnail,
Driver: driverName,
Driver: driver.Config().Name,
Url: file.Url,
}
if file.Type == "folder" {
@ -76,7 +78,7 @@ func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFil
SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(drivers.Json{
SetBody(base.Json{
"drive_id": account.DriveId,
"fields": "*",
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
@ -117,7 +119,7 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
if err != nil {
return nil, err
}
parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]AliFile)
for _, file := range parentFiles {
if file.Name == name {
@ -128,16 +130,16 @@ func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, e
}
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver AliDrive) RefreshToken(account *model.Account) error {
url := "https://auth.aliyundrive.com/v2/account/token"
var resp drivers.TokenResp
var resp base.TokenResp
var e AliRespError
_, err := aliClient.R().
//ForceContentType("application/json").
SetBody(drivers.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetResult(&resp).
SetError(&e).
Post(url)
@ -156,11 +158,86 @@ func (driver AliDrive) RefreshToken(account *model.Account) error {
return nil
}
func (driver AliDrive) Rename(fileId, name string, account *model.Account) error {
var resp base.Json
var e AliRespError
_, err := aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "refuse",
"drive_id": account.DriveId,
"file_id": fileId,
"name": name,
}).Post("https://api.aliyundrive.com/v3/file/update")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Rename(fileId, name, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if resp["name"] == name {
return nil
}
return fmt.Errorf("%+v", resp)
}
func (driver AliDrive) Batch(srcId,dstId string, account *model.Account) error {
var e AliRespError
res, err := aliClient.R().SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"requests": []base.Json{
{
"headers": base.Json{
"Content-Type": "application/json",
},
"method":"POST",
"id":srcId,
"body":base.Json{
"drive_id": account.DriveId,
"file_id":srcId,
"to_drive_id":account.DriveId,
"to_parent_file_id":dstId,
},
},
},
"resource": "file",
}).Post("https://api.aliyundrive.com/v3/batch")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Batch(srcId, dstId, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if strings.Contains(res.String(), `"status":200`) {
return nil
}
return errors.New(res.String())
}
func init() {
drivers.RegisterDriver(driverName, &AliDrive{})
base.RegisterDriver(&AliDrive{})
aliClient.
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json").
SetHeader("origin", "https://aliyundrive.com")
SetHeader("origin", "https://www.aliyundrive.com")
}

View File

@ -1,60 +1,63 @@
package alidrive
import (
"bytes"
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"path/filepath"
)
type AliDrive struct{}
var driverName = "AliDrive"
func (driver AliDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "AliDrive",
OnlyProxy: false,
}
}
func (driver AliDrive) Items() []drivers.Item {
return []drivers.Item{
func (driver AliDrive) Items() []base.Item {
return []base.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Values: "name,size,updated_at,created_at",
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,size,updated_at,created_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Values: "ASC,DESC",
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "ASC,DESC",
Required: false,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Type: base.TypeString,
Required: false,
},
{
Name: "limit",
Label: "limit",
Type: "number",
Type: base.TypeNumber,
Required: false,
Description: ">0 and <=200",
},
@ -75,7 +78,7 @@ func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
if err != nil {
return err
}
var resp drivers.Json
var resp base.Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+account.AccessToken).
@ -112,7 +115,7 @@ func (driver AliDrive) File(path string, account *model.Account) (*model.File, e
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
@ -126,13 +129,13 @@ func (driver AliDrive) File(path string, account *model.Account) (*model.File, e
return &file, nil
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []AliFile
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]AliFile)
} else {
@ -145,7 +148,7 @@ func (driver AliDrive) Files(path string, account *model.Account) ([]model.File,
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
@ -155,37 +158,39 @@ func (driver AliDrive) Files(path string, account *model.Account) ([]model.File,
return files, nil
}
func (driver AliDrive) Link(path string, account *model.Account) (string, error) {
func (driver AliDrive) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(path, account)
if err != nil {
return "", err
return nil, err
}
var resp drivers.Json
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(drivers.Json{
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
"expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
if err != nil {
return "", err
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return "", err
return nil, err
} else {
_ = model.SaveAccount(account)
return driver.Link(path, account)
}
}
return "", fmt.Errorf("%s", e.Message)
return nil, fmt.Errorf("%s", e.Message)
}
return resp["url"].(string), nil
return &base.Link{
Url: resp["url"].(string),
}, nil
}
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -195,8 +200,12 @@ func (driver AliDrive) Path(path string, account *model.Account) (*model.File, [
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
file.Url, _ = driver.Link(path, account)
if !file.IsDir() {
link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil
}
files, err := driver.Files(path, account)
@ -217,10 +226,10 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
return nil, err
}
// office
var resp drivers.Json
var resp base.Json
var e AliRespError
var url string
req := drivers.Json{
req := base.Json{
"drive_id": account.DriveId,
"file_id": file.FileId,
}
@ -236,7 +245,7 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
req["category"] = "live_transcoding"
}
default:
return nil, fmt.Errorf("don't support")
return nil, base.ErrNotSupport
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
@ -250,4 +259,221 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{}
return resp, nil
}
var _ drivers.Driver = (*AliDrive)(nil)
func (driver AliDrive) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "refuse",
"drive_id": account.DriveId,
"name": name,
"parent_file_id": parentFile.Id,
"type": "folder",
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.MakeDir(path, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if resp["file_name"] == name {
_ = base.DeleteCache(dir, account)
return nil
}
return fmt.Errorf("%+v", resp)
}
func (driver AliDrive) Move(src string, dst string, account *model.Account) error {
srcDir, _ := filepath.Split(src)
dstDir, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
// rename
if srcDir == dstDir {
err = driver.Rename(srcFile.Id, dstName, account)
} else {
// move
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
err = driver.Batch(srcFile.Id, dstDirFile.Id, account)
}
if err != nil {
_ = base.DeleteCache(srcDir, account)
_ = base.DeleteCache(dstDir, account)
}
return err
}
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver AliDrive) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
var e AliRespError
res, err := aliClient.R().SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
}).Post("https://api.aliyundrive.com/v2/recyclebin/trash")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Delete(path, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if res.StatusCode() == 204 {
_ = base.DeleteCache(utils.Dir(path), account)
return nil
}
return errors.New(res.String())
}
type UploadResp struct {
FileId string `json:"file_id"`
UploadId string `json:"upload_id"`
PartInfoList []struct {
UploadUrl string `json:"upload_url"`
} `json:"part_info_list"`
}
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
const DEFAULT uint64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
var resp UploadResp
var e AliRespError
partInfoList := make([]base.Json, 0)
var i int64
for i = 0; i < count; i++ {
partInfoList = append(partInfoList, base.Json{
"part_number": i + 1,
})
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "auto_rename",
// content_hash
"content_hash_name": "none",
"drive_id": account.DriveId,
"name": file.GetFileName(),
"parent_file_id": parentFile.Id,
"part_info_list": partInfoList,
//proof_code
"proof_version": "v1",
"size": file.GetSize(),
"type": "file",
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
//log.Debugf("%+v\n%+v", resp, e)
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Upload(file, account)
}
}
return fmt.Errorf("%s", e.Message)
}
var byteSize uint64
for i = 0; i < count; i++ {
byteSize = file.GetSize() - finish
if DEFAULT < byteSize {
byteSize = DEFAULT
}
log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData)
//n, err := file.Read(byteData)
//byteData, err := io.ReadAll(file)
//n := len(byteData)
log.Debug(err, n)
if err != nil {
return err
}
finish += uint64(n)
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData))
if err != nil {
return err
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
//res, err := base.BaseClient.R().
// SetHeader("Content-Type","").
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)
//if err != nil {
// return err
//}
//log.Debugf("put to %s : %d,%s", resp.PartInfoList[i].UploadUrl, res.StatusCode(),res.String())
}
var resp2 base.Json
_, err = aliClient.R().SetResult(&resp2).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": resp.FileId,
"upload_id": resp.UploadId,
}).Post("https://api.aliyundrive.com/v2/file/complete")
if e.Code != "" {
//if e.Code == "AccessTokenInvalid" {
// err = driver.RefreshToken(account)
// if err != nil {
// return err
// } else {
// _ = model.SaveAccount(account)
// return driver.Upload(file, account)
// }
//}
return fmt.Errorf("%s", e.Message)
}
if resp2["file_id"] == resp.FileId {
return nil
}
return fmt.Errorf("%+v", resp2)
}
var _ base.Driver = (*AliDrive)(nil)

40
drivers/alist/alist.go Normal file
View File

@ -0,0 +1,40 @@
package alist
import (
"errors"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
)
type BaseResp struct {
Code int `json:"code"`
Message string `json:"message"`
}
type PathResp struct {
BaseResp
Data []model.File `json:"data"`
}
type PreviewResp struct {
BaseResp
Data interface{} `json:"data"`
}
func (driver *Alist) Login(account *model.Account) error {
var resp BaseResp
_, err := base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
Get(account.SiteUrl+"/api/admin/login")
if err != nil {
return err
}
if resp.Code != 200 {
return errors.New(resp.Message)
}
return nil
}
func init() {
base.RegisterDriver(&Alist{})
}

182
drivers/alist/driver.go Normal file
View File

@ -0,0 +1,182 @@
package alist
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"path/filepath"
"strings"
"time"
)
type Alist struct{}
func (driver Alist) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Alist",
OnlyProxy: false,
}
}
func (driver Alist) Items() []base.Item {
return []base.Item{
{
Name: "site_url",
Label: "alist site url",
Type: base.TypeString,
Required: true,
},
{
Name: "access_token",
Label: "token",
Type: base.TypeString,
Description: "admin token",
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
}
}
func (driver Alist) Save(account *model.Account, old *model.Account) error {
account.SiteUrl = strings.TrimRight(account.SiteUrl, "/")
if account.RootFolder == "" {
account.RootFolder = "/"
}
err := driver.Login(account)
if err == nil {
account.Status = "work"
} else {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Alist) File(path string, account *model.Account) (*model.File, error) {
now := time.Now()
if path == "/" {
return &model.File{
Id: "root",
Name: "root",
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: &now,
}, nil
}
_, files, err := driver.Path(utils.Dir(path), account)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrPathNotFound
}
name := utils.Base(path)
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Alist) Files(path string, account *model.Account) ([]model.File, error) {
//return nil, base.ErrNotImplement
_, files, err := driver.Path(utils.Dir(path), account)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func (driver Alist) Link(path string, account *model.Account) (*base.Link, error) {
path = utils.ParsePath(path)
name := utils.Base(path)
flag := "d"
if utils.GetFileType(filepath.Ext(path)) == conf.TEXT {
flag = "p"
}
link := base.Link{}
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
return &link, nil
}
func (driver Alist) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files := cache.([]model.File)
return nil, files, nil
}
var resp PathResp
_, err = base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
SetBody(base.Json{
"path": path,
}).Post(account.SiteUrl + "/api/public/path")
if err != nil {
return nil, nil, err
}
if resp.Code != 200 {
return nil, nil, errors.New(resp.Message)
}
if resp.Message == "file" {
return &resp.Data[0], nil, nil
}
if len(resp.Data) > 0 {
_ = base.SetCache(path, resp.Data, account)
}
return nil, resp.Data, nil
}
func (driver Alist) Proxy(c *gin.Context, account *model.Account) {}
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
var resp PathResp
_, err := base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
SetBody(base.Json{
"path": path,
}).Post(account.SiteUrl + "/api/public/preview")
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
return resp.Data, nil
}
func (driver Alist) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Alist)(nil)

12
drivers/all.go Normal file
View File

@ -0,0 +1,12 @@
package drivers
import (
_ "github.com/Xhofe/alist/drivers/123"
_ "github.com/Xhofe/alist/drivers/189"
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/google"
_ "github.com/Xhofe/alist/drivers/lanzou"
_ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive"
)

25
drivers/base/cache.go Normal file
View File

@ -0,0 +1,25 @@
package base
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
)
func KeyCache(path string, account *model.Account) string {
path = utils.ParsePath(path)
return fmt.Sprintf("%s%s", account.Name, path)
}
func SetCache(path string, obj interface{}, account *model.Account) error {
return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil)
}
func GetCache(path string, account *model.Account) (interface{}, error) {
return conf.Cache.Get(conf.Ctx, KeyCache(path, account))
}
func DeleteCache(path string, account *model.Account) error {
return conf.Cache.Delete(conf.Ctx, KeyCache(path, account))
}

112
drivers/base/driver.go Normal file
View File

@ -0,0 +1,112 @@
package base
import (
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
)
type DriverConfig struct {
Name string
OnlyProxy bool
NoLink bool // 必须本机返回的
}
type Driver interface {
Config() DriverConfig
Items() []Item
Save(account *model.Account, old *model.Account) error
File(path string, account *model.Account) (*model.File, error)
Files(path string, account *model.Account) ([]model.File, error)
Link(path string, account *model.Account) (*Link, error)
Path(path string, account *model.Account) (*model.File, []model.File, error)
Proxy(c *gin.Context, account *model.Account)
Preview(path string, account *model.Account) (interface{}, error)
// TODO
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
MakeDir(path string, account *model.Account) error
Move(src string, dst string, account *model.Account) error
Copy(src string, dst string, account *model.Account) error
Delete(path string, account *model.Account) error
Upload(file *model.FileStream, account *model.Account) error
}
type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
}
var driversMap = map[string]Driver{}
func RegisterDriver(driver Driver) {
log.Infof("register driver: [%s]", driver.Config().Name)
driversMap[driver.Config().Name] = driver
}
func GetDriver(name string) (driver Driver, ok bool) {
driver, ok = driversMap[name]
return
}
func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0)
for k, v := range driversMap {
if v.Config().OnlyProxy {
res[k] = v.Items()
} else {
res[k] = append([]Item{
//{
// Name: "allow_proxy",
// Label: "allow_proxy",
// Type: TypeBool,
// Required: true,
// Description: "allow proxy",
//},
{
Name: "proxy",
Label: "proxy",
Type: TypeBool,
Required: true,
Description: "web proxy",
},
{
Name: "webdav_proxy",
Label: "webdav proxy",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the server",
},
}, v.Items()...)
}
// 不支持给本地文件添加中转
if v.Config().Name != "Native" {
res[k] = append(res[k], Item{
Name: "proxy_url",
Label: "proxy_url",
Type: TypeString,
Required: false,
Description: "proxy url",
})
}
}
return res
}
var NoRedirectClient *resty.Client
var RestyClient = resty.New()
var HttpClient = &http.Client{}
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
NoRedirectClient.SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
}

37
drivers/base/types.go Normal file
View File

@ -0,0 +1,37 @@
package base
import (
"errors"
)
var (
ErrPathNotFound = errors.New("path not found")
ErrNotFile = errors.New("not file")
ErrNotImplement = errors.New("not implement")
ErrNotSupport = errors.New("not support")
ErrNotFolder = errors.New("not a folder")
)
const (
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeNumber = "number"
)
type Json map[string]interface{}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type Header struct{
Name string `json:"name"`
Value string `json:"value"`
}
type Link struct {
Url string `json:"url"`
Headers []Header `json:"headers"`
}

View File

@ -1,72 +0,0 @@
package drivers
import (
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
)
type Driver interface {
Items() []Item
Save(account *model.Account, old *model.Account) error
File(path string, account *model.Account) (*model.File, error)
Files(path string, account *model.Account) ([]model.File, error)
Link(path string, account *model.Account) (string, error)
Path(path string, account *model.Account) (*model.File, []model.File, error)
Proxy(c *gin.Context, account *model.Account)
Preview(path string, account *model.Account) (interface{}, error)
// TODO
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
//MakeDir(path string, account *model.Account) error
//Move(src string, des string, account *model.Account) error
//Delete(path string) error
//Upload(file *fs.File, path string, account *model.Account) error
}
type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var driversMap = map[string]Driver{}
func RegisterDriver(name string, driver Driver) {
log.Infof("register driver: [%s]", name)
driversMap[name] = driver
}
func GetDriver(name string) (driver Driver, ok bool) {
driver, ok = driversMap[name]
return
}
func GetDrivers() map[string][]Item {
res := make(map[string][]Item, 0)
for k, v := range driversMap {
res[k] = v.Items()
}
return res
}
type Json map[string]interface{}
var NoRedirectClient *resty.Client
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
}

View File

@ -1,8 +0,0 @@
package drivers
import "fmt"
var (
PathNotFound = fmt.Errorf("path not found")
NotFile = fmt.Errorf("not file")
)

View File

@ -1,9 +1,9 @@
package googledrive
package google
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
@ -13,33 +13,38 @@ import (
type GoogleDrive struct{}
var driverName = "GoogleDrive"
func (driver GoogleDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "GoogleDrive",
OnlyProxy: true,
}
}
func (driver GoogleDrive) Items() []drivers.Item {
return []drivers.Item{
func (driver GoogleDrive) Items() []base.Item {
return []base.Item{
{
Name: "client_id",
Label: "client id",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: "string",
Required: true,
Type: base.TypeString,
Required: false,
},
}
}
@ -52,6 +57,9 @@ func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error
_ = model.SaveAccount(account)
return err
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
@ -65,7 +73,7 @@ func (driver GoogleDrive) File(path string, account *model.Account) (*model.File
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
@ -79,13 +87,13 @@ func (driver GoogleDrive) File(path string, account *model.Account) (*model.File
return &file, nil
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []GoogleFile
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]GoogleFile)
} else {
@ -98,7 +106,7 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
return nil, err
}
if len(rawFiles) > 0 {
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil)
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
@ -108,31 +116,40 @@ func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.Fi
return files, nil
}
func (driver GoogleDrive) Link(path string, account *model.Account) (string, error) {
func (driver GoogleDrive) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(path, account)
if err != nil {
return "", err
return nil, err
}
if file.Type == conf.FOLDER {
return "", drivers.NotFile
return nil, base.ErrNotFile
}
link := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
var e GoogleError
_, _ = googleClient.R().SetError(&e).
SetHeader("Authorization", "Bearer "+account.AccessToken).
Get(link)
Get(url)
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return "", err
return nil, err
}
return driver.Link(path, account)
}
return "", fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return link + "&alt=media", nil
link := base.Link{
Url: url + "&alt=media",
Headers: []base.Header{
{
Name: "Authorization",
Value: "Bearer " + account.AccessToken,
},
},
}
return &link, nil
}
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -158,5 +175,27 @@ func (driver GoogleDrive) Proxy(c *gin.Context, account *model.Account) {
}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
return nil, base.ErrNotSupport
}
func (driver GoogleDrive) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver GoogleDrive) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver GoogleDrive) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver GoogleDrive) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver GoogleDrive) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*GoogleDrive)(nil)

View File

@ -1,9 +1,9 @@
package googledrive
package google
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
@ -21,7 +21,7 @@ type GoogleTokenError struct {
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp drivers.TokenResp
var resp base.TokenResp
var e GoogleTokenError
_, err := googleClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
@ -57,7 +57,7 @@ func (driver GoogleDrive) FormatFile(file *GoogleFile) *model.File {
f := &model.File{
Id: file.Id,
Name: file.Name,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: file.ModifiedTime,
Thumbnail: "",
Url: "",
@ -134,7 +134,7 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
//func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) {
// dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir)
// _, _, err := driver.Path(dir, account)
// _, _, err := driver.ParentPath(dir, account)
// if err != nil {
// return nil, err
// }
@ -145,16 +145,14 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF
// if !driver.IsDir(file.MimeType) {
// return &file, err
// } else {
// return nil, drivers.NotFile
// return nil, drivers.ErrNotFile
// }
// }
// }
// return nil, drivers.PathNotFound
// return nil, drivers.ErrPathNotFound
//}
var _ drivers.Driver = (*GoogleDrive)(nil)
func init() {
drivers.RegisterDriver(driverName, &GoogleDrive{})
base.RegisterDriver(&GoogleDrive{})
googleClient.SetRetryCount(3)
}

190
drivers/lanzou/driver.go Normal file
View File

@ -0,0 +1,190 @@
package lanzou
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Lanzou struct{}
func (driver Lanzou) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Lanzou",
OnlyProxy: false,
}
}
func (driver Lanzou) Items() []base.Item {
return []base.Item{
{
Name: "onedrive_type",
Label: "lanzou type",
Type: base.TypeSelect,
Required: true,
Values: "cookie,url",
},
{
Name: "access_token",
Label: "cookie",
Type: base.TypeString,
Description: "about 15 days valid",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
},
{
Name: "site_url",
Label: "share url",
Type: base.TypeString,
},
{
Name: "password",
Label: "share password",
Type: base.TypeString,
},
}
}
func (driver Lanzou) Save(account *model.Account, old *model.Account) error {
if account.OnedriveType == "cookie" {
if account.RootFolder == "" {
account.RootFolder = "-1"
}
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
}
func (driver Lanzou) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []LanZouFile
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]LanZouFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Lanzou) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
log.Debugf("down file: %+v", file)
downId := file.Id
if account.OnedriveType == "cookie" {
downId, err = driver.GetDownPageId(file.Id, account)
if err != nil {
return nil, err
}
}
url, err := driver.GetLink(downId)
if err != nil {
return nil, err
}
link := base.Link{
Url: url,
}
return &link, nil
}
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("lanzou path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
link, err := driver.Link(path, account)
if err != nil {
return nil, nil, err
}
file.Url = link.Url
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Lanzou) Proxy(c *gin.Context, account *model.Account) {
c.Request.Header.Del("Origin")
}
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver *Lanzou) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Lanzou)(nil)

239
drivers/lanzou/lanzou.go Normal file
View File

@ -0,0 +1,239 @@
package lanzou
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"regexp"
"strconv"
"time"
)
var lanzouClient = resty.New()
type LanZouFile struct {
Name string `json:"name"`
NameAll string `json:"name_all"`
Id string `json:"id"`
FolId string `json:"fol_id"`
Size string `json:"size"`
Time string `json:"time"`
Folder bool
}
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
now := time.Now()
f := &model.File{
Id: file.Id,
Name: file.Name,
Driver: driver.Config().Name,
SizeStr: file.Size,
TimeStr: file.Time,
UpdatedAt: &now,
}
if file.Folder {
f.Type = conf.FOLDER
f.Id = file.FolId
} else {
f.Name = file.NameAll
f.Type = utils.GetFileType(filepath.Ext(file.NameAll))
}
return f
}
type LanZouFilesResp struct {
Zt int `json:"zt"`
Info interface{} `json:"info"`
Text []LanZouFile `json:"text"`
}
func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZouFile, error) {
if account.OnedriveType == "cookie" {
files := make([]LanZouFile, 0)
var resp LanZouFilesResp
// folders
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "47",
"folder_id": folderId,
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return nil, err
}
log.Debug(res.String())
if resp.Zt != 1 && resp.Zt != 2 {
return nil, fmt.Errorf("%v", resp.Info)
}
for _, file := range resp.Text {
file.Folder = true
files = append(files, file)
}
// files
pg := 1
for {
_, err = lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "5",
"folder_id": folderId,
"pg": strconv.Itoa(pg),
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return nil, err
}
if resp.Zt != 1 {
return nil, fmt.Errorf("%v", resp.Info)
}
if len(resp.Text) == 0 {
break
}
files = append(files, resp.Text...)
pg++
}
return files, nil
} else {
return driver.GetFilesByUrl(account)
}
}
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
files := make([]LanZouFile, 0)
shareUrl := account.SiteUrl
res, err := lanzouClient.R().Get(shareUrl)
if err != nil {
return nil, err
}
lxArr := regexp.MustCompile(`'lx':(.+?),`).FindStringSubmatch(res.String())
if len(lxArr) == 0 {
return nil, fmt.Errorf("get empty page")
}
lx := lxArr[1]
fid := regexp.MustCompile(`'fid':(.+?),`).FindStringSubmatch(res.String())[1]
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
ls := regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
k := regexp.MustCompile(`var ` + kName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
pg := 1
for {
var resp LanZouFilesResp
res, err = lanzouClient.R().SetResult(&resp).SetFormData(map[string]string{
"lx": lx,
"fid": fid,
"uid": uid,
"pg": strconv.Itoa(pg),
"rep": rep,
"t": t,
"k": k,
"up": up,
"ls": ls,
"pwd": account.Password,
}).Post("https://wwa.lanzouo.com/filemoreajax.php")
if err != nil {
log.Debug(err)
break
}
log.Debug(res.String())
if resp.Zt != 1 {
return nil, fmt.Errorf("%v", resp.Info)
}
if len(resp.Text) == 0 {
break
}
pg++
files = append(files, resp.Text...)
}
return files, nil
}
//type LanzouDownInfo struct {
// FId string `json:"f_id"`
// IsNewd string `json:"is_newd"`
//}
// 获取下载页面的ID
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
var resp LanZouFilesResp
res, err := lanzouClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "22",
"file_id": fileId,
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return "", err
}
log.Debug(res.String())
if resp.Zt != 1 {
return "", fmt.Errorf("%v", resp.Info)
}
info, ok := resp.Info.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%v", resp.Info)
}
fid, ok := info["f_id"].(string)
if !ok {
return "", fmt.Errorf("%v", info["f_id"])
}
return fid, nil
}
type LanzouLinkResp struct {
Dom string `json:"dom"`
Url string `json:"url"`
Zt int `json:"zt"`
}
func (driver *Lanzou) GetLink(downId string) (string, error) {
res, err := lanzouClient.R().Get("https://wwa.lanzouo.com/" + downId)
if err != nil {
return "", err
}
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
if len(iframe) == 0 {
return "", fmt.Errorf("get down empty page")
}
iframeUrl := "https://wwa.lanzouo.com" + iframe[1]
res, err = lanzouClient.R().Get(iframeUrl)
if err != nil {
return "", err
}
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
if len(ajaxdata) == 0 {
return "", fmt.Errorf("get iframe empty page")
}
signs := ajaxdata[1]
sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
var resp LanzouLinkResp
form := map[string]string{
"action": "downprocess",
"signs": signs,
"sign": sign,
"ves": "1",
"websign": "",
"websignkey": websignkey,
}
log.Debugf("form: %+v", form)
_, err = lanzouClient.R().SetResult(&resp).
SetHeader("origin", "https://wwa.lanzouo.com").
SetHeader("referer", iframeUrl).
SetFormData(form).Post("https://wwa.lanzouo.com/ajaxm.php")
if resp.Zt == 1 {
return resp.Dom + "/file/" + resp.Url, nil
}
return "", fmt.Errorf("can't get link")
}
func init() {
base.RegisterDriver(&Lanzou{})
lanzouClient.
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36")
}

View File

@ -3,11 +3,12 @@ package native
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
@ -16,27 +17,33 @@ import (
type Native struct{}
var driverName = "Native"
func (driver Native) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Native",
OnlyProxy: true,
NoLink: true,
}
}
func (driver Native) Items() []drivers.Item {
return []drivers.Item{
func (driver Native) Items() []base.Item {
return []base.Item{
{
Name: "root_folder",
Label: "root folder path",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Type: base.TypeSelect,
Values: "name,size,updated_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Type: base.TypeSelect,
Values: "ASC,DESC",
Required: false,
},
@ -62,7 +69,7 @@ func (driver Native) Save(account *model.Account, old *model.Account) error {
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
f, err := os.Stat(fullPath)
if err != nil {
@ -73,7 +80,7 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
Name: f.Name(),
Size: f.Size(),
UpdatedAt: &time,
Driver: driverName,
Driver: driver.Config().Name,
}
if f.IsDir() {
file.Type = conf.FOLDER
@ -86,7 +93,7 @@ func (driver Native) File(path string, account *model.Account) (*model.File, err
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
files := make([]model.File, 0)
rawFiles, err := ioutil.ReadDir(fullPath)
@ -103,7 +110,7 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
Size: f.Size(),
Type: 0,
UpdatedAt: &time,
Driver: driverName,
Driver: driver.Config().Name,
}
if f.IsDir() {
file.Type = conf.FOLDER
@ -116,16 +123,19 @@ func (driver Native) Files(path string, account *model.Account) ([]model.File, e
return files, nil
}
func (driver Native) Link(path string, account *model.Account) (string, error) {
func (driver Native) Link(path string, account *model.Account) (*base.Link, error) {
fullPath := filepath.Join(account.RootFolder, path)
s, err := os.Stat(fullPath)
if err != nil {
return "", err
return nil, err
}
if s.IsDir() {
return "", fmt.Errorf("can't down folder")
return nil, base.ErrNotFile
}
return fullPath, nil
link := base.Link{
Url: fullPath,
}
return &link, nil
}
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -134,7 +144,7 @@ func (driver Native) Path(path string, account *model.Account) (*model.File, []m
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
if !file.IsDir() {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
@ -151,7 +161,74 @@ func (driver Native) Proxy(c *gin.Context, account *model.Account) {
}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, fmt.Errorf("no need")
return nil, base.ErrNotSupport
}
var _ drivers.Driver = (*Native)(nil)
func (driver Native) MakeDir(path string, account *model.Account) error {
fullPath := filepath.Join(account.RootFolder, path)
err := os.MkdirAll(fullPath, 0700)
return err
}
func (driver Native) Move(src string, dst string, account *model.Account) error {
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
return os.Rename(fullSrc, fullDst)
}
func (driver Native) Copy(src string, dst string, account *model.Account) error {
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstFile, err := driver.File(dst, account)
if err == nil {
if !dstFile.IsDir() {
return base.ErrNotSupport
}
}
if srcFile.IsDir() {
return driver.CopyDir(fullSrc, fullDst)
}
return driver.CopyFile(fullSrc, fullDst)
}
func (driver Native) Delete(path string, account *model.Account) error {
fullPath := filepath.Join(account.RootFolder, path)
file, err := driver.File(path, account)
if err != nil {
return err
}
if file.IsDir() {
return os.RemoveAll(fullPath)
}
return os.Remove(fullPath)
}
func (driver Native) Upload(file *model.FileStream, account *model.Account) error {
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
if err == nil {
// TODO overwrite?
}
basePath := filepath.Dir(fullPath)
if !utils.Exists(basePath) {
err := os.MkdirAll(basePath, 0744)
if err != nil {
return err
}
}
out, err := os.Create(fullPath)
if err != nil {
return err
}
defer func() {
_ = out.Close()
}()
_, err = io.Copy(out, file)
return err
}
var _ base.Driver = (*Native)(nil)

View File

@ -1,9 +1,74 @@
package native
import (
"github.com/Xhofe/alist/drivers"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"io"
"io/ioutil"
"os"
"path"
)
func init() {
drivers.RegisterDriver(driverName, &Native{})
// File copies a single file from src to dst
func (driver *Native) CopyFile(src, dst string) error {
var err error
var srcfd *os.File
var dstfd *os.File
var srcinfo os.FileInfo
if srcfd, err = os.Open(src); err != nil {
return err
}
defer srcfd.Close()
if dstfd, err = os.Create(dst); err != nil {
return err
}
defer dstfd.Close()
if _, err = io.Copy(dstfd, srcfd); err != nil {
return err
}
if srcinfo, err = os.Stat(src); err != nil {
return err
}
return os.Chmod(dst, srcinfo.Mode())
}
// Dir copies a whole directory recursively
func (driver *Native) CopyDir(src string, dst string) error {
var err error
var fds []os.FileInfo
var srcinfo os.FileInfo
if srcinfo, err = os.Stat(src); err != nil {
return err
}
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
if fds, err = ioutil.ReadDir(src); err != nil {
return err
}
for _, fd := range fds {
srcfp := path.Join(src, fd.Name())
dstfp := path.Join(dst, fd.Name())
if fd.IsDir() {
if err = driver.CopyDir(srcfp, dstfp); err != nil {
fmt.Println(err)
}
} else {
if err = driver.CopyFile(srcfp, dstfp); err != nil {
fmt.Println(err)
}
}
}
return nil
}
func init() {
base.RegisterDriver(&Native{})
}

View File

@ -3,7 +3,7 @@ package onedrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
@ -14,21 +14,20 @@ import (
type Onedrive struct{}
var driverName = "Onedrive"
func (driver Onedrive) Items() []drivers.Item {
return []drivers.Item{
{
Name: "proxy",
Label: "proxy",
Type: "bool",
Required: true,
Description: "allow proxy",
},
func (driver Onedrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Onedrive",
OnlyProxy: false,
}
}
func (driver Onedrive) Items() []base.Item {
return []base.Item{
{
Name: "zone",
Label: "zone",
Type: "select",
Type: base.TypeSelect,
Required: true,
Values: "global,cn,us,de",
Description: "",
@ -36,57 +35,57 @@ func (driver Onedrive) Items() []drivers.Item {
{
Name: "onedrive_type",
Label: "onedrive type",
Type: "select",
Type: base.TypeSelect,
Required: true,
Values: "onedrive,sharepoint",
},
{
Name: "client_id",
Label: "client id",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "redirect_uri",
Label: "redirect uri",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: "string",
Type: base.TypeString,
Required: true,
},
{
Name: "site_id",
Label: "site id",
Type: "string",
Type: base.TypeString,
Required: false,
},
{
Name: "root_folder",
Label: "root folder path",
Type: "string",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: "select",
Type: base.TypeSelect,
Values: "name,size,lastModifiedDateTime",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: "select",
Type: base.TypeSelect,
Values: "asc,desc",
Required: false,
},
@ -136,7 +135,7 @@ func (driver Onedrive) File(path string, account *model.Account) (*model.File, e
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driverName,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
@ -150,12 +149,12 @@ func (driver Onedrive) File(path string, account *model.Account) (*model.File, e
return &file, nil
}
}
return nil, drivers.PathNotFound
return nil, base.ErrPathNotFound
}
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path))
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
@ -168,19 +167,24 @@ func (driver Onedrive) Files(path string, account *model.Account) ([]model.File,
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
_ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), files, nil)
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Onedrive) Link(path string, account *model.Account) (string, error) {
func (driver Onedrive) Link(path string, account *model.Account) (*base.Link, error) {
file, err := driver.GetFile(account, path)
if err != nil {
return "", err
return nil, err
}
if file.File.MimeType == "" {
return "", fmt.Errorf("can't down folder")
return nil, base.ErrNotFile
}
return file.Url, nil
link := base.Link{
Url: file.Url,
}
return &link, nil
}
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
@ -189,7 +193,7 @@ func (driver Onedrive) Path(path string, account *model.Account) (*model.File, [
if err != nil {
return nil, nil, err
}
if file.Type != conf.FOLDER {
if !file.IsDir() {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
@ -205,5 +209,27 @@ func (driver Onedrive) Proxy(c *gin.Context, account *model.Account) {
}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, nil
return nil, base.ErrNotSupport
}
func (driver Onedrive) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Onedrive) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Onedrive) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Onedrive) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Onedrive) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Onedrive)(nil)

View File

@ -3,7 +3,7 @@ package onedrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
@ -56,7 +56,7 @@ func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string
}
case "sharepoint":
{
if path == "/" {
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
} else {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
@ -74,7 +74,7 @@ type OneTokenErr struct {
func (driver Onedrive) RefreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp drivers.TokenResp
var resp base.TokenResp
var e OneTokenErr
_, err := oneClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
@ -105,6 +105,11 @@ type OneFile struct {
File struct {
MimeType string `json:"mimeType"`
} `json:"file"`
Thumbnails []struct{
Medium struct{
Url string `json:"url"`
} `json:"medium"`
} `json:"thumbnails"`
}
type OneFiles struct {
@ -124,9 +129,12 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
Name: file.Name,
Size: file.Size,
UpdatedAt: file.LastModifiedDateTime,
Driver: driverName,
Driver: driver.Config().Name,
Url: file.Url,
}
if len(file.Thumbnails) > 0 {
f.Thumbnail = file.Thumbnails[0].Medium.Url
}
if file.File.MimeType == "" {
f.Type = conf.FOLDER
} else {
@ -137,11 +145,11 @@ func (driver Onedrive) FormatFile(file *OneFile) *model.File {
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
var res []OneFile
nextLink := driver.GetMetaUrl(account, false, path) + "/children"
nextLink := driver.GetMetaUrl(account, false, path) + "/children?$expand=thumbnails"
if account.OrderBy != "" {
nextLink += fmt.Sprintf("?orderby=%s", account.OrderBy)
nextLink += fmt.Sprintf("&orderby=%s", account.OrderBy)
if account.OrderDirection != "" {
nextLink += fmt.Sprintf(" %s", account.OrderDirection)
nextLink += fmt.Sprintf("%%20%s", account.OrderDirection)
}
}
for nextLink != "" {
@ -177,9 +185,7 @@ func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, e
return &file, nil
}
var _ drivers.Driver = (*Onedrive)(nil)
func init() {
drivers.RegisterDriver(driverName, &Onedrive{})
base.RegisterDriver(&Onedrive{})
oneClient.SetRetryCount(3)
}

View File

@ -7,22 +7,21 @@ import (
)
type Account struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"unique" binding:"required"`
Index int `json:"index"`
Type string `json:"type"`
ID uint `json:"id" gorm:"primaryKey"` // 唯一ID
Name string `json:"name" gorm:"unique" binding:"required"` // 唯一名称
Index int `json:"index"` // 序号 用于排序
Type string `json:"type"` // 类型即driver
Username string `json:"username"`
Password string `json:"password"`
RefreshToken string `json:"refresh_token"`
AccessToken string `json:"access_token"`
RootFolder string `json:"root_folder"`
Status string `json:"status"`
Status string `json:"status"` // 状态
CronId int
DriveId string
Limit int `json:"limit"`
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
Proxy bool `json:"proxy"`
UpdatedAt *time.Time `json:"updated_at"`
Search bool `json:"search"`
ClientId string `json:"client_id"`
@ -32,6 +31,10 @@ type Account struct {
SiteUrl string `json:"site_url"`
SiteId string `json:"site_id"`
OnedriveType string `json:"onedrive_type"`
WebdavProxy bool `json:"webdav_proxy"`
Proxy bool `json:"proxy"` // 是否中转
//AllowProxy bool `json:"allow_proxy"` // 是否允许中转下载
ProxyUrl string `json:"proxy_url"` // 用于中转下载服务的URL
}
var accountsMap = map[string]Account{}

View File

@ -16,6 +16,8 @@ type File struct {
UpdatedAt *time.Time `json:"updated_at"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
SizeStr string `json:"size_str"`
TimeStr string `json:"time_str"`
}
func SortFiles(files []File, account *Account) {
@ -63,4 +65,4 @@ func (f File) ModTime() time.Time {
func (f File) IsDir() bool {
return f.Type == conf.FOLDER
}
}

35
model/file_stream.go Normal file
View File

@ -0,0 +1,35 @@
package model
import "io"
type FileStream struct {
File io.ReadCloser
Size uint64
ParentPath string
Name string
MIMEType string
}
func (file FileStream) Read(p []byte) (n int, err error) {
return file.File.Read(p)
}
func (file FileStream) GetMIMEType() string {
return file.MIMEType
}
func (file FileStream) GetSize() uint64 {
return file.Size
}
func (file FileStream) Close() error {
return file.File.Close()
}
func (file FileStream) GetFileName() string {
return file.Name
}
func (file FileStream) GetParentPath() string {
return file.ParentPath
}

View File

@ -1,7 +1,9 @@
package model
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"strings"
)
@ -18,6 +20,7 @@ type SettingItem struct {
Type string `json:"type"`
Group int `json:"group"`
Values string `json:"values"`
Version string `json:"version"`
}
func SaveSettings(items []SettingItem) error {
@ -44,6 +47,13 @@ func GetSettings() (*[]SettingItem, error) {
return &items, nil
}
func DeleteSetting(key string) error {
setting := SettingItem{
Key: key,
}
return conf.DB.Delete(&setting).Error
}
func GetSettingByKey(key string) (*SettingItem, error) {
var items SettingItem
if err := conf.DB.Where("`key` = ?", key).First(&items).Error; err != nil {
@ -61,7 +71,7 @@ func LoadSettings() {
if err == nil {
conf.CheckParent = checkParent.Value == "true"
}
checkDown,err := GetSettingByKey("check down link")
checkDown, err := GetSettingByKey("check down link")
if err == nil {
conf.CheckDown = checkDown.Value == "true"
}
@ -72,18 +82,20 @@ func LoadSettings() {
}
title, err := GetSettingByKey("title")
if err == nil {
//conf.CustomizeStyle = customizeStyle.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "Loading...", title.Value, 1)
}
customizeStyle, err := GetSettingByKey("customize style")
customizeHead, err := GetSettingByKey("customize head")
if err == nil {
//conf.CustomizeStyle = customizeStyle.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "/* customize-style */", customizeStyle.Value, 1)
conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize head -->", customizeHead.Value, 1)
}
customizeScript, err := GetSettingByKey("customize script")
customizeBody, err := GetSettingByKey("customize body")
if err == nil {
//conf.CustomizeStyle = customizeScript.Value
conf.IndexHtml = strings.Replace(conf.IndexHtml, "// customize-js", customizeScript.Value, 1)
conf.IndexHtml = strings.Replace(conf.IndexHtml, "<!-- customize body -->", customizeBody.Value, 1)
}
adminPassword, err := GetSettingByKey("password")
if err == nil {
conf.Token = utils.GetMD5Encode(fmt.Sprintf("https://github.com/Xhofe/alist-%s", adminPassword.Value))
}
davUsername, err := GetSettingByKey("WebDAV username")

View File

@ -1,80 +0,0 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"path/filepath"
)
func Auth(c *gin.Context) {
token := c.GetHeader("Authorization")
password, err := model.GetSettingByKey("password")
if err != nil {
if err == gorm.ErrRecordNotFound {
ErrorResp(c, fmt.Errorf("password not set"), 400)
return
}
ErrorResp(c, err, 500)
return
}
if token != utils.GetMD5Encode(password.Value) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
c.Next()
}
func Login(c *gin.Context) {
SuccessResp(c)
}
func CheckAccount(c *gin.Context) {
if model.AccountsCount() == 0 {
ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
return
}
c.Next()
}
func CheckParent(path string, password string) bool {
meta, err := model.GetMetaByPath(path)
if err == nil {
if meta.Password != "" && meta.Password != password {
return false
}
return true
} else {
if path == "/" || path == "\\" {
return true
}
return CheckParent(filepath.Dir(path), password)
}
}
func CheckDownLink(path string, passwordMd5 string) bool {
if !conf.CheckDown {
return true
}
meta, err := model.GetMetaByPath(path)
log.Debugf("check down path: %s", path)
if err == nil {
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
if meta.Password != "" && utils.Get16MD5Encode(meta.Password) != passwordMd5 {
return false
}
return true
} else {
if !conf.CheckParent {
return true
}
if path == "/" || path == "\\" {
return true
}
return CheckDownLink(filepath.Dir(path), passwordMd5)
}
}

51
server/common/check.go Normal file
View File

@ -0,0 +1,51 @@
package common
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func Login(c *gin.Context) {
SuccessResp(c)
}
func CheckParent(path string, password string) bool {
meta, err := model.GetMetaByPath(path)
if err == nil {
if meta.Password != "" && meta.Password != password {
return false
}
return true
} else {
if path == "/" {
return true
}
return CheckParent(utils.Dir(path), password)
}
}
func CheckDownLink(path string, passwordMd5 string, name string) bool {
if !conf.CheckDown {
return true
}
meta, err := model.GetMetaByPath(path)
log.Debugf("check down path: %s", path)
if err == nil {
log.Debugf("check down link: %s,%s", meta.Password, passwordMd5)
if meta.Password != "" && utils.SignWithPassword(name, meta.Password) != passwordMd5 {
return false
}
return true
} else {
if !conf.CheckParent {
return true
}
if path == "/" {
return true
}
return CheckDownLink(utils.Dir(path), passwordMd5, name)
}
}

View File

@ -1,8 +1,8 @@
package server
package common
import (
"fmt"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@ -15,7 +15,12 @@ type Resp struct {
Data interface{} `json:"data"`
}
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
type PathReq struct {
Path string `json:"path"`
Password string `json:"password"`
}
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
var path, name string
switch model.AccountsCount() {
case 0:
@ -32,7 +37,7 @@ func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name)
}
driver, ok := drivers.GetDriver(account.Type)
driver, ok := base.GetDriver(account.Type)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
}

View File

@ -1,9 +1,10 @@
package server
package controllers
import (
"fmt"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"strconv"
@ -13,52 +14,52 @@ import (
func GetAccounts(c *gin.Context) {
accounts, err := model.GetAccounts()
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
SuccessResp(c, accounts)
common.SuccessResp(c, accounts)
}
func CreateAccount(c *gin.Context) {
var req model.Account
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
driver, ok := drivers.GetDriver(req.Type)
driver, ok := base.GetDriver(req.Type)
if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return
}
now := time.Now()
req.UpdatedAt = &now
if err := model.CreateAccount(&req); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
log.Debugf("new account: %+v", req)
err = driver.Save(&req, nil)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
SuccessResp(c)
common.SuccessResp(c)
}
}
func SaveAccount(c *gin.Context) {
var req model.Account
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
driver, ok := drivers.GetDriver(req.Type)
driver, ok := base.GetDriver(req.Type)
if !ok {
ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
common.ErrorResp(c, fmt.Errorf("no [%s] driver", req.Type), 400)
return
}
old, err := model.GetAccountById(req.ID)
if err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
now := time.Now()
@ -67,15 +68,15 @@ func SaveAccount(c *gin.Context) {
model.DeleteAccountFromMap(old.Name)
}
if err := model.SaveAccount(&req); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
log.Debugf("save account: %+v", req)
err = driver.Save(&req, old)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
SuccessResp(c)
common.SuccessResp(c)
}
}
@ -83,12 +84,12 @@ func DeleteAccount(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
if err := model.DeleteAccount(uint(id)); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
SuccessResp(c)
common.SuccessResp(c)
}

View File

@ -1,15 +1,16 @@
package server
package controllers
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
func ClearCache(c *gin.Context) {
err := conf.Cache.Clear(conf.Ctx)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
SuccessResp(c)
common.SuccessResp(c)
}
}

143
server/controllers/down.go Normal file
View File

@ -0,0 +1,143 @@
package controllers
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
)
func Down(c *gin.Context) {
rawPath := c.Param("path")
rawPath = utils.ParsePath(rawPath)
log.Debugf("down: %s", rawPath)
account, path, driver, err := common.ParsePath(rawPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if driver.Config().OnlyProxy || account.Proxy {
Proxy(c)
return
}
link, err := driver.Link(path, account)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
c.Redirect(302, link.Url)
return
}
func Proxy(c *gin.Context) {
rawPath := c.Param("path")
rawPath = utils.ParsePath(rawPath)
log.Debugf("proxy: %s", rawPath)
account, path, driver, err := common.ParsePath(rawPath)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
// 只有三种情况允许中转:
// 1. 账号开启中转
// 2. driver只能中转
// 3. 是文本类型文件
if !account.Proxy && !driver.Config().OnlyProxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT {
common.ErrorResp(c, fmt.Errorf("[%s] not allowed proxy", account.Name), 403)
return
}
// 中转时有中转机器使用中转机器
if account.ProxyUrl != "" {
name := utils.Base(rawPath)
link := fmt.Sprintf("%s%s?sign=%s", account.ProxyUrl, rawPath, utils.SignWithToken(name, conf.Token))
c.Redirect(302, link)
return
}
link, err := driver.Link(path, account)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
// 本机文件直接返回文件
if account.Type == "Native" {
// 对于名称为index.html的文件需要特殊处理
if utils.Base(rawPath) == "index.html" {
file, err := os.Open(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
defer func() {
_ = file.Close()
}()
fileStat, err := os.Stat(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
http.ServeContent(c.Writer, c.Request, utils.Base(rawPath), fileStat.ModTime(), file)
return
}
c.File(link.Url)
return
} else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link)
return
}
driver.Proxy(c, account)
r := c.Request
w := c.Writer
target, err := url.Parse(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
protocol := "http://"
if strings.HasPrefix(link.Url, "https://") {
protocol = "https://"
}
targetHost, err := url.Parse(fmt.Sprintf("%s%s", protocol, target.Host))
proxy := httputil.NewSingleHostReverseProxy(targetHost)
r.URL = target
r.Host = target.Host
proxy.ServeHTTP(w, r)
}
}
var client *resty.Client
func init() {
client = resty.New()
client.SetRetryCount(3)
}
func Text(c *gin.Context, link *base.Link) {
res, err := client.R().Get(link.Url)
if err != nil {
common.ErrorResp(c, err, 500)
return
}
text := res.String()
t := utils.GetStrCoding(res.Body())
log.Debugf("text type: %s", t)
if t != utils.UTF8 {
body, err := utils.GbkToUtf8(res.Body())
if err != nil {
common.ErrorResp(c, err, 500)
return
}
text = string(body)
}
c.String(200, text)
}

View File

@ -0,0 +1,11 @@
package controllers
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
func GetDrivers(c *gin.Context) {
common.SuccessResp(c, base.GetDrivers())
}

View File

@ -1,7 +1,8 @@
package server
package controllers
import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"strconv"
@ -10,37 +11,37 @@ import (
func GetMetas(c *gin.Context) {
metas,err := model.GetMetas()
if err != nil {
ErrorResp(c,err,500)
common.ErrorResp(c,err,500)
return
}
SuccessResp(c, metas)
common.SuccessResp(c, metas)
}
func CreateMeta(c *gin.Context) {
var req model.Meta
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
if err := model.CreateMeta(req); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
SuccessResp(c)
common.SuccessResp(c)
}
}
func SaveMeta(c *gin.Context) {
var req model.Meta
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
if err := model.SaveMeta(req); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
SuccessResp(c)
common.SuccessResp(c)
}
}
@ -48,13 +49,13 @@ func DeleteMeta(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
//path = utils.ParsePath(path)
if err := model.DeleteMeta(uint(id)); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
SuccessResp(c)
common.SuccessResp(c)
}

View File

@ -1,75 +1,59 @@
package server
package controllers
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
type PathReq struct {
Path string `json:"Path"`
Password string `json:"Password"`
}
func Path(c *gin.Context) {
var req PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
log.Debugf("path: %s", req.Path)
meta, err := model.GetMetaByPath(req.Path)
if err == nil {
if meta.Password != "" && meta.Password != req.Password {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
// TODO hide or ignore?
} else if conf.CheckParent {
if !CheckParent(filepath.Dir(req.Path), req.Password) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
}
reqV, _ := c.Get("req")
req := reqV.(common.PathReq)
if model.AccountsCount() > 1 && req.Path == "/" {
files, err := model.GetAccountFiles()
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
c.JSON(200, Resp{
c.JSON(200, common.Resp{
Code: 200,
Message: "folder",
Data: files,
})
return
}
account, path, driver, err := ParsePath(req.Path)
account, path, driver, err := common.ParsePath(req.Path)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
file, files, err := driver.Path(path, account)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
if file != nil {
if account.Type == "Native" {
file.Url = fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path)
// 对于中转文件或只能中转,将链接修改为中转链接
if driver.Config().OnlyProxy || account.Proxy {
if account.ProxyUrl != "" {
file.Url = fmt.Sprintf("%s%s?sign=%s", account.ProxyUrl, req.Path, utils.SignWithToken(file.Name, conf.Token))
} else {
file.Url = fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path)
}
}
c.JSON(200, Resp{
c.JSON(200, common.Resp{
Code: 200,
Message: "file",
Data: []*model.File{file},
})
} else {
meta, _ := model.GetMetaByPath(req.Path)
if meta != nil && meta.Hide != "" {
tmpFiles := make([]model.File, 0)
hideFiles := strings.Split(meta.Hide, ",")
@ -80,7 +64,7 @@ func Path(c *gin.Context) {
}
files = tmpFiles
}
c.JSON(200, Resp{
c.JSON(200, common.Resp{
Code: 200,
Message: "folder",
Data: files,
@ -88,56 +72,53 @@ func Path(c *gin.Context) {
}
}
// 返回真实的链接,且携带头,只提供给中转程序使用
func Link(c *gin.Context) {
var req PathReq
var req common.PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
rawPath := req.Path
rawPath = utils.ParsePath(rawPath)
log.Debugf("link: %s", rawPath)
account, path, driver, err := ParsePath(rawPath)
account, path, driver, err := common.ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
SuccessResp(c, gin.H{
"url": fmt.Sprintf("//%s/d%s", c.Request.Host, req.Path),
if driver.Config().NoLink {
common.SuccessResp(c, base.Link{
Url: fmt.Sprintf("//%s/d%s?sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)),
})
return
} else {
SuccessResp(c, gin.H{
"url": link,
})
common.SuccessResp(c, link)
return
}
}
func Preview(c *gin.Context) {
var req PathReq
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
return
}
reqV, _ := c.Get("req")
req := reqV.(common.PathReq)
rawPath := req.Path
rawPath = utils.ParsePath(rawPath)
log.Debugf("preview: %s", rawPath)
account, path, driver, err := ParsePath(rawPath)
account, path, driver, err := common.ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
return
}
data, err := driver.Preview(path, account)
if err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
SuccessResp(c, data)
common.SuccessResp(c, data)
}
}

View File

@ -1,38 +1,48 @@
package server
package controllers
import (
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
func SaveSettings(c *gin.Context) {
var req []model.SettingItem
if err := c.ShouldBind(&req); err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
if err := model.SaveSettings(req); err != nil {
ErrorResp(c, err, 500)
common.ErrorResp(c, err, 500)
} else {
model.LoadSettings()
SuccessResp(c)
common.SuccessResp(c)
}
}
func GetSettings(c *gin.Context) {
settings, err := model.GetSettings()
if err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
SuccessResp(c, settings)
common.SuccessResp(c, settings)
}
func GetSettingsPublic(c *gin.Context) {
settings, err := model.GetSettingsPublic()
if err != nil {
ErrorResp(c, err, 400)
common.ErrorResp(c, err, 400)
return
}
SuccessResp(c, settings)
common.SuccessResp(c, settings)
}
func DeleteSetting(c *gin.Context) {
key := c.Query("key")
if err := model.DeleteSetting(key); err != nil {
common.ErrorResp(c, err, 500)
return
}
common.SuccessResp(c)
}

View File

@ -1,132 +0,0 @@
package server
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http/httputil"
"net/url"
"path/filepath"
"strings"
)
func Down(c *gin.Context) {
rawPath, err := url.PathUnescape(c.Param("path"))
if err != nil {
ErrorResp(c, err, 500)
return
}
rawPath = utils.ParsePath(rawPath)
log.Debugf("down: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(filepath.Dir(rawPath), pw) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "GoogleDrive" {
Proxy(c)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
c.File(link)
return
} else {
c.Redirect(302, link)
return
}
}
func Proxy(c *gin.Context) {
rawPath, err := url.PathUnescape(c.Param("path"))
if err != nil {
ErrorResp(c, err, 500)
return
}
rawPath = utils.ParsePath(rawPath)
log.Debugf("proxy: %s", rawPath)
pw := c.Query("pw")
if !CheckDownLink(filepath.Dir(rawPath), pw) {
ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
account, path, driver, err := ParsePath(rawPath)
if err != nil {
ErrorResp(c, err, 500)
return
}
if !account.Proxy && utils.GetFileType(filepath.Ext(rawPath)) != conf.TEXT {
ErrorResp(c, fmt.Errorf("[%s] not allowed proxy", account.Name), 403)
return
}
link, err := driver.Link(path, account)
if err != nil {
ErrorResp(c, err, 500)
return
}
if account.Type == "Native" {
c.File(link)
return
} else {
if utils.GetFileType(filepath.Ext(rawPath)) == conf.TEXT {
Text(c, link)
return
}
driver.Proxy(c, account)
r := c.Request
w := c.Writer
target, err := url.Parse(link)
if err != nil {
ErrorResp(c, err, 500)
return
}
protocol := "http://"
if strings.HasPrefix(link, "https://") {
protocol = "https://"
}
targetHost, err := url.Parse(fmt.Sprintf("%s%s", protocol, target.Host))
proxy := httputil.NewSingleHostReverseProxy(targetHost)
r.URL = target
r.Host = target.Host
proxy.ServeHTTP(w, r)
}
}
var client *resty.Client
func init() {
client = resty.New()
client.SetRetryCount(3)
}
func Text(c *gin.Context, link string) {
res, err := client.R().Get(link)
if err != nil {
ErrorResp(c, err, 500)
return
}
text := res.String()
t := utils.GetStrCoding(res.Body())
log.Debugf("text type: %s", t)
if t != utils.UTF8 {
body, err := utils.GbkToUtf8(res.Body())
if err != nil {
ErrorResp(c, err, 500)
return
}
text = string(body)
}
c.String(200, text)
}

View File

@ -1,10 +0,0 @@
package server
import (
"github.com/Xhofe/alist/drivers"
"github.com/gin-gonic/gin"
)
func GetDrivers(c *gin.Context) {
SuccessResp(c, drivers.GetDrivers())
}

View File

@ -0,0 +1,16 @@
package middlewares
import (
"fmt"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
func CheckAccount(c *gin.Context) {
if model.AccountsCount() == 0 {
common.ErrorResp(c, fmt.Errorf("no accounts,please add one first"), 1001)
return
}
c.Next()
}

View File

@ -0,0 +1,27 @@
package middlewares
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/gin-gonic/gin"
)
func Auth(c *gin.Context) {
token := c.GetHeader("Authorization")
//password, err := model.GetSettingByKey("password")
//if err != nil {
// if err == gorm.ErrRecordNotFound {
// common.ErrorResp(c, fmt.Errorf("password not set"), 400)
// return
// }
// common.ErrorResp(c, err, 500)
// return
//}
//if token != utils.GetMD5Encode(password.Value) {
if token != conf.Token {
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
return
}
c.Next()
}

View File

@ -0,0 +1,27 @@
package middlewares
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
func DownCheck(c *gin.Context) {
sign := c.Query("sign")
rawPath := c.Param("path")
rawPath = utils.ParsePath(rawPath)
name := utils.Base(rawPath)
if sign == utils.SignWithToken(name, conf.Token) {
c.Next()
return
}
pw := c.Query("pw")
if !common.CheckDownLink(utils.Dir(rawPath), pw, utils.Base(rawPath)) {
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
c.Abort()
return
}
c.Next()
}

View File

@ -0,0 +1,40 @@
package middlewares
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/utils"
"github.com/gin-gonic/gin"
)
func PathCheck(c *gin.Context) {
var req common.PathReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
req.Path = utils.ParsePath(req.Path)
c.Set("req",req)
token := c.GetHeader("Authorization")
if token == conf.Token {
c.Next()
return
}
meta, err := model.GetMetaByPath(req.Path)
if err == nil {
if meta.Password != "" && meta.Password != req.Password {
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
c.Abort()
return
}
} else if conf.CheckParent {
if !common.CheckParent(utils.Dir(req.Path), req.Password) {
common.ErrorResp(c, fmt.Errorf("wrong password"), 401)
c.Abort()
return
}
}
c.Next()
}

View File

@ -1,6 +1,9 @@
package server
import (
"github.com/Xhofe/alist/server/common"
"github.com/Xhofe/alist/server/controllers"
"github.com/Xhofe/alist/server/middlewares"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
@ -9,35 +12,42 @@ func InitApiRouter(r *gin.Engine) {
// TODO from settings
Cors(r)
r.GET("/d/*path", Down)
r.GET("/p/*path", Proxy)
r.GET("/d/*path", middlewares.DownCheck, controllers.Down)
r.GET("/p/*path", middlewares.DownCheck, controllers.Proxy)
api := r.Group("/api")
public := api.Group("/public")
{
public.POST("/path", CheckAccount, Path)
public.POST("/preview", CheckAccount, Preview)
public.GET("/settings", GetSettingsPublic)
public.POST("/link", CheckAccount, Link)
path := public.Group("", middlewares.PathCheck, middlewares.CheckAccount)
path.POST("/path", controllers.Path)
path.POST("/preview", controllers.Preview)
//path.POST("/link",middlewares.Auth, controllers.Link)
public.GET("/settings", controllers.GetSettingsPublic)
}
admin := api.Group("/admin")
{
admin.Use(Auth)
admin.GET("/login", Login)
admin.GET("/settings", GetSettings)
admin.POST("/settings", SaveSettings)
admin.POST("/account/create", CreateAccount)
admin.POST("/account/save", SaveAccount)
admin.GET("/accounts", GetAccounts)
admin.DELETE("/account", DeleteAccount)
admin.GET("/drivers", GetDrivers)
admin.GET("/clear_cache", ClearCache)
admin.Use(middlewares.Auth)
admin.GET("/login", common.Login)
admin.GET("/settings", controllers.GetSettings)
admin.POST("/settings", controllers.SaveSettings)
admin.DELETE("/setting", controllers.DeleteSetting)
admin.GET("/metas", GetMetas)
admin.POST("/meta/create", CreateMeta)
admin.POST("/meta/save", SaveMeta)
admin.DELETE("/meta", DeleteMeta)
admin.POST("/account/create", controllers.CreateAccount)
admin.POST("/account/save", controllers.SaveAccount)
admin.GET("/accounts", controllers.GetAccounts)
admin.DELETE("/account", controllers.DeleteAccount)
admin.GET("/drivers", controllers.GetDrivers)
admin.GET("/clear_cache", controllers.ClearCache)
admin.GET("/metas", controllers.GetMetas)
admin.POST("/meta/create", controllers.CreateMeta)
admin.POST("/meta/save", controllers.SaveMeta)
admin.DELETE("/meta", controllers.DeleteMeta)
admin.POST("/link", controllers.Link)
}
Static(r)
WebDav(r)

View File

@ -8,7 +8,7 @@ import (
"context"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
@ -21,28 +21,28 @@ import (
type FileSystem struct{}
func ParsePath(rawPath string) (*model.Account, string, drivers.Driver, error) {
var path, name string
func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) {
var internalPath, name string
switch model.AccountsCount() {
case 0:
return nil, "", nil, fmt.Errorf("no accounts,please add one first")
case 1:
path = rawPath
internalPath = rawPath
break
default:
paths := strings.Split(rawPath, "/")
path = "/" + strings.Join(paths[2:], "/")
internalPath = "/" + strings.Join(paths[2:], "/")
name = paths[1]
}
account, ok := model.GetAccount(name)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] account", name)
}
driver, ok := drivers.GetDriver(account.Type)
driver, ok := base.GetDriver(account.Type)
if !ok {
return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type)
}
return &account, path, driver, nil
return &account, internalPath, driver, nil
}
func (fs *FileSystem) File(rawPath string) (*model.File, error) {
@ -80,29 +80,30 @@ func (fs *FileSystem) Files(rawPath string) ([]model.File, error) {
return driver.Files(path_, account)
}
func GetPW(path string) string {
if !conf.CheckDown {
return ""
}
meta, err := model.GetMetaByPath(path)
if err == nil {
if meta.Password != "" {
utils.Get16MD5Encode(meta.Password)
}
return ""
} else {
if !conf.CheckParent {
return ""
}
if path == "/" || path == "\\" {
return ""
}
return GetPW(filepath.Dir(path))
}
}
//func GetPW(path string, name string) string {
// if !conf.CheckDown {
// return ""
// }
// meta, err := model.GetMetaByPath(path)
// if err == nil {
// if meta.Password != "" {
// return utils.SignWithPassword(name, meta.Password)
// }
// return ""
// } else {
// if !conf.CheckParent {
// return ""
// }
// if path == "/" {
// return ""
// }
// return GetPW(utils.Dir(path), name)
// }
//}
func (fs *FileSystem) Link(host, rawPath string) (string, error) {
func (fs *FileSystem) Link(r *http.Request, rawPath string) (string, error) {
rawPath = utils.ParsePath(rawPath)
log.Debugf("get link path: %s", rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
// error
}
@ -110,20 +111,81 @@ func (fs *FileSystem) Link(host, rawPath string) (string, error) {
if err != nil {
return "", err
}
if account.Type == "Native" || account.Type == "GoogleDrive" {
link := fmt.Sprintf("//%s/p%s", host, rawPath)
if conf.CheckDown {
pw := GetPW(filepath.Dir(rawPath))
link += "?pw" + pw
}
log.Debugf("proxy link: %s", link)
return link, nil
link := ""
protocol := "http"
if r.TLS != nil {
protocol = "https"
}
return driver.Link(path_, account)
if driver.Config().OnlyProxy || account.WebdavProxy {
link = fmt.Sprintf("%s://%s/p%s", protocol, r.Host, rawPath)
if conf.CheckDown {
sign := utils.SignWithToken(utils.Base(rawPath), conf.Token)
link += "?sign" + sign
}
} else {
link_, err := driver.Link(path_, account)
if err != nil {
return "", err
}
link = link_.Url
}
log.Debugf("webdav get link: %s", link)
return link, err
}
func (fs *FileSystem) CreateDirectory(ctx context.Context, reqPath string) (interface{}, error) {
return nil, nil
func (fs *FileSystem) CreateDirectory(ctx context.Context, rawPath string) error {
rawPath = utils.ParsePath(rawPath)
if rawPath == "/" {
return ErrNotImplemented
}
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
return ErrNotImplemented
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return err
}
return driver.MakeDir(path_,account)
}
func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath string) error {
rawPath = utils.ParsePath(rawPath)
if model.AccountsCount() > 1 && rawPath == "/" {
return ErrNotImplemented
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return err
}
//fileSize, err := strconv.ParseUint(r.Header.Get("Content-Length"), 10, 64)
fileSize := uint64(r.ContentLength)
//if err != nil {
// return err
//}
filePath, fileName := filepath.Split(path_)
fileData := model.FileStream{
MIMEType: r.Header.Get("Content-Type"),
File: r.Body,
Size: fileSize,
Name: fileName,
ParentPath: filePath,
}
return driver.Upload(&fileData, account)
}
func (fs *FileSystem) Delete(rawPath string) error {
rawPath = utils.ParsePath(rawPath)
if rawPath == "/" {
return ErrNotImplemented
}
if model.AccountsCount() > 1 && len(strings.Split(rawPath, "/")) < 2 {
return ErrNotImplemented
}
account, path_, driver, err := ParsePath(rawPath)
if err != nil {
return err
}
return driver.Delete(path_, account)
}
// slashClean is equivalent to but slightly more efficient than
@ -138,16 +200,55 @@ func slashClean(name string) string {
// moveFiles moves files and/or directories from src to dst.
//
// See section 9.9.4 for when various HTTP status codes apply.
func moveFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool) (status int, err error) {
func moveFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool) (status int, err error) {
src = utils.ParsePath(src)
dst = utils.ParsePath(dst)
if src == dst {
return http.StatusMethodNotAllowed, errDestinationEqualsSource
}
srcAccount, srcPath, driver, err := ParsePath(src)
if err != nil {
return http.StatusMethodNotAllowed, err
}
dstAccount, dstPath, _, err := ParsePath(dst)
if err != nil {
return http.StatusMethodNotAllowed, err
}
if srcAccount.Name != dstAccount.Name {
return http.StatusMethodNotAllowed, errInvalidDestination
}
err = driver.Move(srcPath,dstPath,srcAccount)
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusNoContent, nil
}
// copyFiles copies files and/or directories from src to dst.
//
// See section 9.8.5 for when various HTTP status codes apply.
func copyFiles(ctx context.Context, fs *FileSystem, src FileInfo, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
func copyFiles(ctx context.Context, fs *FileSystem, src string, dst string, overwrite bool, depth int, recursion int) (status int, err error) {
src = utils.ParsePath(src)
dst = utils.ParsePath(dst)
if src == dst {
return http.StatusMethodNotAllowed, errDestinationEqualsSource
}
srcAccount, srcPath, driver, err := ParsePath(src)
if err != nil {
return http.StatusMethodNotAllowed, err
}
dstAccount, dstPath, _, err := ParsePath(dst)
if err != nil {
return http.StatusMethodNotAllowed, err
}
if srcAccount.Name != dstAccount.Name {
// TODO 跨账号复制
return http.StatusMethodNotAllowed, errInvalidDestination
}
err = driver.Copy(srcPath,dstPath,srcAccount)
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusNoContent, nil
}

View File

@ -233,8 +233,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *
}
w.Header().Set("ETag", etag)
log.Debugf("url: %+v", r.URL)
host := r.Host
link, err := fs.Link(host, reqPath)
link, err := fs.Link(r, reqPath)
if err != nil {
return http.StatusInternalServerError, err
}
@ -254,26 +253,11 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *FileS
return status, err
}
defer release()
//ctx := r.Context()
//// 尝试作为文件删除
//if ok, file := fs.IsFileExist(reqPath); ok {
// if err := fs.Delete(ctx, []uint{}, []uint{file.ID}, false); err != nil {
// return http.StatusMethodNotAllowed, err
// }
// return http.StatusNoContent, nil
//}
//
//// 尝试作为目录删除
//if ok, folder := fs.IsPathExist(reqPath); ok {
// if err := fs.Delete(ctx, []uint{folder.ID}, []uint{}, false); err != nil {
// return http.StatusMethodNotAllowed, err
// }
// return http.StatusNoContent, nil
//}
return http.StatusNotFound, nil
err = fs.Delete(reqPath)
if err != nil {
return http.StatusMethodNotAllowed, err
}
return http.StatusNoContent, nil
}
// OK
@ -292,7 +276,13 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *FileSyst
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, nil)
err = fs.Upload(ctx, r, reqPath)
if err != nil {
return http.StatusMethodNotAllowed, err
}
_, fi := isPathExist(ctx, fs, reqPath)
etag, err := findETag(ctx, fs, h.LockSystem, reqPath, fi)
if err != nil {
return http.StatusInternalServerError, err
}
@ -322,7 +312,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *FileSy
// ctx = context.WithValue(ctx, fsctx.IgnoreDirectoryConflictCtx, true)
//}
}
if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {
if err := fs.CreateDirectory(ctx, reqPath); err != nil {
return http.StatusConflict, err
}
return http.StatusCreated, nil
@ -362,7 +352,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
ctx := r.Context()
isExist, target := isPathExist(ctx, fs, src)
isExist, _ := isPathExist(ctx, fs, src)
if !isExist {
return http.StatusNotFound, nil
@ -391,7 +381,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
return http.StatusBadRequest, errInvalidDepth
}
}
return copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
return copyFiles(ctx, fs, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
}
// windows下某些情况下网盘根目录下Office保存文件时附带的锁token只包含源文件
@ -410,7 +400,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *Fil
return http.StatusBadRequest, errInvalidDepth
}
}
return moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
return moveFiles(ctx, fs, src, dst, r.Header.Get("Overwrite") == "T")
}
// OK

View File

@ -94,4 +94,23 @@ func RemoveLastSlash(path string) string {
return strings.TrimSuffix(path, "/")
}
return path
}
func Dir(path string) string {
idx := strings.LastIndex(path, "/")
if idx == 0 {
return "/"
}
if idx == -1 {
return path
}
return path[:idx]
}
func Base(path string) string {
idx := strings.LastIndex(path, "/")
if idx == -1 {
return path
}
return path[idx+1:]
}

View File

@ -3,6 +3,7 @@ package utils
import (
"crypto/md5"
"encoding/hex"
"fmt"
)
// GetMD5Encode
@ -16,3 +17,11 @@ func GetMD5Encode(data string) string {
func Get16MD5Encode(data string) string {
return GetMD5Encode(data)[8:24]
}
func SignWithPassword(name, password string) string {
return Get16MD5Encode(fmt.Sprintf("alist-%s-%s", password, name))
}
func SignWithToken(name, token string) string {
return Get16MD5Encode(fmt.Sprintf("alist-%s-%s", token, name))
}