Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions internal/app/downloader/github_api.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package downloader

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path/filepath"
"reflect"
"regexp"
"strings"

Expand Down Expand Up @@ -49,16 +51,22 @@ func (g *gitHubAPIDownloader) Download() error {
}
}

if err := util.EnsureDir(g.opts.OutputDir); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}

if !g.opts.Quiet {
fmt.Printf("Downloading directory %s from %s/%s (branch: %s)...\n",
g.opts.Subdir, owner, repo, g.opts.Branch)
}

return g.downloadDirectory(owner, repo, g.opts.Subdir, g.opts.OutputDir)
//check if the item is a file or directory
content, isFile, err := g.getContents(owner, repo, g.opts.Subdir)
if err != nil {
return err
}

if isFile == true {
return g.downloadFile(content.(GitHubContentItem).DownloadURL, g.opts.OutputDir)
} else {
return g.downloadDirectory(content.([]GitHubContentItem), g.opts.OutputDir)
}
}

func parseGitHubURL(repoURL string) (owner string, repo string, err error) {
Expand All @@ -77,21 +85,21 @@ func parseGitHubURL(repoURL string) (owner string, repo string, err error) {
return "", "", fmt.Errorf("URL does not match GitHub repository pattern: %s", repoURL)
}

func (g *gitHubAPIDownloader) downloadDirectory(owner, repo, path, outputDir string) error {
items, err := g.getContents(owner, repo, path)
if err != nil {
return err
func (g *gitHubAPIDownloader) downloadDirectory(items []GitHubContentItem, outputDir string) error {
if err := util.EnsureDir(g.opts.OutputDir); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}

for _, item := range items {
targetPath := filepath.Join(outputDir, item.Name)

if item.Type == "dir" {
//download sub directories
if err := util.EnsureDir(targetPath); err != nil {
return fmt.Errorf("failed to create directory %s: %w", targetPath, err)
}

if err := g.downloadDirectory(owner, repo, item.Path, targetPath); err != nil {
if err := g.downloadDirectory(items, targetPath); err != nil {
return err
}
} else if item.Type == "file" {
Expand All @@ -107,7 +115,7 @@ func (g *gitHubAPIDownloader) downloadDirectory(owner, repo, path, outputDir str
return nil
}

func (g *gitHubAPIDownloader) getContents(owner, repo, path string) ([]GitHubContentItem, error) {
func (g *gitHubAPIDownloader) getContents(owner, repo, path string) (any, bool, error) {
apiURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s",
GitHubAPIBaseURL, owner, repo, url.PathEscape(path))

Expand All @@ -117,12 +125,11 @@ func (g *gitHubAPIDownloader) getContents(owner, repo, path string) ([]GitHubCon

req, err := util.NewGitHubRequest("GET", apiURL, g.opts.Token)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
return nil, false, fmt.Errorf("failed to create request: %w", err)
}

resp, err := g.client.Do(req)
if err != nil {
return nil, &errors.AppError{
return nil, false, &errors.AppError{
Err: errors.ErrNetworkFailure,
Message: "Failed to connect to GitHub API",
Hint: "Check your internet connection and try again",
Expand All @@ -133,19 +140,26 @@ func (g *gitHubAPIDownloader) getContents(owner, repo, path string) ([]GitHubCon
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
bodyStr := strings.TrimSpace(string(body))
return nil, errors.ParseGitHubAPIError(resp.StatusCode, bodyStr)
return nil, false, errors.ParseGitHubAPIError(resp.StatusCode, bodyStr)
}

//debug: this code below was rejecting downloading files
var items []GitHubContentItem
if err := json.NewDecoder(resp.Body).Decode(&items); err != nil {
var item GitHubContentItem
if errSingle := json.Unmarshal([]byte(err.Error()), &item); errSingle == nil {
return []GitHubContentItem{item}, nil
}
return nil, fmt.Errorf("failed to parse API response: %w", err)
var singleItem GitHubContentItem
var contentJSON any
var contentBytes bytes.Buffer

json.NewDecoder(resp.Body).Decode(&contentJSON)
json.NewEncoder(&contentBytes).Encode(contentJSON)
if reflect.TypeOf(contentJSON) == reflect.TypeFor[[]any]() {
json.Unmarshal(contentBytes.Bytes(), &items)
return items, false, nil
} else if reflect.TypeOf(contentJSON) == reflect.TypeFor[map[string]any]() {
json.Unmarshal(contentBytes.Bytes(), &singleItem)
return singleItem, true, nil
}

return items, nil
return items, false, nil
}

func (g *gitHubAPIDownloader) downloadFile(url, outputPath string) error {
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Arguments:
} else {
outputDir = filepath.Base(folderPath)
}
// Normalize folder path to use forward slashes
folderPath = strings.ReplaceAll(folderPath, "\\", "/")

if provider == "" {
if strings.Contains(repoURL, "github.com") {
Expand Down Expand Up @@ -96,7 +98,6 @@ Arguments:
}

err := app.Download(opts)

var appErr *apperrors.AppError
if errors.As(err, &appErr) {
cmd.SilenceUsage = true
Expand Down