Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(project): add documents #2

Merged
merged 1 commit into from
Aug 8, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/build-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build Project
on:
pull_request:
branches:
- "main"
- "*"

jobs:
build-and-test:
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"errgroup",
"oget",
"oomol",
"Wrapf"
]
}
166 changes: 165 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,165 @@
# oget
# oget

oget is a Golang download library. It supports parallel downloads, resuming after failures, SHA512 verification, and download progress monitoring.

## Installation

```shell
$ go install github.com/oomol-lab/oget
```

## Quick Start

```go
import "github.com/oomol-lab/oget"

task, err := oget.CreateGettingTask(&oget.RemoteFile{
URL: "https://github.com/oomol-lab/oget/raw/main/tests/target.bin",
})
if err != nil {
panic(err)
}
_, err = task.Get(&oget.GettingConfig{
FilePath: "/path/to/save/file.bin",
})
if err != nil {
panic(err)
}
```

## Advanced Features

### Segmented Download

It splits large files into multiple smaller parts for parallel downloading, then merges them into one large file. Additionally, you can specify the temporary directory for storing the split files by adding the `PartsPath` field.

```go
import "github.com/oomol-lab/oget"

task, err := oget.CreateGettingTask(&oget.RemoteFile{
URL: "https://github.com/oomol-lab/oget/raw/main/tests/target.bin",
})
if err != nil {
panic(err)
}
_, err = task.Get(&oget.GettingConfig{
// Path to store the final file
FilePath: "/path/to/save/file.bin",
// Number of file parts for splitting and parallel downloading
Parts: 4,
// If not specified, defaults to the same directory as `FilePath`
PartsPath: "/path/to/save/temp/files",
})
if err != nil {
panic(err)
}
```

### Download Progress Monitoring

`ListenProgress` is not thread-safe and may be called in multiple threads. You need to manually lock it to ensure thread safety.

```go
import "github.com/oomol-lab/oget"

task, err := oget.CreateGettingTask(&oget.RemoteFile{
URL: "https://github.com/oomol-lab/oget/raw/main/tests/target.bin",
})
if err != nil {
panic(err)
}
var mux sync.Mutex
_, err = task.Get(&oget.GettingConfig{
FilePath: "/path/to/save/file.bin",
ListenProgress: func(event oget.ProgressEvent) {
// Callback may be invoked in multiple threads
// Use a lock to ensure thread safety
mux.Lock()
defer mux.Unlock()
switch event.phase {
case oget.ProgressPhaseDownloading:
// Progress of downloading from the network
case oget.ProgressPhaseCoping:
// Download complete, merging multiple file parts into one file
case oget.ProgressPhaseDone:
// All tasks are completed
}
// Number of bytes completed in this step
progress := event.Progress
// Total number of bytes in this step
total := event.Total
},
})
if err != nil {
panic(err)
}
```

### SHA512 Verification

After downloading, the library performs a SHA512 checksum on the entire file. If the checksum fails, an `oget.SHA512Error` is thrown.

```go
import "github.com/oomol-lab/oget"

task, err := oget.CreateGettingTask(&oget.RemoteFile{
URL: "https://github.com/oomol-lab/oget/raw/main/tests/target.bin",
})
if err != nil {
panic(err)
}
_, err = task.Get(&oget.GettingConfig{
FilePath: "/path/to/save/file.bin",
SHA512: "d286fbb1fab9014fdbc543d09f54cb93da6e0f2c809e62ee0c81d69e4bf58eec44571fae192a8da9bc772ce1340a0d51ad638cdba6118909b555a12b005f2930",
})
if err != nil {
if sha512Error, ok := err.(oget.SHA512Error); ok {
// Failed due to SHA512 verification failure
}
panic(err)
}
```

### Resuming Downloads

