mirror of https://github.com/Xhofe/alist
parent
6106a2d4cc
commit
bdf4b52885
3
go.mod
3
go.mod
|
@ -33,6 +33,7 @@ require (
|
|||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0
|
||||
github.com/hirochachacha/go-smb2 v1.1.0
|
||||
github.com/ipfs/go-ipfs-api v0.7.0
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
|
@ -82,6 +83,8 @@ require (
|
|||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hekmon/cunits/v2 v2.1.0 // indirect
|
||||
github.com/ipfs/boxo v0.12.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
)
|
||||
|
|
6
go.sum
6
go.sum
|
@ -240,11 +240,17 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hekmon/cunits/v2 v2.1.0 h1:k6wIjc4PlacNOHwKEMBgWV2/c8jyD4eRMs5mR1BBhI0=
|
||||
github.com/hekmon/cunits/v2 v2.1.0/go.mod h1:9r1TycXYXaTmEWlAIfFV8JT+Xo59U96yUJAYHxzii2M=
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0 h1:0Fb11qE0IBh4V4GlOwHNYpqpjcYDp5GouolwrpmcUDQ=
|
||||
github.com/hekmon/transmissionrpc/v3 v3.0.0/go.mod h1:38SlNhFzinVUuY87wGj3acOmRxeYZAZfrj6Re7UgCDg=
|
||||
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
|
|
@ -54,11 +54,15 @@ const (
|
|||
Aria2Uri = "aria2_uri"
|
||||
Aria2Secret = "aria2_secret"
|
||||
|
||||
// transmission
|
||||
TransmissionUri = "transmission_uri"
|
||||
TransmissionSeedtime = "transmission_seedtime"
|
||||
|
||||
// single
|
||||
Token = "token"
|
||||
IndexProgress = "index_progress"
|
||||
|
||||
//SSO
|
||||
// SSO
|
||||
SSOClientId = "sso_client_id"
|
||||
SSOClientSecret = "sso_client_secret"
|
||||
SSOLoginEnabled = "sso_login_enabled"
|
||||
|
@ -73,7 +77,7 @@ const (
|
|||
SSODefaultPermission = "sso_default_permission"
|
||||
SSOCompatibilityMode = "sso_compatibility_mode"
|
||||
|
||||
//ldap
|
||||
// ldap
|
||||
LdapLoginEnabled = "ldap_login_enabled"
|
||||
LdapServer = "ldap_server"
|
||||
LdapManagerDN = "ldap_manager_dn"
|
||||
|
@ -84,7 +88,7 @@ const (
|
|||
LdapDefaultDir = "ldap_default_dir"
|
||||
LdapLoginTips = "ldap_login_tips"
|
||||
|
||||
//s3
|
||||
// s3
|
||||
S3Buckets = "s3_buckets"
|
||||
S3AccessKeyId = "s3_access_key_id"
|
||||
S3SecretAccessKey = "s3_secret_access_key"
|
||||
|
@ -97,7 +101,7 @@ const (
|
|||
const (
|
||||
UNKNOWN = iota
|
||||
FOLDER
|
||||
//OFFICE
|
||||
// OFFICE
|
||||
VIDEO
|
||||
AUDIO
|
||||
TEXT
|
||||
|
|
|
@ -6,4 +6,5 @@ import (
|
|||
_ "github.com/alist-org/alist/v3/internal/offline_download/http"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/pikpak"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/qbit"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/transmission"
|
||||
)
|
||||
|
|
|
@ -101,6 +101,19 @@ outer:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t.tool.Name() == "transmission" {
|
||||
// hack for transmission
|
||||
seedTime := setting.GetInt(conf.TransmissionSeedtime, 0)
|
||||
if seedTime >= 0 {
|
||||
t.Status = "offline download completed, waiting for seeding"
|
||||
<-time.After(time.Minute * time.Duration(seedTime))
|
||||
err := t.tool.Remove(t)
|
||||
if err != nil {
|
||||
log.Errorln(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
package transmission
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/hekmon/transmissionrpc/v3"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Transmission struct {
|
||||
client *transmissionrpc.Client
|
||||
}
|
||||
|
||||
func (t *Transmission) Run(task *tool.DownloadTask) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (t *Transmission) Name() string {
|
||||
return "transmission"
|
||||
}
|
||||
|
||||
func (t *Transmission) Items() []model.SettingItem {
|
||||
// transmission settings
|
||||
return []model.SettingItem{
|
||||
{Key: conf.TransmissionUri, Value: "http://localhost:9091/transmission/rpc", Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
{Key: conf.TransmissionSeedtime, Value: "0", Type: conf.TypeNumber, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transmission) Init() (string, error) {
|
||||
t.client = nil
|
||||
uri := setting.GetStr(conf.TransmissionUri)
|
||||
endpoint, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to init transmission client")
|
||||
}
|
||||
c, err := transmissionrpc.New(endpoint, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to init transmission client")
|
||||
}
|
||||
|
||||
ok, serverVersion, serverMinimumVersion, err := c.RPCVersion(context.Background())
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed get transmission version")
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return "", fmt.Errorf("remote transmission RPC version (v%d) is incompatible with the transmission library (v%d): remote needs at least v%d",
|
||||
serverVersion, transmissionrpc.RPCVersion, serverMinimumVersion)
|
||||
}
|
||||
|
||||
t.client = c
|
||||
log.Infof("remote transmission RPC version (v%d) is compatible with our transmissionrpc library (v%d)\n",
|
||||
serverVersion, transmissionrpc.RPCVersion)
|
||||
log.Infof("using transmission version: %d", serverVersion)
|
||||
return fmt.Sprintf("transmission version: %d", serverVersion), nil
|
||||
}
|
||||
|
||||
func (t *Transmission) IsReady() bool {
|
||||
return t.client != nil
|
||||
}
|
||||
|
||||
func (t *Transmission) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||
endpoint, err := url.Parse(args.Url)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse transmission uri")
|
||||
}
|
||||
|
||||
rpcPayload := transmissionrpc.TorrentAddPayload{
|
||||
DownloadDir: &args.TempDir,
|
||||
}
|
||||
// http url for .torrent file
|
||||
if endpoint.Scheme == "http" || endpoint.Scheme == "https" {
|
||||
resp, err := http.Get(args.Url)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get .torrent file")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, buffer)
|
||||
// Stream file to the encoder
|
||||
if _, err = io.Copy(encoder, resp.Body); err != nil {
|
||||
return "", errors.Wrap(err, "can't copy file content into the base64 encoder")
|
||||
}
|
||||
// Flush last bytes
|
||||
if err = encoder.Close(); err != nil {
|
||||
return "", errors.Wrap(err, "can't flush last bytes of the base64 encoder")
|
||||
}
|
||||
// Get the string form
|
||||
b64 := buffer.String()
|
||||
rpcPayload.MetaInfo = &b64
|
||||
} else { // magnet uri
|
||||
rpcPayload.Filename = &args.Url
|
||||
}
|
||||
|
||||
torrent, err := t.client.TorrentAdd(context.TODO(), rpcPayload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if torrent.ID == nil {
|
||||
return "", fmt.Errorf("failed get torrent ID")
|
||||
}
|
||||
gid := strconv.FormatInt(*torrent.ID, 10)
|
||||
return gid, nil
|
||||
}
|
||||
|
||||
func (t *Transmission) Remove(task *tool.DownloadTask) error {
|
||||
gid, err := strconv.ParseInt(task.GID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.client.TorrentRemove(context.TODO(), transmissionrpc.TorrentRemovePayload{
|
||||
IDs: []int64{gid},
|
||||
DeleteLocalData: false,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Transmission) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||
gid, err := strconv.ParseInt(task.GID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos, err := t.client.TorrentGetAllFor(context.TODO(), []int64{gid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(infos) < 1 {
|
||||
return nil, fmt.Errorf("failed get status, wrong gid: %s", task.GID)
|
||||
}
|
||||
info := infos[0]
|
||||
|
||||
s := &tool.Status{
|
||||
Completed: *info.IsFinished,
|
||||
Err: err,
|
||||
}
|
||||
s.Progress = *info.PercentDone * 100
|
||||
|
||||
switch *info.Status {
|
||||
case transmissionrpc.TorrentStatusCheckWait,
|
||||
transmissionrpc.TorrentStatusDownloadWait,
|
||||
transmissionrpc.TorrentStatusCheck,
|
||||
transmissionrpc.TorrentStatusDownload,
|
||||
transmissionrpc.TorrentStatusIsolated:
|
||||
s.Status = "[transmission] " + info.Status.String()
|
||||
case transmissionrpc.TorrentStatusSeedWait,
|
||||
transmissionrpc.TorrentStatusSeed:
|
||||
s.Completed = true
|
||||
case transmissionrpc.TorrentStatusStopped:
|
||||
s.Err = errors.Errorf("[transmission] failed to download %s, status: %s, error: %s", task.GID, info.Status.String(), *info.ErrorString)
|
||||
default:
|
||||
s.Err = errors.Errorf("[transmission] unknown status occurred downloading %s, err: %s", task.GID, *info.ErrorString)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var _ tool.Tool = (*Transmission)(nil)
|
||||
|
||||
func init() {
|
||||
tool.Tools.Add(&Transmission{})
|
||||
}
|
|
@ -30,6 +30,10 @@ func SetAria2(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
_tool, err := tool.Tools.Get("aria2")
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
version, err := _tool.Init()
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
|
@ -74,6 +78,37 @@ func OfflineDownloadTools(c *gin.Context) {
|
|||
common.SuccessResp(c, tools)
|
||||
}
|
||||
|
||||
type SetTransmissionReq struct {
|
||||
Uri string `json:"uri" form:"uri"`
|
||||
Seedtime string `json:"seedtime" form:"seedtime"`
|
||||
}
|
||||
|
||||
func SetTransmission(c *gin.Context) {
|
||||
var req SetTransmissionReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
items := []model.SettingItem{
|
||||
{Key: conf.TransmissionUri, Value: req.Uri, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
{Key: conf.TransmissionSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
}
|
||||
if err := op.SaveSettingItems(items); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
_tool, err := tool.Tools.Get("transmission")
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if _, err := _tool.Init(); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, "ok")
|
||||
}
|
||||
|
||||
type AddOfflineDownloadReq struct {
|
||||
Urls []string `json:"urls"`
|
||||
Path string `json:"path"`
|
||||
|
|
|
@ -62,7 +62,7 @@ func Init(e *gin.Engine) {
|
|||
api.GET("/auth/get_sso_id", handles.SSOLoginCallback)
|
||||
api.GET("/auth/sso_get_token", handles.SSOLoginCallback)
|
||||
|
||||
//webauthn
|
||||
// webauthn
|
||||
webauthn.GET("/webauthn_begin_registration", handles.BeginAuthnRegistration)
|
||||
webauthn.POST("/webauthn_finish_registration", handles.FinishAuthnRegistration)
|
||||
webauthn.GET("/webauthn_begin_login", handles.BeginAuthnLogin)
|
||||
|
@ -125,6 +125,7 @@ func admin(g *gin.RouterGroup) {
|
|||
setting.POST("/reset_token", handles.ResetToken)
|
||||
setting.POST("/set_aria2", handles.SetAria2)
|
||||
setting.POST("/set_qbit", handles.SetQbittorrent)
|
||||
setting.POST("/set_transmission", handles.SetTransmission)
|
||||
|
||||
task := g.Group("/task")
|
||||
handles.SetupTaskRoute(task)
|
||||
|
@ -159,14 +160,15 @@ func _fs(g *gin.RouterGroup) {
|
|||
g.PUT("/put", middlewares.FsUp, handles.FsStream)
|
||||
g.PUT("/form", middlewares.FsUp, handles.FsForm)
|
||||
g.POST("/link", middlewares.AuthAdmin, handles.Link)
|
||||
//g.POST("/add_aria2", handles.AddOfflineDownload)
|
||||
//g.POST("/add_qbit", handles.AddQbittorrent)
|
||||
// g.POST("/add_aria2", handles.AddOfflineDownload)
|
||||
// g.POST("/add_qbit", handles.AddQbittorrent)
|
||||
// g.POST("/add_transmission", handles.SetTransmission)
|
||||
g.POST("/add_offline_download", handles.AddOfflineDownload)
|
||||
}
|
||||
|
||||
func Cors(r *gin.Engine) {
|
||||
config := cors.DefaultConfig()
|
||||
//config.AllowAllOrigins = true
|
||||
// config.AllowAllOrigins = true
|
||||
config.AllowOrigins = conf.Conf.Cors.AllowOrigins
|
||||
config.AllowHeaders = conf.Conf.Cors.AllowHeaders
|
||||
config.AllowMethods = conf.Conf.Cors.AllowMethods
|
||||
|
|
Loading…
Reference in New Issue