Skip to content
1 change: 1 addition & 0 deletions drivers/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
_ "github.com/alist-org/alist/v3/drivers/febbox"
_ "github.com/alist-org/alist/v3/drivers/ftp"
_ "github.com/alist-org/alist/v3/drivers/github"
_ "github.com/alist-org/alist/v3/drivers/github_release"
_ "github.com/alist-org/alist/v3/drivers/google_drive"
_ "github.com/alist-org/alist/v3/drivers/google_photo"
_ "github.com/alist-org/alist/v3/drivers/halalcloud"
Expand Down
45 changes: 45 additions & 0 deletions drivers/github_release/backoff.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package template

import (
"math/rand"
"time"
)

const (
initialRetryInterval = 500 * time.Millisecond
maxInterval = 1 * time.Minute
maxElapsedTime = 15 * time.Minute
randomizationFactor = 0.5
multiplier = 1.5
)

// Backoff 提供了确定在重试操作之前等待的时间算法
type Backoff struct {
interval time.Duration
elapsedTime time.Duration
}

// Pause 返回重试操作之前等待的时间量,如果可以再次尝试则返回 true,否则返回 false,表示操作应该被放弃。
func (b *Backoff) Pause() (time.Duration, bool) {
if b.interval == 0 {
// first time
b.interval = initialRetryInterval
b.elapsedTime = 0
}

// interval from [1 - randomizationFactor, 1 + randomizationFactor)
randomizedInterval := time.Duration((rand.Float64()*(2*randomizationFactor) + (1 - randomizationFactor)) * float64(b.interval))
b.elapsedTime += randomizedInterval

if b.elapsedTime > maxElapsedTime {
return 0, false
}

// 将间隔增加到间隔上限
b.interval = time.Duration(float64(b.interval) * multiplier)
if b.interval > maxInterval {
b.interval = maxInterval
}

return randomizedInterval, true
}
37 changes: 37 additions & 0 deletions drivers/github_release/backoff_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package template

import (
"testing"
"time"
)

func TestBackoffMultiple(t *testing.T) {
b := &Backoff{}
for i := 0; i < 19; i++ {
p, ok := b.Pause()
t.Logf("iteration %d pausing for %s", i, p)
if !ok {
t.Fatalf("hit the pause timeout after %d pauses", i)
}
}
}

func TestBackoffTimeout(t *testing.T) {
var elapsed time.Duration
b := &Backoff{}
for i := 0; i < 40; i++ {
p, ok := b.Pause()
elapsed += p
t.Logf("iteration %d pausing for %s (total %s)", i, p, elapsed)
if !ok {
break
}
}
if _, ok := b.Pause(); ok {
t.Fatalf("did not hit the pause timeout")
}

if elapsed > maxElapsedTime {
t.Fatalf("waited too long: %s > %s", elapsed, maxElapsedTime)
}
}
168 changes: 168 additions & 0 deletions drivers/github_release/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package template

import (
"context"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/pkg/errors"
)

type GithubRelease struct {
model.Storage
Addition

api *ApiContext
repo repository
}

func (d *GithubRelease) Config() driver.Config {
return config
}

func (d *GithubRelease) GetAddition() driver.Additional {
return &d.Addition
}

func (d *GithubRelease) Init(ctx context.Context) error {
token := d.Addition.Token
if token == "" {
return errs.EmptyToken
}

if d.Addition.MaxReleases < 1 {
return errors.New("max_releases must be greater than 0")
}

if d.Addition.MaxReleases > 100 {
d.Addition.MaxReleases = 100
}

d.api = NewApiContext(token, nil)

repo, err := newRepository(d.Addition.Repo)
if err != nil {
return err
}
d.repo = repo

return nil
}

// Drop Delete this driver
func (d *GithubRelease) Drop(ctx context.Context) error {
return nil
}

func (d *GithubRelease) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
repo, err := newRepository(d.Addition.Repo)
if err != nil {
return nil, err
}

// 判断 dir 是不是挂在点。如果 dir 是挂载点,则返回所有的 release;
// 如果 dir 不是挂载点,则返回 dir 下的 release。
if dir.GetPath() == "" {
releases, err := d.api.GetReleases(repo, d.Addition.MaxReleases)
if err != nil {
return nil, err
}
return releases, nil
}

idStr := dir.GetID()
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "list release %s failed, id is not a number", idStr)
}

release, err := d.api.GetRelease(repo, id)
if err != nil {
return nil, err
}

return release.Children()
}

func (d *GithubRelease) proxyDownload(file model.Obj, args model.LinkArgs) bool {
if d.Config().MustProxy() || d.GetStorage().WebProxy {
return true
}

req := args.HttpReq
if args.HttpReq != nil &&
req.URL != nil &&
strings.HasPrefix(req.URL.Path, fmt.Sprintf("/p%s", d.GetStorage().MountPath)) {
return true
}

return false
}

func (d *GithubRelease) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
idStr := file.GetID()
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return nil, errors.Wrapf(err, "get link of file %s failed, id is not a number", idStr)
}
asset, err := d.api.GetReleaseAsset(d.repo, id)
if err != nil {
return nil, err
}

if d.proxyDownload(file, args) {

header := http.Header{
"User-Agent": {"Alist/" + conf.VERSION},
"Accept": {"application/octet-stream"},
}
d.api.SetAuthHeader(header)

return &model.Link{
URL: asset.URL,
Header: header,
}, nil
}

return &model.Link{
URL: asset.BrowserDownloadURL,
}, nil

}

func (d *GithubRelease) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
return nil, errs.NotSupport
}

func (d *GithubRelease) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
return nil, errs.NotSupport
}

func (d *GithubRelease) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
// TODO rename obj, optional
return nil, errs.NotImplement
}

func (d *GithubRelease) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
return nil, errs.NotSupport
}

func (d *GithubRelease) Remove(ctx context.Context, obj model.Obj) error {
return errs.NotSupport
}

func (d *GithubRelease) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
return nil, errs.NotSupport
}

//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
// return nil, errs.NotSupport
//}

var _ driver.Driver = (*GithubRelease)(nil)
Loading