During a download, oget creates a temporary file with the extension `*.downloading` (regardless of whether it's split into parts). If a download fails due to network issues and the temporary file is not deleted, resuming the download will retain the progress from the previous attempt. To implement resuming downloads, ignore download failures caused by network issues and retry the download.

First, create a `task` object using `oget.CreateGettingTask`. This step only fetches file metadata from the server without starting the download. If this step fails, it should be considered a complete failure of the download task.

```go
import "github.com/oomol-lab/oget"

task, err := oget.CreateGettingTask(&oget.RemoteFile{
URL: "https://github.com/oomol-lab/oget/raw/main/tests/target.bin",
})
if err != nil {
panic(err)
}
```

Then, call `task.Get()` to initiate the download. Check if the error is of type `oget.SHA512Error`. If not, it is likely due to network issues and should be retried.

Note that the first return value of `task.Get()` is a function `clean` that deletes the temporary download files. Call it to free up disk space if you don't want to keep these files for the next download attempt after a download failure.

```go
success := false

for i := 0; i < 10; i++ {
clean, err = task.Get(&oget.GettingConfig{
FilePath: "/path/to/save/file.bin",
Parts: 4,
})
if err != nil {
if sha512Error, ok := err.(oget.SHA512Error); ok {
clean()
panic(sha512Error)
}
fmt.Printf("download failed with error and retry %s", err)
} else {
success = true
}
}
if !success {
panic("download fail")
}
```
42 changes: 32 additions & 10 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,42 @@ import (
)

type RemoteFile struct {
Context context.Context
Timeout time.Duration
URL string
Useragent string
Referer string
// the context for the http request.
// if the value is nil, the context will be context.Background().
Context context.Context
// the maximum amount of time a dial will wait for a connect or copy to complete.
// the default is 10 seconds.
Timeout time.Duration
// the URL of the file to download.
URL string
// the User-Agent header field value.
// if the value is empty, the User-Agent header will not be set.
Useragent string
// the Referer header field value.
// if the value is empty, the Referer header will not be set.
Referer string
// the maximum number of idle (keep-alive) connections to keep per-host.
// the default is 16.
MaxIdleConnsPerHost int
}

type GettingConfig struct {
FilePath string
SHA512 string
PartsPath string
PartName string
Parts int
// the path to save the downloaded file.
FilePath string
// the SHA512 code of the file.
// if the code is empty, the file will not be checked.
SHA512 string
// PartsPath is the path to save the temp files of downloaded parts.
// if the value is empty, the temp files will be saved in the same directory as the FilePath.
PartsPath string
// the name of the part file.
// if the value is empty, the name will be the same as the file name.
PartName string
// the number of parts to download the file.
// if the value is less than or equal to 0, the file will be downloaded in one part.
Parts int
// the progress listener.
// if the value is nil, the progress will not be listened.
ListenProgress ProgressListener
}

Expand Down
3 changes: 3 additions & 0 deletions oget.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type GettingTask struct {
timeout time.Duration
}

// creates a new GettingTask. will access the URL to get the file information.
func CreateGettingTask(config *RemoteFile) (*GettingTask, error) {
c := config.standardize()
client := newGettingClient(c.MaxIdleConnsPerHost)
Expand Down Expand Up @@ -74,10 +75,12 @@ func CreateGettingTask(config *RemoteFile) (*GettingTask, error) {
return task, nil
}

// returns the content length of the file.
func (t *GettingTask) ContentLength() int64 {
return t.contentLength
}

// downloads the file.
func (t *GettingTask) Get(config *GettingConfig) (func() error, error) {
var prog *progress
c := config.standardize()
Expand Down
13 changes: 11 additions & 2 deletions progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@ import (
type ProgressPhase int

const (
// ProgressPhaseDownloading is the phase of downloading.
ProgressPhaseDownloading ProgressPhase = iota
// ProgressPhaseCoping is the phase of merging from parts of temp files.
ProgressPhaseCoping
// ProgressPhaseDone is the phase of downloading done.
ProgressPhaseDone
)

// ProgressListener is the listener of the progress.
type ProgressListener func(event ProgressEvent)

// ProgressEvent is the event of the progress.
type ProgressEvent struct {
Phase ProgressPhase
// the phase of the progress.
Phase ProgressPhase
// the progress of the downloading (bytes).
Progress int64
Total int64
// the total length of the downloading (bytes).
Total int64
}

type progress struct {
Expand Down
2 changes: 2 additions & 0 deletions sha512.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
)

// returns the SHA512 code of the file.
func SHA512(path string) (string, error) {
return sha512OfFiles(&[]string{path})
}
Expand All @@ -27,6 +28,7 @@ func sha512OfFiles(pathList *[]string) (string, error) {
return hashStr, nil
}

// SHA512Error is the error type of SHA512.
type SHA512Error struct {
message string
}
Expand Down
Loading