diff --git a/drivers/github_releases/driver.go b/drivers/github_releases/driver.go index 92d94b4dcf..6e074c64bc 100644 --- a/drivers/github_releases/driver.go +++ b/drivers/github_releases/driver.go @@ -45,134 +45,117 @@ func (d *GithubReleases) List(ctx context.Context, dir model.Obj, args model.Lis for i := range d.points { point := &d.points[i] - if !d.Addition.ShowAllVersion { // latest - err := point.RequestRelease(d.GetRequest, args.Refresh) + if !d.Addition.ShowAllVersion { + // latest version mode + release, err := d.getLatestRelease(point.Repo) if err != nil { log.Warnf("failed to request release for %s: %v", point.Repo, err) + continue + } + if release == nil { + continue } - if point.Point == path { // 与仓库路径相同 - if point.Release == nil { - if err != nil { - return nil, fmt.Errorf("failed to get release for %s: %w", point.Repo, err) - } - return nil, fmt.Errorf("failed to get release for %s: unknown error", point.Repo) - } - files = append(files, point.GetLatestRelease()...) + if point.Point == path { + // 当前目录就是仓库挂载点 + files = append(files, releaseToFiles(point.Point, release)...) if d.Addition.ShowReadme { - otherFiles, err := point.GetOtherFile(d.GetRequest, args.Refresh) - if err != nil { - return nil, fmt.Errorf("failed to get other files for %s: %w", point.Repo, err) + other, err := d.fetchRepoFiles(point.Repo) + if err == nil { + files = append(files, otherFiles(point.Point, other)...) + } else { + log.Warnf("failed to get other files for %s: %v", point.Repo, err) } - files = append(files, otherFiles...) } if d.Addition.ShowSourceCode { - files = append(files, point.GetSourceCode()...) + files = append(files, sourceCodeFiles(point.Point, release)...) } - } else if strings.HasPrefix(point.Point, path) { // 仓库目录的父目录 + } else if strings.HasPrefix(point.Point, path) { + // 仓库目录的父目录,需要聚合显示 nextDir := GetNextDir(point.Point, path) if nextDir == "" { continue } - if err != nil { - return nil, fmt.Errorf("failed to get release for %s: %w", point.Repo, err) - } hasSameDir := false for index := range files { if files[index].GetName() == nextDir { hasSameDir = true - files[index].Size += point.GetLatestSize() + files[index].Size += releaseSize(release) break } } if !hasSameDir { - var updateAt, createAt string - if point.Release != nil { - updateAt = point.Release.PublishedAt - createAt = point.Release.CreatedAt - } files = append(files, File{ Path: stdpath.Join(path, nextDir), FileName: nextDir, - Size: point.GetLatestSize(), - UpdateAt: updateAt, - CreateAt: createAt, + Size: releaseSize(release), + UpdateAt: release.PublishedAt, + CreateAt: release.CreatedAt, Type: "dir", Url: "", }) } } - } else { // all version - err := point.RequestReleases(d.GetRequest, args.Refresh) + } else { + // all versions mode + releases, err := d.getAllReleases(point.Repo) if err != nil { log.Warnf("failed to request releases for %s: %v", point.Repo, err) + continue + } + if len(releases) == 0 { + continue } - if point.Point == path { // 与仓库路径相同 - if point.Releases == nil { - if err != nil { - return nil, fmt.Errorf("failed to get releases for %s: %w", point.Repo, err) - } - return nil, fmt.Errorf("failed to get releases for %s: unknown error", point.Repo) - } - files = append(files, point.GetAllVersion()...) + if point.Point == path { + // 当前目录就是仓库挂载点 + files = append(files, releasesToVersionDirs(point.Point, releases)...) if d.Addition.ShowReadme { - otherFiles, err := point.GetOtherFile(d.GetRequest, args.Refresh) - if err != nil { - return nil, fmt.Errorf("failed to get other files for %s: %w", point.Repo, err) + other, err := d.fetchRepoFiles(point.Repo) + if err == nil { + files = append(files, otherFiles(point.Point, other)...) + } else { + log.Warnf("failed to get other files for %s: %v", point.Repo, err) } - files = append(files, otherFiles...) } - } else if strings.HasPrefix(point.Point, path) { // 仓库目录的父目录 + } else if strings.HasPrefix(point.Point, path) { + // 仓库目录的父目录 nextDir := GetNextDir(point.Point, path) if nextDir == "" { continue } - if err != nil { - return nil, fmt.Errorf("failed to get releases for %s: %w", point.Repo, err) - } hasSameDir := false for index := range files { if files[index].GetName() == nextDir { hasSameDir = true - files[index].Size += point.GetAllVersionSize() + files[index].Size += releasesTotalSize(releases) break } } if !hasSameDir { - var updateAt, createAt string - if point.Releases != nil && len(*point.Releases) > 0 { - updateAt = (*point.Releases)[0].PublishedAt - createAt = (*point.Releases)[0].CreatedAt - } files = append(files, File{ FileName: nextDir, Path: stdpath.Join(path, nextDir), - Size: point.GetAllVersionSize(), - UpdateAt: updateAt, - CreateAt: createAt, + Size: releasesTotalSize(releases), + UpdateAt: releases[0].PublishedAt, + CreateAt: releases[0].CreatedAt, Type: "dir", Url: "", }) } - } else if strings.HasPrefix(path, point.Point) { // 仓库目录的子目录 + } else if strings.HasPrefix(path, point.Point) { + // 仓库目录的子目录(某个版本) tagName := GetNextDir(path, point.Point) if tagName == "" { continue } - if point.Releases == nil { - if err != nil { - return nil, fmt.Errorf("failed to get releases for %s: %w", point.Repo, err) - } - return nil, fmt.Errorf("failed to get releases for %s: unknown error", point.Repo) - } - files = append(files, point.GetReleaseByTagName(tagName)...) + files = append(files, releaseAssetsByTag(point.Point, tagName, releases)...) if d.Addition.ShowSourceCode { - files = append(files, point.GetSourceCodeByTagName(tagName)...) + files = append(files, sourceCodeFilesByTag(point.Point, releases, tagName)...) } } } diff --git a/drivers/github_releases/meta.go b/drivers/github_releases/meta.go index fc5fad6953..36824db782 100644 --- a/drivers/github_releases/meta.go +++ b/drivers/github_releases/meta.go @@ -12,6 +12,8 @@ type Addition struct { Token string `json:"token" type:"string" required:"false" help:"GitHub token, if you want to access private repositories or increase the rate limit"` ShowSourceCode bool `json:"show_source_code" type:"bool" default:"false" help:"show Source code (zip/tar.gz)"` ShowAllVersion bool `json:"show_all_version" type:"bool" default:"false" help:"show all versions"` + PerPage int `json:"per_page" type:"number" default:"30" help:"releases per page (max 100), only works when show all versions"` + MaxPage int `json:"max_page" type:"number" default:"0" help:"max pages to fetch (0 = unlimited), only works when show all versions"` GitHubProxy string `json:"gh_proxy" type:"string" default:"" help:"GitHub proxy, e.g. https://ghproxy.net/github.com or https://gh-proxy.com/github.com "` } diff --git a/drivers/github_releases/types.go b/drivers/github_releases/types.go index abc024cc8d..07580e14d5 100644 --- a/drivers/github_releases/types.go +++ b/drivers/github_releases/types.go @@ -1,76 +1,27 @@ package github_releases import ( - "encoding/json" "path" - "strings" "time" "github.com/OpenListTeam/OpenList/v4/pkg/utils" - "github.com/go-resty/resty/v2" ) +// MountPoint 表示一个仓库挂载点 type MountPoint struct { - Point string // 挂载点 - Repo string // 仓库名 owner/repo - Release *Release // Release 指针 latest - Releases *[]Release // []Release 指针 - OtherFile *[]FileInfo // 仓库根目录下的其他文件 + Point string // 挂载点路径 + Repo string // 仓库名 owner/repo } -// 请求最新版本 -func (m *MountPoint) RequestRelease(get func(url string) (*resty.Response, error), refresh bool) error { - if m.Repo == "" { +// Release 转为 File 列表 +func releaseToFiles(point string, release *Release) []File { + if release == nil { return nil } - - if m.Release == nil || refresh { - resp, err := get("https://api.github.com/repos/" + m.Repo + "/releases/latest") - if err != nil { - m.Release = nil - return err - } - release := new(Release) - if err := json.Unmarshal(resp.Body(), release); err != nil { - m.Release = nil - return err - } - m.Release = release - } - return nil -} - -// 请求所有版本 -func (m *MountPoint) RequestReleases(get func(url string) (*resty.Response, error), refresh bool) error { - if m.Repo == "" { - return nil - } - - if m.Releases == nil || refresh { - resp, err := get("https://api.github.com/repos/" + m.Repo + "/releases") - if err != nil { - m.Releases = nil - return err - } - releases := new([]Release) - if err := json.Unmarshal(resp.Body(), releases); err != nil { - m.Releases = nil - return err - } - m.Releases = releases - } - return nil -} - -// 获取最新版本 -func (m *MountPoint) GetLatestRelease() []File { - if m.Release == nil { - return nil - } - files := make([]File, 0, len(m.Release.Assets)) - for _, asset := range m.Release.Assets { + files := make([]File, 0, len(release.Assets)) + for _, asset := range release.Assets { files = append(files, File{ - Path: path.Join(m.Point, asset.Name), + Path: path.Join(point, asset.Name), FileName: asset.Name, Size: asset.Size, Type: "file", @@ -82,53 +33,43 @@ func (m *MountPoint) GetLatestRelease() []File { return files } -// 获取最新版本大小 -func (m *MountPoint) GetLatestSize() int64 { - if m.Release == nil { +// 计算 release 的 asset 总大小 +func releaseSize(release *Release) int64 { + if release == nil { return 0 } size := int64(0) - for _, asset := range m.Release.Assets { + for _, asset := range release.Assets { size += asset.Size } return size } -// 获取所有版本 -func (m *MountPoint) GetAllVersion() []File { - if m.Releases == nil { - return nil - } - files := make([]File, 0) - for _, release := range *m.Releases { - file := File{ - Path: path.Join(m.Point, release.TagName), +// Releases 列表转为版本目录 File 列表 +func releasesToVersionDirs(point string, releases []Release) []File { + files := make([]File, 0, len(releases)) + for _, release := range releases { + files = append(files, File{ + Path: path.Join(point, release.TagName), FileName: release.TagName, - Size: m.GetSizeByTagName(release.TagName), + Size: releaseSize(&release), Type: "dir", UpdateAt: release.PublishedAt, CreateAt: release.CreatedAt, Url: release.HtmlUrl, - } - for _, asset := range release.Assets { - file.Size += asset.Size - } - files = append(files, file) + }) } return files } -// 根据版本号获取版本 -func (m *MountPoint) GetReleaseByTagName(tagName string) []File { - if m.Releases == nil { - return nil - } - for _, item := range *m.Releases { +// 根据 tagName 查找 release 的 asset 文件列表 +func releaseAssetsByTag(point, tagName string, releases []Release) []File { + for _, item := range releases { if item.TagName == tagName { - files := make([]File, 0) + files := make([]File, 0, len(item.Assets)) for _, asset := range item.Assets { files = append(files, File{ - Path: path.Join(m.Point, tagName, asset.Name), + Path: path.Join(point, tagName, asset.Name), FileName: asset.Name, Size: asset.Size, Type: "file", @@ -143,118 +84,73 @@ func (m *MountPoint) GetReleaseByTagName(tagName string) []File { return nil } -// 根据版本号获取版本大小 -func (m *MountPoint) GetSizeByTagName(tagName string) int64 { - if m.Releases == nil { - return 0 - } - for _, item := range *m.Releases { +// 根据 tagName 计算 asset 总大小 +func releasesSizeByTag(releases []Release, tagName string) int64 { + for _, item := range releases { if item.TagName == tagName { - size := int64(0) - for _, asset := range item.Assets { - size += asset.Size - } - return size + return releaseSize(&item) } } return 0 } -// 获取所有版本大小 -func (m *MountPoint) GetAllVersionSize() int64 { - if m.Releases == nil { - return 0 - } +// 计算所有 releases 的 asset 总大小 +func releasesTotalSize(releases []Release) int64 { size := int64(0) - for _, release := range *m.Releases { - for _, asset := range release.Assets { - size += asset.Size - } + for _, release := range releases { + size += releaseSize(&release) } return size } -func (m *MountPoint) GetSourceCode() []File { - if m.Release == nil { +// Source code 文件 +func sourceCodeFiles(point string, release *Release) []File { + if release == nil { return nil } - files := make([]File, 0) - - // 无法获取文件大小,此处设为 1 - files = append(files, File{ - Path: path.Join(m.Point, "Source code (zip)"), - FileName: "Source code (zip)", - Size: 1, - Type: "file", - UpdateAt: m.Release.CreatedAt, - CreateAt: m.Release.CreatedAt, - Url: m.Release.ZipballUrl, - }) - files = append(files, File{ - Path: path.Join(m.Point, "Source code (tar.gz)"), - FileName: "Source code (tar.gz)", - Size: 1, - Type: "file", - UpdateAt: m.Release.CreatedAt, - CreateAt: m.Release.CreatedAt, - Url: m.Release.TarballUrl, - }) - - return files + return []File{ + { + Path: path.Join(point, "Source code (zip)"), + FileName: "Source code (zip)", + Size: 1, + Type: "file", + UpdateAt: release.CreatedAt, + CreateAt: release.CreatedAt, + Url: release.ZipballUrl, + }, + { + Path: path.Join(point, "Source code (tar.gz)"), + FileName: "Source code (tar.gz)", + Size: 1, + Type: "file", + UpdateAt: release.CreatedAt, + CreateAt: release.CreatedAt, + Url: release.TarballUrl, + }, + } } -func (m *MountPoint) GetSourceCodeByTagName(tagName string) []File { - if m.Releases == nil { - return nil - } - for _, item := range *m.Releases { +// 根据 tagName 获取 Source Code 文件 +func sourceCodeFilesByTag(point string, releases []Release, tagName string) []File { + for _, item := range releases { if item.TagName == tagName { - files := make([]File, 0) - files = append(files, File{ - Path: path.Join(m.Point, "Source code (zip)"), - FileName: "Source code (zip)", - Size: 1, - Type: "file", - UpdateAt: item.CreatedAt, - CreateAt: item.CreatedAt, - Url: item.ZipballUrl, - }) - files = append(files, File{ - Path: path.Join(m.Point, "Source code (tar.gz)"), - FileName: "Source code (tar.gz)", - Size: 1, - Type: "file", - UpdateAt: item.CreatedAt, - CreateAt: item.CreatedAt, - Url: item.TarballUrl, - }) - return files + return sourceCodeFiles(point, &item) } } return nil } -func (m *MountPoint) GetOtherFile(get func(url string) (*resty.Response, error), refresh bool) ([]File, error) { - if m.OtherFile == nil || refresh { - resp, err := get("https://api.github.com/repos/" + m.Repo + "/contents") - if err != nil { - m.OtherFile = nil - return nil, err - } - otherFile := new([]FileInfo) - if err := json.Unmarshal(resp.Body(), otherFile); err != nil { - m.OtherFile = nil - return nil, err - } - m.OtherFile = otherFile - } - +// 仓库根目录下的 README/LICENSE 文件 +func otherFiles(point string, fileInfos []FileInfo) []File { files := make([]File, 0) defaultTime := "1970-01-01T00:00:00Z" - for _, file := range *m.OtherFile { - if strings.HasSuffix(file.Name, ".md") || strings.HasPrefix(file.Name, "LICENSE") { + for _, file := range fileInfos { + if file.Type == "dir" { + continue + } + if file.Name == "README.md" || file.Name == "LICENSE" || file.Name == "LICENSE.txt" || file.Name == "LICENSE.md" { files = append(files, File{ - Path: path.Join(m.Point, file.Name), + Path: path.Join(point, file.Name), FileName: file.Name, Size: file.Size, Type: "file", @@ -264,7 +160,7 @@ func (m *MountPoint) GetOtherFile(get func(url string) (*resty.Response, error), }) } } - return files, nil + return files } type File struct { diff --git a/drivers/github_releases/util.go b/drivers/github_releases/util.go index a6401e6cd7..cbaafb08b2 100644 --- a/drivers/github_releases/util.go +++ b/drivers/github_releases/util.go @@ -1,8 +1,8 @@ package github_releases import ( + "encoding/json" "fmt" - "path/filepath" "strings" "github.com/OpenListTeam/OpenList/v4/drivers/base" @@ -49,10 +49,8 @@ func (d *GithubReleases) ParseRepos(text string) ([]MountPoint, error) { } points = append(points, MountPoint{ - Point: path, - Repo: repo, - Release: nil, - Releases: nil, + Point: path, + Repo: repo, }) } d.points = points @@ -76,9 +74,79 @@ func GetNextDir(wholePath string, basePath string) string { return "" } -// 判断当前目录是否是目标目录的祖先目录 -func IsAncestorDir(parentDir string, targetDir string) bool { - absTargetDir, _ := filepath.Abs(targetDir) - absParentDir, _ := filepath.Abs(parentDir) - return strings.HasPrefix(absTargetDir, absParentDir) +// getLatestRelease 获取最新 release +func (d *GithubReleases) getLatestRelease(repo string) (*Release, error) { + resp, err := d.GetRequest("https://api.github.com/repos/" + repo + "/releases/latest") + if err != nil { + return nil, err + } + release := new(Release) + if err := json.Unmarshal(resp.Body(), release); err != nil { + return nil, err + } + return release, nil +} + +// getAllReleases 获取所有 releases(支持自动翻页) +func (d *GithubReleases) getAllReleases(repo string) ([]Release, error) { + perPage := d.Addition.PerPage + if perPage < 1 { + perPage = 30 + } else if perPage > 100 { + perPage = 100 + } + + maxPage := d.Addition.MaxPage + if maxPage < 0 { + maxPage = 0 + } + + allReleases := make([]Release, 0) + page := 1 + + for { + url := fmt.Sprintf("https://api.github.com/repos/%s/releases?per_page=%d&page=%d", repo, perPage, page) + resp, err := d.GetRequest(url) + if err != nil { + return nil, err + } + + releases := make([]Release, 0) + if err := json.Unmarshal(resp.Body(), &releases); err != nil { + return nil, err + } + + if len(releases) == 0 { + break + } + + allReleases = append(allReleases, releases...) + + // 达到最大页数限制 + if maxPage > 0 && page >= maxPage { + break + } + + // 如果返回数量小于 perPage,说明是最后一页 + if len(releases) < perPage { + break + } + + page++ + } + + return allReleases, nil +} + +// fetchRepoFiles 获取仓库根目录文件列表 +func (d *GithubReleases) fetchRepoFiles(repo string) ([]FileInfo, error) { + resp, err := d.GetRequest("https://api.github.com/repos/" + repo + "/contents") + if err != nil { + return nil, err + } + files := make([]FileInfo, 0) + if err := json.Unmarshal(resp.Body(), &files); err != nil { + return nil, err + } + return files, nil }