301 lines
7.7 KiB
Go
301 lines
7.7 KiB
Go
/*
|
|
* Copyright 2022 The Dragonfly Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package dfcache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
commonv1 "d7y.io/api/pkg/apis/common/v1"
|
|
dfdaemonv1 "d7y.io/api/pkg/apis/dfdaemon/v1"
|
|
|
|
"d7y.io/dragonfly/v2/client/config"
|
|
"d7y.io/dragonfly/v2/internal/dferrors"
|
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
|
dfdaemonclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
|
|
)
|
|
|
|
// Format that's used to cast the given cid to URI.
|
|
// "d7y" as scheme, and absolute path starts with "/"
|
|
const cidURIFormat = "d7y:/%s"
|
|
|
|
func newCid(cid string) string {
|
|
return fmt.Sprintf(cidURIFormat, url.QueryEscape(cid))
|
|
}
|
|
|
|
// Stat checks if the given cache entry exists in local storage and/or in P2P network, and returns
|
|
// os.ErrNotExist if cache is not found.
|
|
func Stat(cfg *config.DfcacheConfig, client dfdaemonclient.V1) error {
|
|
var (
|
|
ctx = context.Background()
|
|
cancel context.CancelFunc
|
|
statError error
|
|
)
|
|
|
|
if err := cfg.Validate(config.CmdStat); err != nil {
|
|
return fmt.Errorf("validate stat option failed: %w", err)
|
|
}
|
|
|
|
wLog := logger.With("Cid", cfg.Cid, "Tag", cfg.Tag)
|
|
wLog.Info("init success and start to stat")
|
|
|
|
if cfg.Timeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, cfg.Timeout)
|
|
} else {
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
}
|
|
|
|
go func() {
|
|
statError = statTask(ctx, client, cfg, wLog)
|
|
cancel()
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("stat timeout(%s)", cfg.Timeout)
|
|
}
|
|
return statError
|
|
}
|
|
|
|
func statTask(ctx context.Context, client dfdaemonclient.V1, cfg *config.DfcacheConfig, wLog *logger.SugaredLoggerOnWith) error {
|
|
if client == nil {
|
|
return errors.New("stat has no daemon client")
|
|
}
|
|
|
|
start := time.Now()
|
|
statError := client.StatTask(ctx, newStatRequest(cfg))
|
|
if statError == nil {
|
|
wLog.Infof("task found in %.6f s", time.Since(start).Seconds())
|
|
return nil
|
|
}
|
|
|
|
// Task not found, return os.ErrNotExist
|
|
if dferrors.CheckError(statError, commonv1.Code_PeerTaskNotFound) {
|
|
return os.ErrNotExist
|
|
}
|
|
|
|
// Otherwise hit internal error
|
|
wLog.Errorf("daemon stat file error: %s", statError)
|
|
return statError
|
|
}
|
|
|
|
func newStatRequest(cfg *config.DfcacheConfig) *dfdaemonv1.StatTaskRequest {
|
|
return &dfdaemonv1.StatTaskRequest{
|
|
Url: newCid(cfg.Cid),
|
|
UrlMeta: &commonv1.UrlMeta{
|
|
Tag: cfg.Tag,
|
|
},
|
|
LocalOnly: cfg.LocalOnly,
|
|
}
|
|
}
|
|
|
|
// Import imports the given cache into P2P network.
|
|
func Import(cfg *config.DfcacheConfig, client dfdaemonclient.V1) error {
|
|
var (
|
|
ctx = context.Background()
|
|
cancel context.CancelFunc
|
|
importError error
|
|
)
|
|
|
|
if err := cfg.Validate(config.CmdImport); err != nil {
|
|
return fmt.Errorf("validate import option failed: %w", err)
|
|
}
|
|
|
|
wLog := logger.With("Cid", cfg.Cid, "Tag", cfg.Tag, "file", cfg.Path)
|
|
wLog.Info("init success and start to import")
|
|
|
|
if cfg.Timeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, cfg.Timeout)
|
|
} else {
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
}
|
|
|
|
go func() {
|
|
importError = importTask(ctx, client, cfg, wLog)
|
|
cancel()
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("import timeout(%s)", cfg.Timeout)
|
|
}
|
|
return importError
|
|
}
|
|
|
|
func importTask(ctx context.Context, client dfdaemonclient.V1, cfg *config.DfcacheConfig, wLog *logger.SugaredLoggerOnWith) error {
|
|
if client == nil {
|
|
return errors.New("import has no daemon client")
|
|
}
|
|
|
|
start := time.Now()
|
|
importError := client.ImportTask(ctx, newImportRequest(cfg))
|
|
if importError != nil {
|
|
wLog.Errorf("daemon import file error: %s", importError)
|
|
return importError
|
|
}
|
|
|
|
wLog.Infof("task imported successfully in %.6f s", time.Since(start).Seconds())
|
|
return nil
|
|
}
|
|
|
|
func newImportRequest(cfg *config.DfcacheConfig) *dfdaemonv1.ImportTaskRequest {
|
|
return &dfdaemonv1.ImportTaskRequest{
|
|
Type: commonv1.TaskType_DfCache,
|
|
Url: newCid(cfg.Cid),
|
|
Path: cfg.Path,
|
|
UrlMeta: &commonv1.UrlMeta{
|
|
Tag: cfg.Tag,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Export exports or downloads the given cache from P2P network, and return os.ErrNotExist if cache
|
|
// doesn't exist.
|
|
func Export(cfg *config.DfcacheConfig, client dfdaemonclient.V1) error {
|
|
var (
|
|
ctx = context.Background()
|
|
cancel context.CancelFunc
|
|
exportError error
|
|
)
|
|
|
|
if err := cfg.Validate(config.CmdExport); err != nil {
|
|
return fmt.Errorf("validate export option failed: %w", err)
|
|
}
|
|
|
|
wLog := logger.With("Cid", cfg.Cid, "Tag", cfg.Tag, "output", cfg.Output)
|
|
wLog.Info("init success and start to export")
|
|
|
|
if cfg.Timeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, cfg.Timeout)
|
|
} else {
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
}
|
|
|
|
go func() {
|
|
exportError = exportTask(ctx, client, cfg, wLog)
|
|
cancel()
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("export timeout(%s)", cfg.Timeout)
|
|
}
|
|
return exportError
|
|
}
|
|
|
|
func exportTask(ctx context.Context, client dfdaemonclient.V1, cfg *config.DfcacheConfig, wLog *logger.SugaredLoggerOnWith) error {
|
|
if client == nil {
|
|
return errors.New("export has no daemon client")
|
|
}
|
|
|
|
start := time.Now()
|
|
exportError := client.ExportTask(ctx, newExportRequest(cfg))
|
|
if exportError == nil {
|
|
wLog.Infof("task exported successfully in %.6f s", time.Since(start).Seconds())
|
|
return nil
|
|
}
|
|
|
|
// Task not found, return os.ErrNotExist
|
|
if dferrors.CheckError(exportError, commonv1.Code_PeerTaskNotFound) {
|
|
return os.ErrNotExist
|
|
}
|
|
|
|
// Otherwise hit internal error
|
|
wLog.Errorf("daemon export file error: %s", exportError)
|
|
return exportError
|
|
}
|
|
|
|
func newExportRequest(cfg *config.DfcacheConfig) *dfdaemonv1.ExportTaskRequest {
|
|
return &dfdaemonv1.ExportTaskRequest{
|
|
Url: newCid(cfg.Cid),
|
|
Output: cfg.Output,
|
|
Timeout: uint64(cfg.Timeout),
|
|
Limit: float64(cfg.RateLimit),
|
|
UrlMeta: &commonv1.UrlMeta{
|
|
Tag: cfg.Tag,
|
|
},
|
|
Uid: int64(os.Getuid()),
|
|
Gid: int64(os.Getgid()),
|
|
LocalOnly: cfg.LocalOnly,
|
|
}
|
|
}
|
|
|
|
func Delete(cfg *config.DfcacheConfig, client dfdaemonclient.V1) error {
|
|
var (
|
|
ctx = context.Background()
|
|
cancel context.CancelFunc
|
|
deleteError error
|
|
)
|
|
|
|
if err := cfg.Validate(config.CmdDelete); err != nil {
|
|
return fmt.Errorf("validate delete option failed: %w", err)
|
|
}
|
|
|
|
wLog := logger.With("Cid", cfg.Cid, "Tag", cfg.Tag)
|
|
wLog.Info("init success and start to delete")
|
|
|
|
if cfg.Timeout > 0 {
|
|
ctx, cancel = context.WithTimeout(ctx, cfg.Timeout)
|
|
} else {
|
|
ctx, cancel = context.WithCancel(ctx)
|
|
}
|
|
|
|
go func() {
|
|
deleteError = deleteTask(ctx, client, cfg, wLog)
|
|
cancel()
|
|
}()
|
|
|
|
<-ctx.Done()
|
|
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("delete timeout(%s)", cfg.Timeout)
|
|
}
|
|
return deleteError
|
|
}
|
|
|
|
func deleteTask(ctx context.Context, client dfdaemonclient.V1, cfg *config.DfcacheConfig, wLog *logger.SugaredLoggerOnWith) error {
|
|
if client == nil {
|
|
return errors.New("delete has no daemon client")
|
|
}
|
|
|
|
start := time.Now()
|
|
deleteError := client.DeleteTask(ctx, newDeleteRequest(cfg))
|
|
if deleteError != nil {
|
|
wLog.Errorf("daemon delete file error: %s", deleteError)
|
|
return deleteError
|
|
}
|
|
|
|
wLog.Infof("task deleted successfully in %.6f s", time.Since(start).Seconds())
|
|
return nil
|
|
}
|
|
|
|
func newDeleteRequest(cfg *config.DfcacheConfig) *dfdaemonv1.DeleteTaskRequest {
|
|
return &dfdaemonv1.DeleteTaskRequest{
|
|
Url: newCid(cfg.Cid),
|
|
UrlMeta: &commonv1.UrlMeta{
|
|
Tag: cfg.Tag,
|
|
},
|
|
}
|
|
}
|