2023-08-27 21:30:19 +02:00
|
|
|
package backblaze
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/kurin/blazer/b2"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (b *BackBlaze) Sync(ctx context.Context) error {
|
2023-10-01 14:35:50 +02:00
|
|
|
if b.options.Bucket == "" && (b.options.FilePath == "" || b.options.Dir == "") {
|
|
|
|
return fmt.Errorf("bucket name is %v | filePath is %v | dir is %v", b.options.Bucket, b.options.FilePath, b.options.Dir)
|
2023-08-27 21:30:19 +02:00
|
|
|
}
|
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
if b.options.FilePath != "" && b.options.Dir != "" {
|
2023-08-27 21:30:19 +02:00
|
|
|
return errors.New("you must select just 1 option, dir or file")
|
|
|
|
}
|
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
bc, err := b.b2Client.Bucket(ctx, b.options.Bucket)
|
2023-08-27 21:30:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("b2Client.Bucket %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if bc == nil {
|
2023-10-01 14:35:50 +02:00
|
|
|
return fmt.Errorf("bucket doesn't exist %s", b.options.Bucket)
|
2023-08-27 21:30:19 +02:00
|
|
|
}
|
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
b.logger.Infoln("bucket found:", bc.Name())
|
|
|
|
if b.options.FilePath != "" {
|
|
|
|
b.logger.Infoln("file:", b.options.FilePath)
|
2023-08-27 21:30:19 +02:00
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
if err := b.copyFile(ctx, bc, b.options.FilePath); err != nil {
|
2023-08-27 21:30:19 +02:00
|
|
|
return fmt.Errorf("copyFile %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
if b.options.Dir != "" {
|
2023-12-26 16:44:37 +01:00
|
|
|
// Create a separate context for long-running operations
|
|
|
|
longRunningCtx, cancelLongRunningOps := context.WithCancel(context.Background())
|
|
|
|
defer cancelLongRunningOps()
|
|
|
|
|
|
|
|
oldFiles, err := b.bucketFiles(longRunningCtx, bc)
|
2023-08-27 21:30:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("bucketFiles %w", err)
|
|
|
|
}
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Debugln(strings.Repeat("*", 40))
|
|
|
|
b.logger.Debugln("oldFiles to clean:\n\t\t" + strings.Join(oldFiles, "\n\t\t"))
|
|
|
|
b.logger.Debugln(strings.Repeat("*", 40))
|
2023-08-27 21:30:19 +02:00
|
|
|
|
|
|
|
fileChan := make(chan string)
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
2023-10-01 14:35:50 +02:00
|
|
|
for i := 0; i < b.options.MaxWorkers; i++ {
|
2023-08-27 21:30:19 +02:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
for src := range fileChan {
|
2023-12-26 16:44:37 +01:00
|
|
|
if err := b.copyFile(longRunningCtx, bc, src); err != nil {
|
2023-08-27 21:30:19 +02:00
|
|
|
b.logger.Errorf("error copying file %s: %v\n", src, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk the directory and send files to the channel for uploading
|
2023-10-01 14:35:50 +02:00
|
|
|
err = filepath.WalkDir(b.options.Dir, func(path string, d fs.DirEntry, err error) error {
|
2023-08-27 21:30:19 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !d.IsDir() {
|
|
|
|
fileChan <- path
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error walking the directory: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the channel (no more files to send)
|
|
|
|
close(fileChan)
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
// Cleanup old files after backup is completed
|
2023-12-26 16:44:37 +01:00
|
|
|
if err := b.cleanBucket(longRunningCtx, bc, oldFiles); err != nil {
|
2023-08-27 21:30:19 +02:00
|
|
|
return fmt.Errorf("cleanBucket %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Infoln("copied successfully")
|
2023-08-27 21:30:19 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BackBlaze) copyFile(ctx context.Context, bucket *b2.Bucket, src string) error {
|
|
|
|
f, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
w := bucket.Object(fi.Name()).NewWriter(ctx)
|
2023-10-01 14:35:50 +02:00
|
|
|
w.ConcurrentUploads = b.options.ConcurrentUploads
|
2023-08-27 21:30:19 +02:00
|
|
|
w.UseFileBuffer = true
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Infoln("start copying ", fi.Name())
|
2023-08-27 21:30:19 +02:00
|
|
|
if _, err := io.Copy(w, f); err != nil {
|
|
|
|
w.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := w.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Infoln("end copying ", fi.Name())
|
2023-08-27 21:30:19 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BackBlaze) cleanBucket(ctx context.Context, bucket *b2.Bucket, files []string) error {
|
|
|
|
var errorBuilder strings.Builder
|
|
|
|
for _, v := range files {
|
|
|
|
obj := bucket.Object(v)
|
|
|
|
if obj == nil {
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Errorln("bucket.Object is nil", v)
|
2023-08-27 21:30:19 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := obj.Delete(ctx); err != nil {
|
|
|
|
errorBuilder.WriteString(fmt.Errorf("error deleting %s : %w", v, err).Error())
|
|
|
|
errorBuilder.WriteString("; ")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if errorBuilder.Len() > 0 {
|
|
|
|
return errors.New(errorBuilder.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BackBlaze) bucketFiles(ctx context.Context, bucket *b2.Bucket) ([]string, error) {
|
|
|
|
bucketIter := bucket.List(ctx)
|
|
|
|
if bucketIter == nil {
|
|
|
|
return nil, fmt.Errorf("bucket list cannot be nil")
|
|
|
|
}
|
|
|
|
var files []string
|
|
|
|
for {
|
|
|
|
if !bucketIter.Next() {
|
|
|
|
if bucketIter.Err() != nil {
|
|
|
|
return nil, fmt.Errorf("bucketIter err %w", bucketIter.Err())
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if bucketIter.Object() == nil {
|
2023-08-29 11:40:07 +02:00
|
|
|
b.logger.Errorln("bucketIter Object is nil")
|
2023-08-27 21:30:19 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
files = append(files, bucketIter.Object().Name())
|
|
|
|
}
|
|
|
|
return files, nil
|
|
|
|
}
|