Repository: dragonflyoss/Dragonfly2
Pull request #1227 (2022-04-06 06:27:36)

feat: define and implement new dfdaemon APIs to make dragonfly2 work as a distributed cache
## Description

Define and implement new dfdaemon and scheduler APIs to make dragonfly2 to work as a distributed cache system. So that client of dfdaemon could import, export, check, and delete a file into/from dragonfly2 P2P network.

I defined and implemented the following 4 dfdaemon APIs.

```protobuf
service Daemon {  
  // Check if given task exists in P2P cache system
  rpc StatTask(StatTaskRequest) returns(google.protobuf.Empty);
  // Import the given file into P2P cache system
  rpc ImportTask(ImportTaskRequest) returns(google.protobuf.Empty);
  // Export or download file from P2P cache system
  rpc ExportTask(ExportTaskRequest) returns(google.protobuf.Empty);
  // Delete file from P2P cache system
  rpc DeleteTask(DeleteTaskRequest) returns(google.protobuf.Empty);
}
```

And defined and implemented the following 2 scheduler APIs to support dfdaemon APIs.
```protobuf
service Scheduler{
  // Checks if any peer has the given task
  rpc StatTask(StatTaskRequest)returns(Task);
  // A peer announces that it has the announced task to other peers
  rpc AnnounceTask(AnnounceTaskRequest) returns(google.protobuf.Empty);
}
```

Also, I added a new dfcache binary, which takes use of the new dfdaemon APIs to import, export, stat and delete files into/from dragonfly2 P2P network. Note that this binary is mainly for debug and demonstrate new API useages.

For example:

```bash
    - add a file into cache system
      dfcache import --cid sha256:xxxxxx --tag testtag /path/to/file

    - check if a file exists in cache system
      dfcache stat --cid testid --local  # only check local cache
      dfcache stat --cid testid          # check other peers as well

    - export/download a file from cache system
      dfcache export --cid testid -O /path/to/output

    - delete a file from cache system, both local cache and P2P network
      dfcache delete -i testid -t testtag
```

## Motivation and Context
Previously, applications (either via client SDK or via dfget command line) download files through Dragonfly2 P2P network, and dragonfly2 is responsible for downloading this file, either via CDN or via dfdaemon (dfdaemon backsource case), applications don't do back-source itself.

But sometimes we want a distributed cache system, and applications could manipulate this cache system via APIs, files could be added/deleted into/from this cache system by applications, and applications are responsible for downloading such files from source.

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

Note that unit tests are TBD, and metrics are not added either.

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes. 
- [ ] All new and existing tests passed.


jim3ma: Please approve this PR to your branch to fix CI/Test and Lint
codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#1227](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (672780c) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/f685974a6f4bbec9375370e77f369c3cb87a07c6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (f685974) will **decrease** coverage by `2.44%`.
> The diff coverage is `2.30%`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main    #1227      +/-   ##
==========================================
- Coverage   46.62%   44.18%   -2.45%     
==========================================
  Files         133      134       +1     
  Lines       10584    11045     +461     
==========================================
- Hits         4935     4880      -55     
- Misses       5092     5616     +524     
+ Partials      557      549       -8     
```

| Flag | Coverage Δ | |
|---|---|---|
| Object-compatibility-e2etests | `?` | |
| e2etests | `?` | |
| unittests | `44.18% <2.30%> (-2.45%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/config/dfcache.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2NvbmZpZy9kZmNhY2hlLmdv) | `0.00% <0.00%> (ø)` | |
| [client/daemon/peer/peertask\_dummy.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2R1bW15Lmdv) | `0.00% <0.00%> (ø)` | |
| [client/daemon/peer/peertask\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX21hbmFnZXIuZ28=) | `22.69% <0.00%> (-7.88%)` | :arrow_down: |
| [client/daemon/storage/local\_storage.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL2xvY2FsX3N0b3JhZ2UuZ28=) | `28.88% <0.00%> (-0.41%)` | :arrow_down: |
| [client/daemon/storage/local\_storage\_subtask.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL2xvY2FsX3N0b3JhZ2Vfc3VidGFzay5nbw==) | `28.57% <0.00%> (-0.70%)` | :arrow_down: |
| [client/daemon/storage/storage\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL3N0b3JhZ2VfbWFuYWdlci5nbw==) | `17.09% <0.00%> (-0.78%)` | :arrow_down: |
| [internal/util/util.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-aW50ZXJuYWwvdXRpbC91dGlsLmdv) | `87.50% <0.00%> (-12.50%)` | :arrow_down: |
| [scheduler/rpcserver/rpcserver.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-c2NoZWR1bGVyL3JwY3NlcnZlci9ycGNzZXJ2ZXIuZ28=) | `22.72% <0.00%> (-2.28%)` | :arrow_down: |
| [client/daemon/peer/piece\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BpZWNlX21hbmFnZXIuZ28=) | `52.97% <2.43%> (-15.53%)` | :arrow_down: |
| [client/daemon/rpcserver/rpcserver.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9ycGNzZXJ2ZXIvcnBjc2VydmVyLmdv) | `20.46% <3.31%> (-17.45%)` | :arrow_down: |
| ... and [4 more](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1227/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | |


Patch:
diff --git a/Makefile b/Makefile
index 73e2fe6a..00490f05 100644
--- a/Makefile
+++ b/Makefile
@@ -94,7 +94,7 @@ docker-push-manager: docker-build-manager
 .PHONY: docker-push-manager
 
 # Build dragonfly
-build: build-cdn build-scheduler build-dfget build-manager
+build: build-cdn build-scheduler build-dfget build-dfcache build-manager
 .PHONY: build
 
 # Build cdn
@@ -115,6 +115,18 @@ build-linux-dfget: build-dirs
 	GOOS=linux GOARCH=amd64 ./hack/build.sh dfget
 .PHONY: build-linux-dfget
 
+# Build dfcache
+build-dfcache: build-dirs
+	@echo "Begin to build dfcache."
+	./hack/build.sh dfcache
+.PHONY: build-dfcache
+
+# Build linux dfcache
+build-linux-dfcache: build-dirs
+	@echo "Begin to build linux dfcache."
+	GOOS=linux GOARCH=amd64 ./hack/build.sh dfcache
+.PHONY: build-linux-dfcache
+
 # Build scheduler
 build-scheduler: build-dirs
 	@echo "Begin to build scheduler."
@@ -322,6 +334,8 @@ help:
 	@echo "make build-cdn                      build CDN"
 	@echo "make build-dfget                    build dfget"
 	@echo "make build-dfget-linux              build linux dfget"
+	@echo "make build-dfcache                  build dfcache"
+	@echo "make build-dfcache-linux            build linux dfcache"
 	@echo "make build-scheduler                build scheduler"
 	@echo "make build-manager                  build manager"
 	@echo "make build-manager-console          build manager console"
diff --git a/client/config/constants.go b/client/config/constants.go
index 1570de72..6065837f 100644
--- a/client/config/constants.go
+++ b/client/config/constants.go
@@ -77,3 +77,11 @@ const (
 	SimpleLocalTaskStoreStrategy  = StoreStrategy("io.d7y.storage.v2.simple")
 	AdvanceLocalTaskStoreStrategy = StoreStrategy("io.d7y.storage.v2.advance")
 )
+
+/* dfcache subcommand names */
+const (
+	CmdStat   = "stat"
+	CmdImport = "import"
+	CmdExport = "export"
+	CmdDelete = "delete"
+)
diff --git a/client/config/dfcache.go b/client/config/dfcache.go
new file mode 100644
index 00000000..34c7e13f
--- /dev/null
+++ b/client/config/dfcache.go
@@ -0,0 +1,236 @@
+/*
+ *     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 config
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"path"
+	"path/filepath"
+	"syscall"
+	"time"
+
+	"github.com/pkg/errors"
+	"golang.org/x/time/rate"
+
+	"d7y.io/dragonfly/v2/cmd/dependency/base"
+	"d7y.io/dragonfly/v2/internal/dferrors"
+	"d7y.io/dragonfly/v2/pkg/basic"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+)
+
+type DfcacheConfig = CacheOption
+
+// CacheOption holds all the runtime config information.
+type CacheOption struct {
+	base.Options `yaml:",inline" mapstructure:",squash"`
+
+	// Cid content/cache ID
+	Cid string `yaml:"cid,omitempty" mapstructure:"cid,omitempty"`
+
+	// Tag identify task
+	Tag string `yaml:"tag,omitempty" mapstructure:"tag,omitempty"`
+
+	// Timeout operation timeout(second).
+	Timeout time.Duration `yaml:"timeout,omitempty" mapstructure:"timeout,omitempty"`
+
+	// CallSystem optional system name
+	CallSystem string `yaml:"callSystem,omitempty" mapstructure:"callSystem,omitempty"`
+
+	// LogDir is log directory of dfcache.
+	LogDir string `yaml:"logDir,omitempty" mapstructure:"logDir,omitempty"`
+
+	// WorkHome is working directory of dfcache.
+	WorkHome string `yaml:"workHome,omitempty" mapstructure:"workHome,omitempty"`
+
+	// Output full output path for export task
+	Output string `yaml:"output,omitempty" mapstructure:"output,omitempty"`
+
+	// Path full input path for import task
+	// TODO: change to Input
+	Path string `yaml:"path,omitempty" mapstructure:"path,omitempty"`
+
+	// RateLimit limits export task
+	RateLimit rate.Limit `yaml:"rateLimit,omitempty" mapstructure:"rateLimit,omitempty"`
+
+	// LocalOnly indicates check local cache only
+	LocalOnly bool `yaml:"localOnly,omitempty" mapstructure:"localOnly,omitempty"`
+}
+
+func NewDfcacheConfig() *CacheOption {
+	return &CacheOption{}
+}
+
+func validateCacheStat(cfg *CacheOption) error {
+	return nil
+}
+
+func validateCacheImport(cfg *CacheOption) error {
+	if err := cfg.checkInput(); err != nil {
+		return errors.Wrapf(dferrors.ErrInvalidArgument, "input path: %v", err)
+	}
+	return nil
+}
+
+func ValidateCacheExport(cfg *CacheOption) error {
+	if err := cfg.checkOutput(); err != nil {
+		return errors.Wrapf(dferrors.ErrInvalidArgument, "output: %v", err)
+	}
+	return nil
+}
+
+func ValidateCacheDelete(cfg *CacheOption) error {
+	return nil
+}
+
+func (cfg *CacheOption) Validate(cmd string) error {
+	// Some common validations
+	if cfg == nil {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "runtime config")
+	}
+	if cfg.Cid == "" {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "missing Cid")
+	}
+	if stringutils.IsBlank(cfg.Cid) {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "Cid are all blanks")
+	}
+
+	// cmd specific validations
+	switch cmd {
+	case CmdStat:
+		return validateCacheStat(cfg)
+	case CmdImport:
+		return validateCacheImport(cfg)
+	case CmdExport:
+		return ValidateCacheExport(cfg)
+	case CmdDelete:
+		return ValidateCacheDelete(cfg)
+	default:
+		return errors.Wrapf(dferrors.ErrInvalidArgument, "unknown cache subcommand: %s", cmd)
+	}
+}
+
+func ConvertCacheStat(cfg *CacheOption, args []string) error {
+	return nil
+}
+
+func convertCacheImport(cfg *CacheOption, args []string) error {
+	var err error
+	if cfg.Path == "" && len(args) > 0 {
+		cfg.Path = args[0]
+	}
+	if cfg.Path == "" {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "missing input file")
+	}
+
+	if cfg.Path, err = filepath.Abs(cfg.Path); err != nil {
+		return errors.Wrapf(err, "get absulate path for %s", cfg.Path)
+	}
+	return nil
+}
+
+func ConvertCacheExport(cfg *CacheOption, args []string) error {
+	var err error
+	if cfg.Output == "" && len(args) > 0 {
+		cfg.Output = args[0]
+	}
+	if cfg.Output == "" {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "missing output file")
+	}
+
+	if cfg.Output, err = filepath.Abs(cfg.Output); err != nil {
+		return errors.Wrapf(err, "get absulate path for %s", cfg.Output)
+	}
+	return nil
+}
+
+func ConvertCacheDelete(cfg *CacheOption, args []string) error {
+	return nil
+}
+
+func (cfg *CacheOption) Convert(cmd string, args []string) error {
+	if cfg == nil {
+		return errors.Wrap(dferrors.ErrInvalidArgument, "runtime config")
+	}
+
+	switch cmd {
+	case CmdStat:
+		return ConvertCacheStat(cfg, args)
+	case CmdImport:
+		return convertCacheImport(cfg, args)
+	case CmdExport:
+		return ConvertCacheExport(cfg, args)
+	case CmdDelete:
+		return ConvertCacheDelete(cfg, args)
+	default:
+		return errors.Wrapf(dferrors.ErrInvalidArgument, "unknown cache subcommand: %s", cmd)
+	}
+}
+
+func (cfg *CacheOption) String() string {
+	js, _ := json.Marshal(cfg)
+	return string(js)
+}
+
+func (cfg *CacheOption) checkInput() error {
+	stat, err := os.Stat(cfg.Path)
+	if err != nil {
+		return errors.Wrapf(err, "stat input path %q", cfg.Path)
+	}
+	if stat.IsDir() {
+		return fmt.Errorf("path[%q] is directory but requires file path", cfg.Path)
+	}
+	if err := syscall.Access(cfg.Path, syscall.O_RDONLY); err != nil {
+		return errors.Wrapf(err, "access %q", cfg.Path)
+	}
+	return nil
+}
+
+func (cfg *CacheOption) checkOutput() error {
+	if cfg.Output == "" {
+		return errors.New("no output file path specified")
+	}
+
+	if !filepath.IsAbs(cfg.Output) {
+		absPath, err := filepath.Abs(cfg.Output)
+		if err != nil {
+			return fmt.Errorf("get absolute path[%s] error: %v", cfg.Output, err)
+		}
+		cfg.Output = absPath
+	}
+
+	outputDir, _ := path.Split(cfg.Output)
+	if err := MkdirAll(outputDir, 0777, basic.UserID, basic.UserGroup); err != nil {
+		return err
+	}
+
+	f, err := os.Stat(cfg.Output)
+	if err == nil && f.IsDir() {
+		return fmt.Errorf("path[%s] is directory but requires file path", cfg.Output)
+	}
+
+	// check permission
+	for dir := cfg.Output; !stringutils.IsBlank(dir); dir = filepath.Dir(dir) {
+		if err := syscall.Access(dir, syscall.O_RDWR); err == nil {
+			break
+		} else if os.IsPermission(err) || dir == "/" {
+			return fmt.Errorf("user[%s] path[%s] %v", basic.Username, cfg.Output, err)
+		}
+	}
+	return nil
+}
diff --git a/client/daemon/peer/peertask_dummy.go b/client/daemon/peer/peertask_dummy.go
index f2144da9..14df5bff 100644
--- a/client/daemon/peer/peertask_dummy.go
+++ b/client/daemon/peer/peertask_dummy.go
@@ -48,6 +48,14 @@ func (d *dummySchedulerClient) LeaveTask(ctx context.Context, target *scheduler.
 	return nil
 }
 
+func (d *dummySchedulerClient) StatTask(ctx context.Context, request *scheduler.StatTaskRequest, option ...grpc.CallOption) (*scheduler.Task, error) {
+	panic("should not call this function")
+}
+
+func (d *dummySchedulerClient) AnnounceTask(ctx context.Context, request *scheduler.AnnounceTaskRequest, option ...grpc.CallOption) error {
+	panic("should not call this function")
+}
+
 func (d *dummySchedulerClient) Close() error {
 	return nil
 }
diff --git a/client/daemon/peer/peertask_manager.go b/client/daemon/peer/peertask_manager.go
index 0dca39a6..bc9f99f1 100644
--- a/client/daemon/peer/peertask_manager.go
+++ b/client/daemon/peer/peertask_manager.go
@@ -23,6 +23,7 @@ import (
 	"sync"
 
 	"github.com/go-http-utils/headers"
+	"github.com/pkg/errors"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/trace"
 	"golang.org/x/time/rate"
@@ -54,6 +55,14 @@ type TaskManager interface {
 
 	IsPeerTaskRunning(id string) bool
 
+	// Check if the given task exists in P2P network
+	StatTask(ctx context.Context, taskID string) (*scheduler.Task, error)
+
+	// AnnouncePeerTask announces peer task info to P2P network
+	AnnouncePeerTask(ctx context.Context, meta storage.PeerTaskMetadata, cid string, urlMeta *base.UrlMeta) error
+
+	GetPieceManager() PieceManager
+
 	// Stop stops the PeerTaskManager
 	Stop(ctx context.Context) error
 }
@@ -352,3 +361,64 @@ func (ptm *peerTaskManager) IsPeerTaskRunning(taskID string) bool {
 	_, ok := ptm.runningPeerTasks.Load(taskID)
 	return ok
 }
+
+func (ptm *peerTaskManager) StatTask(ctx context.Context, taskID string) (*scheduler.Task, error) {
+	req := &scheduler.StatTaskRequest{
+		TaskId: taskID,
+	}
+
+	return ptm.schedulerClient.StatTask(ctx, req)
+}
+
+func (ptm *peerTaskManager) GetPieceManager() PieceManager {
+	return ptm.pieceManager
+}
+
+func (ptm *peerTaskManager) AnnouncePeerTask(ctx context.Context,
+	meta storage.PeerTaskMetadata, cid string, urlMeta *base.UrlMeta) error {
+	log := logger.With("function", "AnnouncePeerTask", "taskID", meta.TaskID, "peerID", meta.PeerID, "CID", cid)
+
+	// Check if the given task is completed in local storageManager
+	if ptm.storageManager.FindCompletedTask(meta.TaskID) == nil {
+		msg := fmt.Sprintf("task %s not found in local storage", meta.TaskID)
+		log.Errorf(msg)
+		return errors.New(msg)
+	}
+
+	// prepare AnnounceTaskRequest
+	totalPieces, err := ptm.storageManager.GetTotalPieces(ctx, &meta)
+	if err != nil {
+		msg := fmt.Sprintf("get total pieces failed: %s", err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+	pieceTaskRequest := &base.PieceTaskRequest{
+		TaskId:   meta.TaskID,
+		DstPid:   meta.PeerID,
+		StartNum: 0,
+		Limit:    uint32(totalPieces),
+	}
+	piecePacket, err := ptm.storageManager.GetPieces(ctx, pieceTaskRequest)
+	if err != nil {
+		msg := fmt.Sprintf("get pieces info failed: %s", err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+	piecePacket.DstAddr = fmt.Sprintf("%s:%d", ptm.host.Ip, ptm.host.DownPort)
+	req := &scheduler.AnnounceTaskRequest{
+		TaskId:      meta.TaskID,
+		Cid:         cid,
+		UrlMeta:     urlMeta,
+		PeerHost:    ptm.host,
+		PiecePacket: piecePacket,
+	}
+
+	// Announce peer task to scheduler
+	err = ptm.schedulerClient.AnnounceTask(ctx, req)
+	if err != nil {
+		msg := fmt.Sprintf("announce peer task failed: %s", err)
+		log.Error(msg)
+		return errors.Wrapf(err, "failed to announce peer task %s", meta.TaskID)
+	}
+	return nil
+}
diff --git a/client/daemon/peer/piece_manager.go b/client/daemon/peer/piece_manager.go
index 29e553ae..51ebfcce 100644
--- a/client/daemon/peer/piece_manager.go
+++ b/client/daemon/peer/piece_manager.go
@@ -20,12 +20,13 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"math"
 	"net"
 	"net/http"
 	"net/url"
+	"os"
 	"time"
 
+	"github.com/pkg/errors"
 	"golang.org/x/time/rate"
 
 	"d7y.io/dragonfly/v2/client/clientutil"
@@ -34,6 +35,7 @@ import (
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/util"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
@@ -42,6 +44,7 @@ import (
 type PieceManager interface {
 	DownloadSource(ctx context.Context, pt Task, request *scheduler.PeerTaskRequest) error
 	DownloadPiece(ctx context.Context, request *DownloadPieceRequest) (*DownloadPieceResult, error)
+	ImportFile(ctx context.Context, ptm storage.PeerTaskMetadata, tsd storage.TaskStorageDriver, req *dfdaemon.ImportTaskRequest) error
 }
 
 type pieceManager struct {
@@ -305,7 +308,7 @@ func (pm *pieceManager) downloadKnownLengthSource(ctx context.Context, pt Task,
 	log := pt.Log()
 	pt.SetContentLength(contentLength)
 
-	maxPieceNum := int32(math.Ceil(float64(contentLength) / float64(pieceSize)))
+	maxPieceNum := util.ComputePieceNum(contentLength, pieceSize)
 	for pieceNum := int32(0); pieceNum < maxPieceNum; pieceNum++ {
 		size := pieceSize
 		offset := uint64(pieceNum) * uint64(pieceSize)
@@ -419,7 +422,7 @@ func (pm *pieceManager) downloadUnknownLengthSource(ctx context.Context, pt Task
 
 		// last piece, piece size maybe 0
 		contentLength = int64(pieceSize)*int64(pieceNum) + result.Size
-		pt.SetTotalPieces(int32(math.Ceil(float64(contentLength) / float64(pieceSize))))
+		pt.SetTotalPieces(util.ComputePieceNum(contentLength, pieceSize))
 		err = pt.GetStorage().UpdateTask(ctx,
 			&storage.UpdateTaskRequest{
 				PeerTaskMetadata: storage.PeerTaskMetadata{
@@ -455,3 +458,113 @@ func detectBackSourceError(err error) error {
 	}
 	return err
 }
+
+func (pm *pieceManager) processPieceFromFile(ctx context.Context, ptm storage.PeerTaskMetadata,
+	tsd storage.TaskStorageDriver, r io.Reader, pieceNum int32, pieceOffset uint64,
+	pieceSize uint32, isLastPiece func(n int64) (int32, bool)) (int64, error) {
+	var (
+		n      int64
+		reader = r
+		log    = logger.With("function", "processPieceFromFile", "taskID", ptm.TaskID)
+	)
+
+	if pm.calculateDigest {
+		log.Debugf("calculate digest in processPieceFromFile")
+		reader = digestutils.NewDigestReader(log, r)
+	}
+	n, err := tsd.WritePiece(ctx,
+		&storage.WritePieceRequest{
+			UnknownLength:    false,
+			PeerTaskMetadata: ptm,
+			PieceMetadata: storage.PieceMetadata{
+				Num: pieceNum,
+				// storage manager will get digest from DigestReader, keep empty here is ok
+				Md5:    "",
+				Offset: pieceOffset,
+				Range: clientutil.Range{
+					Start:  int64(pieceOffset),
+					Length: int64(pieceSize),
+				},
+			},
+			Reader:         reader,
+			GenPieceDigest: isLastPiece,
+		})
+	if err != nil {
+		msg := fmt.Sprintf("put piece of task %s to storage failed, piece num: %d, wrote: %d, error: %s", ptm.TaskID, pieceNum, n, err)
+		return n, errors.New(msg)
+	}
+	return n, nil
+}
+
+func (pm *pieceManager) ImportFile(ctx context.Context, ptm storage.PeerTaskMetadata, tsd storage.TaskStorageDriver, req *dfdaemon.ImportTaskRequest) error {
+	log := logger.With("function", "ImportFile", "Cid", req.Cid, "taskID", ptm.TaskID)
+	// get file size and compute piece size and piece count
+	stat, err := os.Stat(req.Path)
+	if err != nil {
+		msg := fmt.Sprintf("stat file %s failed: %s", req.Path, err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+	contentLength := stat.Size()
+	pieceSize := pm.computePieceSize(contentLength)
+	maxPieceNum := util.ComputePieceNum(contentLength, pieceSize)
+
+	file, err := os.Open(req.Path)
+	if err != nil {
+		msg := fmt.Sprintf("open file %s failed: %s", req.Path, err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+	defer file.Close()
+
+	reader := file
+	for pieceNum := int32(0); pieceNum < maxPieceNum; pieceNum++ {
+		size := pieceSize
+		offset := uint64(pieceNum) * uint64(pieceSize)
+		isLastPiece := func(int64) (int32, bool) { return maxPieceNum, pieceNum == maxPieceNum-1 }
+		// calculate piece size for last piece
+		if contentLength > 0 && int64(offset)+int64(size) > contentLength {
+			size = uint32(contentLength - int64(offset))
+		}
+
+		log.Debugf("import piece %d", pieceNum)
+		n, er := pm.processPieceFromFile(ctx, ptm, tsd, reader, pieceNum, offset, size, isLastPiece)
+		if er != nil {
+			log.Errorf("import piece %d of task %s error: %s", pieceNum, ptm.TaskID, er)
+			return er
+		}
+		if n != int64(size) {
+			log.Errorf("import piece %d of task %s size not match, desired: %d, actual: %d", pieceNum, ptm.TaskID, size, n)
+			return storage.ErrShortRead
+		}
+	}
+
+	// Update task with length and piece count
+	err = tsd.UpdateTask(ctx, &storage.UpdateTaskRequest{
+		PeerTaskMetadata: ptm,
+		ContentLength:    contentLength,
+		TotalPieces:      maxPieceNum,
+	})
+	if err != nil {
+		msg := fmt.Sprintf("update task(%s) failed: %s", ptm.TaskID, err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+
+	// Save metadata
+	err = tsd.Store(ctx, &storage.StoreRequest{
+		CommonTaskRequest: storage.CommonTaskRequest{
+			PeerID: ptm.PeerID,
+			TaskID: ptm.TaskID,
+		},
+		MetadataOnly:  true,
+		StoreDataOnly: false,
+	})
+	if err != nil {
+		msg := fmt.Sprintf("store task(%s) failed: %s", ptm.TaskID, err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+
+	return nil
+}
diff --git a/client/daemon/rpcserver/rpcserver.go b/client/daemon/rpcserver/rpcserver.go
index 33aa94ee..4f998711 100644
--- a/client/daemon/rpcserver/rpcserver.go
+++ b/client/daemon/rpcserver/rpcserver.go
@@ -22,6 +22,7 @@ import (
 	"math"
 	"net"
 	"os"
+	"time"
 
 	"github.com/pkg/errors"
 	"google.golang.org/grpc"
@@ -40,7 +41,9 @@ import (
 	dfdaemongrpc "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	dfdaemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
+	"d7y.io/dragonfly/v2/pkg/safe"
 	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
+	"d7y.io/dragonfly/v2/scheduler/resource"
 )
 
 type Server interface {
@@ -201,16 +204,26 @@ func (s *server) CheckHealth(context.Context) error {
 
 func (s *server) Download(ctx context.Context,
 	req *dfdaemongrpc.DownRequest, results chan<- *dfdaemongrpc.DownResult) error {
+	return s.doDownload(ctx, req, results, "")
+}
+
+func (s *server) doDownload(ctx context.Context, req *dfdaemongrpc.DownRequest,
+	results chan<- *dfdaemongrpc.DownResult, peerID string) error {
 	s.Keep()
 	if req.UrlMeta == nil {
 		req.UrlMeta = &base.UrlMeta{}
 	}
+
 	// init peer task request, peer uses different peer id to generate every request
+	// if peerID is not specified
+	if peerID == "" {
+		peerID = idgen.PeerID(s.peerHost.Ip)
+	}
 	peerTask := &peer.FileTaskRequest{
 		PeerTaskRequest: scheduler.PeerTaskRequest{
 			Url:      req.Url,
 			UrlMeta:  req.UrlMeta,
-			PeerId:   idgen.PeerID(s.peerHost.Ip),
+			PeerId:   peerID,
 			PeerHost: s.peerHost,
 		},
 		Output:             req.Output,
@@ -295,3 +308,234 @@ func (s *server) Download(ctx context.Context,
 		}
 	}
 }
+
+func (s *server) StatTask(ctx context.Context, req *dfdaemongrpc.StatTaskRequest) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	log := logger.With("function", "StatTask", "Cid", req.Cid, "Tag", req.UrlMeta.Tag, "taskID", taskID, "LocalOnly", req.LocalOnly)
+
+	log.Info("new stat task request")
+	if completed := s.isTaskCompleted(taskID); completed {
+		log.Info("task found in local storage")
+		return nil
+	}
+
+	// If only stat local cache and task doesn't exist, return not found
+	if req.LocalOnly {
+		msg := "task not found in local cache"
+		log.Info(msg)
+		return dferrors.New(base.Code_PeerTaskNotFound, msg)
+	}
+
+	// Check scheduler if other peers hold the task
+	task, se := s.peerTaskManager.StatTask(ctx, taskID)
+	if se != nil {
+		return se
+	}
+	// Task available for download only if task is in succeeded state and has available peer
+	if task.State == resource.TaskStateSucceeded && task.HasAvailablePeer {
+		return nil
+	}
+	msg := fmt.Sprintf("task found but not available for download, state %s, has available peer %t", task.State, task.HasAvailablePeer)
+	log.Info(msg)
+	return dferrors.New(base.Code_PeerTaskNotFound, msg)
+}
+
+func (s *server) isTaskCompleted(taskID string) bool {
+	return s.storageManager.FindCompletedTask(taskID) != nil
+}
+
+func (s *server) ImportTask(ctx context.Context, req *dfdaemongrpc.ImportTaskRequest) error {
+	peerID := idgen.PeerID(s.peerHost.Ip)
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	log := logger.With("function", "ImportTask", "Cid", req.Cid, "Tag", req.UrlMeta.Tag, "taskID", taskID, "file", req.Path)
+
+	log.Info("new import task request")
+	ptm := storage.PeerTaskMetadata{
+		PeerID: peerID,
+		TaskID: taskID,
+	}
+	announceFunc := func() {
+		// TODO: retry announce on error
+		start := time.Now()
+		err := s.peerTaskManager.AnnouncePeerTask(context.Background(), ptm, req.Cid, req.UrlMeta)
+		if err != nil {
+			log.Warnf("Failed to announce task to scheduler: %s", err)
+		} else {
+			log.Infof("Announce task (peerID %s) to scheduler in %.6f seconds", ptm.PeerID, time.Since(start).Seconds())
+		}
+	}
+
+	// 0. Task exists in local storage
+	if task := s.storageManager.FindCompletedTask(taskID); task != nil {
+		msg := fmt.Sprintf("import file skipped, task already exists with peerID %s", task.PeerID)
+		log.Info(msg)
+
+		// Announce to scheduler as well, but in background
+		ptm.PeerID = task.PeerID
+		go announceFunc()
+		return nil
+	}
+
+	// 1. Register to storageManager
+	// TODO: compute and check hash digest if digest exists in ImportTaskRequest
+	tsd, err := s.storageManager.RegisterTask(ctx, &storage.RegisterTaskRequest{
+		PeerTaskMetadata: storage.PeerTaskMetadata{
+			PeerID: peerID,
+			TaskID: taskID,
+		},
+	})
+	if err != nil {
+		msg := fmt.Sprintf("register task to storage manager failed: %v", err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+
+	// 2. Import task file
+	pieceManager := s.peerTaskManager.GetPieceManager()
+	if err := pieceManager.ImportFile(ctx, ptm, tsd, req); err != nil {
+		msg := fmt.Sprintf("import file failed: %v", err)
+		log.Error(msg)
+		return errors.New(msg)
+	}
+	log.Info("import file succeeded")
+
+	// 3. Announce to scheduler asynchronously
+	go announceFunc()
+
+	return nil
+}
+
+func (s *server) ExportTask(ctx context.Context, req *dfdaemongrpc.ExportTaskRequest) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	log := logger.With("function", "ExportTask", "Cid", req.Cid, "Tag", req.UrlMeta.Tag, "taskID", taskID, "destination", req.Output)
+
+	log.Info("new export task request")
+	task := s.storageManager.FindCompletedTask(taskID)
+	if task == nil {
+		// If only use local cache and task doesn't exist, return error
+		if req.LocalOnly {
+			msg := fmt.Sprintf("task not found in local storage")
+			log.Info(msg)
+			return dferrors.New(base.Code_PeerTaskNotFound, msg)
+		}
+		log.Info("task not found, try from peers")
+		return s.exportFromPeers(ctx, log, req)
+	}
+	err := s.exportFromLocal(ctx, req, task.PeerID)
+	if err != nil {
+		log.Errorf("export from local failed: %s", err)
+		return err
+	}
+	return nil
+}
+
+func (s *server) exportFromLocal(ctx context.Context, req *dfdaemongrpc.ExportTaskRequest, peerID string) error {
+	return s.storageManager.Store(ctx, &storage.StoreRequest{
+		CommonTaskRequest: storage.CommonTaskRequest{
+			PeerID:      peerID,
+			TaskID:      idgen.TaskID(req.Cid, req.UrlMeta),
+			Destination: req.Output,
+		},
+		StoreDataOnly: true,
+	})
+}
+
+func (s *server) exportFromPeers(ctx context.Context, log *logger.SugaredLoggerOnWith, req *dfdaemongrpc.ExportTaskRequest) error {
+	peerID := idgen.PeerID(s.peerHost.Ip)
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+
+	task, err := s.peerTaskManager.StatTask(ctx, taskID)
+	if err != nil {
+		if dferrors.CheckError(err, base.Code_PeerTaskNotFound) {
+			log.Info("task not found in P2P network")
+		} else {
+			msg := fmt.Sprintf("failed to StatTask from peers: %s", err)
+			log.Error(msg)
+		}
+		return err
+	}
+	if task.State != resource.TaskStateSucceeded || !task.HasAvailablePeer {
+		msg := fmt.Sprintf("task found but not available for download, state %s, has available peer %t", task.State, task.HasAvailablePeer)
+		log.Info(msg)
+		return dferrors.New(base.Code_PeerTaskNotFound, msg)
+	}
+
+	// Task exists in peers
+	var (
+		start     = time.Now()
+		drc       = make(chan *dfdaemongrpc.DownResult, 1)
+		errChan   = make(chan error, 3)
+		result    *dfdaemongrpc.DownResult
+		downError error
+	)
+	downRequest := &dfdaemongrpc.DownRequest{
+		Url:               req.Cid,
+		Output:            req.Output,
+		Timeout:           req.Timeout,
+		Limit:             req.Limit,
+		DisableBackSource: true,
+		UrlMeta:           req.UrlMeta,
+		Pattern:           "",
+		Callsystem:        req.Callsystem,
+		Uid:               req.Uid,
+		Gid:               req.Gid,
+	}
+
+	go call(ctx, peerID, drc, s, downRequest, errChan)
+	go func() {
+		for result = range drc {
+			if result.Done {
+				log.Infof("export from peer successfully, length: %d bytes cost: %.6f s", result.CompletedLength, time.Since(start).Seconds())
+				break
+			}
+		}
+		errChan <- dferrors.ErrEndOfStream
+	}()
+
+	if downError = <-errChan; dferrors.IsEndOfStream(downError) {
+		downError = nil
+	}
+
+	if downError != nil {
+		msg := fmt.Sprintf("export from peer failed: %s", downError)
+		log.Error(msg)
+		return downError
+	}
+	return nil
+}
+
+func call(ctx context.Context, peerID string, drc chan *dfdaemongrpc.DownResult, s *server, req *dfdaemongrpc.DownRequest, errChan chan error) {
+	err := safe.Call(func() {
+		if err := s.doDownload(ctx, req, drc, peerID); err != nil {
+			errChan <- err
+		}
+	})
+
+	if err != nil {
+		errChan <- err
+	}
+}
+
+func (s *server) DeleteTask(ctx context.Context, req *dfdaemongrpc.DeleteTaskRequest) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	log := logger.With("function", "DeleteTask", "Cid", req.Cid, "Tag", req.UrlMeta.Tag, "taskID", taskID)
+
+	log.Info("new delete task request")
+	task := s.storageManager.FindCompletedTask(taskID)
+	if task == nil {
+		log.Info("task not found, skip delete")
+		return nil
+	}
+
+	// Unregister task
+	unregReq := storage.CommonTaskRequest{
+		PeerID: task.PeerID,
+		TaskID: taskID,
+	}
+	if err := s.storageManager.UnregisterTask(ctx, unregReq); err != nil {
+		msg := fmt.Sprintf("failed to UnregisterTask: %s", err)
+		log.Errorf(msg)
+		return errors.New(msg)
+	}
+	return nil
+}
diff --git a/client/daemon/storage/local_storage.go b/client/daemon/storage/local_storage.go
index 2ba9e3ff..a8040d7d 100644
--- a/client/daemon/storage/local_storage.go
+++ b/client/daemon/storage/local_storage.go
@@ -420,6 +420,16 @@ func (t *localTaskStore) GetPieces(ctx context.Context, req *base.PieceTaskReque
 	return piecePacket, nil
 }
 
+func (t *localTaskStore) GetTotalPieces(ctx context.Context, req *PeerTaskMetadata) (int32, error) {
+	if t.invalid.Load() {
+		t.Errorf("invalid digest, refuse to get total pieces")
+		return -1, ErrInvalidDigest
+	}
+
+	t.touch()
+	return t.TotalPieces, nil
+}
+
 func (t *localTaskStore) CanReclaim() bool {
 	if t.invalid.Load() {
 		return true
diff --git a/client/daemon/storage/local_storage_subtask.go b/client/daemon/storage/local_storage_subtask.go
index b864fe02..e0660f3e 100644
--- a/client/daemon/storage/local_storage_subtask.go
+++ b/client/daemon/storage/local_storage_subtask.go
@@ -234,6 +234,16 @@ func (t *localSubTaskStore) GetPieces(ctx context.Context, req *base.PieceTaskRe
 	return piecePacket, nil
 }
 
+func (t *localSubTaskStore) GetTotalPieces(ctx context.Context, req *PeerTaskMetadata) (int32, error) {
+	if t.invalid.Load() {
+		t.Errorf("invalid digest, refuse to get total pieces")
+		return -1, ErrInvalidDigest
+	}
+
+	t.parent.touch()
+	return t.TotalPieces, nil
+}
+
 func (t *localSubTaskStore) UpdateTask(ctx context.Context, req *UpdateTaskRequest) error {
 	t.parent.touch()
 	t.Lock()
diff --git a/client/daemon/storage/storage_manager.go b/client/daemon/storage/storage_manager.go
index 14b278cb..06dc265f 100644
--- a/client/daemon/storage/storage_manager.go
+++ b/client/daemon/storage/storage_manager.go
@@ -57,6 +57,8 @@ type TaskStorageDriver interface {
 
 	GetPieces(ctx context.Context, req *base.PieceTaskRequest) (*base.PiecePacket, error)
 
+	GetTotalPieces(ctx context.Context, req *PeerTaskMetadata) (int32, error)
+
 	UpdateTask(ctx context.Context, req *UpdateTaskRequest) error
 
 	// Store stores task data to the target path
@@ -87,6 +89,8 @@ type Manager interface {
 	RegisterTask(ctx context.Context, req *RegisterTaskRequest) (TaskStorageDriver, error)
 	// RegisterSubTask registers a subtask in storage driver
 	RegisterSubTask(ctx context.Context, req *RegisterSubTaskRequest) (TaskStorageDriver, error)
+	// UnregisterTask unregisters a task in storage driver
+	UnregisterTask(ctx context.Context, req CommonTaskRequest) error
 	// FindCompletedTask try to find a completed task for fast path
 	FindCompletedTask(taskID string) *ReusePeerTask
 	// FindCompletedSubTask try to find a completed subtask for fast path
@@ -321,6 +325,18 @@ func (s *storageManager) GetPieces(ctx context.Context, req *base.PieceTaskReque
 	return t.GetPieces(ctx, req)
 }
 
+func (s *storageManager) GetTotalPieces(ctx context.Context, req *PeerTaskMetadata) (int32, error) {
+	t, ok := s.LoadTask(
+		PeerTaskMetadata{
+			TaskID: req.TaskID,
+			PeerID: req.PeerID,
+		})
+	if !ok {
+		return -1, ErrTaskNotFound
+	}
+	return t.(TaskStorageDriver).GetTotalPieces(ctx, req)
+}
+
 func (s *storageManager) LoadTask(meta PeerTaskMetadata) (TaskStorageDriver, bool) {
 	s.Keep()
 	d, ok := s.tasks.Load(meta)
@@ -330,6 +346,15 @@ func (s *storageManager) LoadTask(meta PeerTaskMetadata) (TaskStorageDriver, boo
 	return d.(TaskStorageDriver), ok
 }
 
+func (s *storageManager) LoadAndDeleteTask(meta PeerTaskMetadata) (TaskStorageDriver, bool) {
+	s.Keep()
+	d, ok := s.tasks.LoadAndDelete(meta)
+	if !ok {
+		return nil, false
+	}
+	return d.(TaskStorageDriver), ok
+}
+
 func (s *storageManager) UpdateTask(ctx context.Context, req *UpdateTaskRequest) error {
 	t, ok := s.LoadTask(
 		PeerTaskMetadata{
@@ -838,6 +863,30 @@ func (s *storageManager) TryGC() (bool, error) {
 	return true, nil
 }
 
+func (s *storageManager) deleteTask(meta PeerTaskMetadata) error {
+	task, ok := s.LoadAndDeleteTask(meta)
+	if !ok {
+		logger.Infof("deleteTask: task meta not found: %v", meta)
+		return nil
+	}
+
+	logger.Debugf("deleteTask: deleting task: %v", meta)
+	if _, ok := task.(*localTaskStore); ok {
+		s.cleanIndex(meta.TaskID, meta.PeerID)
+	} else {
+		s.cleanSubIndex(meta.TaskID, meta.PeerID)
+	}
+	task.(Reclaimer).MarkReclaim()
+	return task.(Reclaimer).Reclaim()
+}
+
+func (s *storageManager) UnregisterTask(ctx context.Context, req CommonTaskRequest) error {
+	return s.deleteTask(PeerTaskMetadata{
+		TaskID: req.TaskID,
+		PeerID: req.PeerID,
+	})
+}
+
 func (s *storageManager) CleanUp() {
 	_, _ = s.forceGC()
 }
@@ -845,15 +894,7 @@ func (s *storageManager) CleanUp() {
 func (s *storageManager) forceGC() (bool, error) {
 	s.tasks.Range(func(key, task interface{}) bool {
 		meta := key.(PeerTaskMetadata)
-		s.tasks.Delete(meta)
-		if _, ok := task.(*localTaskStore); ok {
-			s.cleanIndex(meta.TaskID, meta.PeerID)
-		} else {
-			s.cleanSubIndex(meta.TaskID, meta.PeerID)
-		}
-
-		task.(Reclaimer).MarkReclaim()
-		err := task.(Reclaimer).Reclaim()
+		err := s.deleteTask(meta)
 		if err != nil {
 			logger.Errorf("gc task store %s error: %s", key, err)
 		}
diff --git a/client/dfcache/dfcache.go b/client/dfcache/dfcache.go
new file mode 100644
index 00000000..17f19807
--- /dev/null
+++ b/client/dfcache/dfcache.go
@@ -0,0 +1,300 @@
+/*
+ *     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"
+	"fmt"
+	"net/url"
+	"os"
+	"time"
+
+	"github.com/pkg/errors"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/internal/dferrors"
+	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/basic"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
+	daemonclient "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 daemonclient.DaemonClient) error {
+	var (
+		ctx       = context.Background()
+		cancel    context.CancelFunc
+		statError error
+	)
+
+	if err := cfg.Validate(config.CmdStat); err != nil {
+		return errors.Wrap(err, "validate stat option failed")
+	}
+
+	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 errors.Errorf("stat timeout(%s)", cfg.Timeout)
+	}
+	return statError
+}
+
+func statTask(ctx context.Context, client daemonclient.DaemonClient, 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, base.Code_PeerTaskNotFound) {
+		return os.ErrNotExist
+	}
+
+	// Otherwise hit internal error
+	wLog.Errorf("daemon stat file error: %s", statError)
+	return statError
+}
+
+func newStatRequest(cfg *config.DfcacheConfig) *dfdaemon.StatTaskRequest {
+	return &dfdaemon.StatTaskRequest{
+		Cid: newCid(cfg.Cid),
+		UrlMeta: &base.UrlMeta{
+			Tag: cfg.Tag,
+		},
+		LocalOnly: cfg.LocalOnly,
+	}
+}
+
+// Import imports the given cache into P2P network.
+func Import(cfg *config.DfcacheConfig, client daemonclient.DaemonClient) error {
+	var (
+		ctx         = context.Background()
+		cancel      context.CancelFunc
+		importError error
+	)
+
+	if err := cfg.Validate(config.CmdImport); err != nil {
+		return errors.Wrap(err, "validate import option failed")
+	}
+
+	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 errors.Errorf("import timeout(%s)", cfg.Timeout)
+	}
+	return importError
+}
+
+func importTask(ctx context.Context, client daemonclient.DaemonClient, 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) *dfdaemon.ImportTaskRequest {
+	return &dfdaemon.ImportTaskRequest{
+		Cid:  newCid(cfg.Cid),
+		Path: cfg.Path,
+		UrlMeta: &base.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 daemonclient.DaemonClient) error {
+	var (
+		ctx         = context.Background()
+		cancel      context.CancelFunc
+		exportError error
+	)
+
+	if err := cfg.Validate(config.CmdExport); err != nil {
+		return errors.Wrap(err, "validate export option failed")
+	}
+
+	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 errors.Errorf("export timeout(%s)", cfg.Timeout)
+	}
+	return exportError
+}
+
+func exportTask(ctx context.Context, client daemonclient.DaemonClient, 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, base.Code_PeerTaskNotFound) {
+		return os.ErrNotExist
+	}
+
+	// Otherwise hit internal error
+	wLog.Errorf("daemon export file error: %s", exportError)
+	return exportError
+}
+
+func newExportRequest(cfg *config.DfcacheConfig) *dfdaemon.ExportTaskRequest {
+	return &dfdaemon.ExportTaskRequest{
+		Cid:     newCid(cfg.Cid),
+		Output:  cfg.Output,
+		Timeout: uint64(cfg.Timeout),
+		Limit:   float64(cfg.RateLimit),
+		UrlMeta: &base.UrlMeta{
+			Tag: cfg.Tag,
+		},
+		Uid:       int64(basic.UserID),
+		Gid:       int64(basic.UserGroup),
+		LocalOnly: cfg.LocalOnly,
+	}
+}
+
+func Delete(cfg *config.DfcacheConfig, client daemonclient.DaemonClient) error {
+	var (
+		ctx         = context.Background()
+		cancel      context.CancelFunc
+		deleteError error
+	)
+
+	if err := cfg.Validate(config.CmdDelete); err != nil {
+		return errors.Wrap(err, "validate delete option failed")
+	}
+
+	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 errors.Errorf("delete timeout(%s)", cfg.Timeout)
+	}
+	return deleteError
+}
+
+func deleteTask(ctx context.Context, client daemonclient.DaemonClient, 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) *dfdaemon.DeleteTaskRequest {
+	return &dfdaemon.DeleteTaskRequest{
+		Cid: newCid(cfg.Cid),
+		UrlMeta: &base.UrlMeta{
+			Tag: cfg.Tag,
+		},
+	}
+}
diff --git a/cmd/dfcache/cmd/delete.go b/cmd/dfcache/cmd/delete.go
new file mode 100644
index 00000000..2b03c099
--- /dev/null
+++ b/cmd/dfcache/cmd/delete.go
@@ -0,0 +1,50 @@
+/*
+ *     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 cmd
+
+import (
+	"github.com/spf13/cobra"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/client/dfcache"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+)
+
+const deleteDesc = "delete file from P2P cache system"
+
+// deleteCmd represents the cache delete command
+var deleteCmd = &cobra.Command{
+	Use:                "delete <-i cid> [flags]",
+	Short:              deleteDesc,
+	Long:               deleteDesc,
+	Args:               cobra.NoArgs,
+	DisableAutoGenTag:  true,
+	SilenceUsage:       true,
+	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return runDfcacheSubcmd(config.CmdDelete, args)
+	},
+}
+
+func initDelete() {
+	// Add the command to parent
+	rootCmd.AddCommand(deleteCmd)
+}
+
+func runDelete(cfg *config.DfcacheConfig, client client.DaemonClient) error {
+	return dfcache.Delete(cfg, client)
+}
diff --git a/cmd/dfcache/cmd/export.go b/cmd/dfcache/cmd/export.go
new file mode 100644
index 00000000..fa4635ea
--- /dev/null
+++ b/cmd/dfcache/cmd/export.go
@@ -0,0 +1,59 @@
+/*
+ *     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 cmd
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/client/dfcache"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+)
+
+const exportDesc = "export file from P2P cache system"
+
+// exportCmd represents the cache export command
+var exportCmd = &cobra.Command{
+	Use:                "export <-i cid> <output>|<-O output> [flags]",
+	Short:              exportDesc,
+	Long:               exportDesc,
+	Args:               cobra.MaximumNArgs(1),
+	DisableAutoGenTag:  true,
+	SilenceUsage:       true,
+	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return runDfcacheSubcmd(config.CmdExport, args)
+	},
+}
+
+func initExport() {
+	// Add the command to parent
+	rootCmd.AddCommand(exportCmd)
+
+	flags := exportCmd.Flags()
+	flags.StringVarP(&dfcacheConfig.Output, "output", "O", "", "export file path")
+	flags.BoolVarP(&dfcacheConfig.LocalOnly, "local", "l", false, "only export file from local cache")
+	if err := viper.BindPFlags(flags); err != nil {
+		panic(errors.Wrap(err, "bind cache export flags to viper"))
+	}
+}
+
+func runExport(cfg *config.DfcacheConfig, client client.DaemonClient) error {
+	return dfcache.Export(cfg, client)
+}
diff --git a/cmd/dfcache/cmd/import.go b/cmd/dfcache/cmd/import.go
new file mode 100644
index 00000000..5331038b
--- /dev/null
+++ b/cmd/dfcache/cmd/import.go
@@ -0,0 +1,58 @@
+/*
+ *     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 cmd
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/client/dfcache"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+)
+
+const importDesc = "import file into P2P cache system"
+
+// importCmd represents the cache import command
+var importCmd = &cobra.Command{
+	Use:                "import <-i cid> <file>|<-I file> [flags]",
+	Short:              importDesc,
+	Long:               importDesc,
+	Args:               cobra.MaximumNArgs(1),
+	DisableAutoGenTag:  true,
+	SilenceUsage:       true,
+	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return runDfcacheSubcmd(config.CmdImport, args)
+	},
+}
+
+func initImport() {
+	// Add the command to parent
+	rootCmd.AddCommand(importCmd)
+
+	flags := importCmd.Flags()
+	flags.StringVarP(&dfcacheConfig.Path, "input", "I", "", "import the given file into P2P network")
+	if err := viper.BindPFlags(flags); err != nil {
+		panic(errors.Wrap(err, "bind cache import flags to viper"))
+	}
+}
+
+func runImport(cfg *config.DfcacheConfig, client client.DaemonClient) error {
+	return dfcache.Import(cfg, client)
+}
diff --git a/cmd/dfcache/cmd/root.go b/cmd/dfcache/cmd/root.go
new file mode 100644
index 00000000..6ea13ac6
--- /dev/null
+++ b/cmd/dfcache/cmd/root.go
@@ -0,0 +1,185 @@
+/*
+ *     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 cmd
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+	"gopkg.in/yaml.v3"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/cmd/dependency"
+	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/internal/dfnet"
+	"d7y.io/dragonfly/v2/pkg/dfpath"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+	"d7y.io/dragonfly/v2/version"
+)
+
+var (
+	dfcacheConfig *config.DfcacheConfig
+)
+
+var dfcacheDescription = `
+dfcache is the cache client to of dragonfly that communicates with dfdaemon and operates
+on files in P2P network, where the P2P network acts as a cache system.
+
+The difference between dfcache and dfget is that, dfget downloads file from a given URL,
+the file might be on other peers in P2P network or a CDN peer, it's the P2P network's
+responsibility to download file from source; but dfcache could only export or download a
+file that has been imported or added into P2P network by other peer, it's the user's
+responsibility to go back to source and add file into P2P network.
+`
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:                "dfcache <command> [flags]",
+	Short:              "the P2P cache client of dragonfly",
+	Long:               dfcacheDescription,
+	Args:               cobra.MaximumNArgs(1),
+	DisableAutoGenTag:  true,
+	SilenceUsage:       true,
+	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	if err := rootCmd.Execute(); err != nil {
+		logger.Error(err)
+		os.Exit(1)
+	}
+}
+
+func init() {
+	// Initialize default dfcache config
+	dfcacheConfig = config.NewDfcacheConfig()
+	// Initialize cobra
+	dependency.InitCobra(rootCmd, false, dfcacheConfig)
+
+	// Bind more cache specific persistent flags
+	flags := rootCmd.PersistentFlags()
+
+	flags.StringP("cid", "i", "", "content or cache ID, e.g. sha256 digest of the content")
+	flags.StringP("tag", "t", "", "different tags for the same cid will be recognized as different  files in P2P network")
+	flags.Duration("timeout", dfcacheConfig.Timeout, "Timeout for this cache operation, 0 is infinite")
+	flags.String("callsystem", dfcacheConfig.CallSystem, "The caller name which is mainly used for statistics and access control")
+	flags.String("workhome", dfcacheConfig.WorkHome, "Dfcache working directory")
+	flags.String("logdir", dfcacheConfig.LogDir, "Dfcache log directory")
+
+	// Bind common flags
+	if err := viper.BindPFlags(flags); err != nil {
+		panic(errors.Wrap(err, "bind cache common flags to viper"))
+	}
+
+	initStat()
+	initImport()
+	initExport()
+	initDelete()
+}
+
+func initDfcacheDfpath(cfg *config.CacheOption) (dfpath.Dfpath, error) {
+	options := []dfpath.Option{}
+	if cfg.WorkHome != "" {
+		options = append(options, dfpath.WithWorkHome(cfg.WorkHome))
+	}
+
+	if cfg.LogDir != "" {
+		options = append(options, dfpath.WithLogDir(cfg.LogDir))
+	}
+
+	return dfpath.New(options...)
+}
+
+// runDfcache does some init operations and starts to download.
+func runDfcacheSubcmd(cmdName string, args []string) error {
+	var (
+		daemonClient client.DaemonClient
+		err          error
+	)
+
+	// Initialize daemon dfpath
+	d, err := initDfcacheDfpath(dfcacheConfig)
+	if err != nil {
+		return err
+	}
+
+	// Initialize logger
+	if err := logger.InitDfcache(dfcacheConfig.Console, d.LogDir()); err != nil {
+		return errors.Wrap(err, "init client dfcache logger")
+	}
+	logger.Infof("Version:\n%s", version.Version())
+
+	// Convert config
+	if err := dfcacheConfig.Convert(cmdName, args); err != nil {
+		return err
+	}
+
+	// Validate config
+	if err := dfcacheConfig.Validate(cmdName); err != nil {
+		return err
+	}
+
+	// Dfcache config values
+	s, _ := yaml.Marshal(dfcacheConfig)
+	logger.Infof("client dfcache configuration:\n%s", string(s))
+
+	ff := dependency.InitMonitor(dfcacheConfig.PProfPort, dfcacheConfig.Telemetry)
+	defer ff()
+
+	if daemonClient, err = checkDaemon(d.DaemonSockPath()); err != nil {
+		logger.Errorf("check daemon error: %v", err)
+		return err
+	}
+
+	runCmd := runStat
+	switch cmdName {
+	case config.CmdStat:
+		runCmd = runStat
+	case config.CmdImport:
+		runCmd = runImport
+	case config.CmdExport:
+		runCmd = runExport
+	case config.CmdDelete:
+		runCmd = runDelete
+	default:
+		msg := fmt.Sprintf("unknown sub-command %s", cmdName)
+		logger.Error(msg)
+		return errors.New(msg)
+	}
+
+	return runCmd(dfcacheConfig, daemonClient)
+}
+
+// checkDaemon checks if daemon is running
+func checkDaemon(daemonSockPath string) (client.DaemonClient, error) {
+	target := dfnet.NetAddr{Type: dfnet.UNIX, Addr: daemonSockPath}
+	daemonClient, err := client.GetClientByAddr([]dfnet.NetAddr{target})
+	if err != nil {
+		return nil, err
+	}
+
+	if daemonClient.CheckHealth(context.Background(), target) == nil {
+		return daemonClient, nil
+	}
+	return nil, errors.New("daemon not running")
+}
diff --git a/cmd/dfcache/cmd/stat.go b/cmd/dfcache/cmd/stat.go
new file mode 100644
index 00000000..4642ab87
--- /dev/null
+++ b/cmd/dfcache/cmd/stat.go
@@ -0,0 +1,58 @@
+/*
+ *     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 cmd
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+
+	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/client/dfcache"
+	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
+)
+
+const statDesc = "stat checks if a file exists in P2P cache system"
+
+// statCmd represents the cache stat command
+var statCmd = &cobra.Command{
+	Use:                "stat <-i cid>",
+	Short:              statDesc,
+	Long:               statDesc,
+	Args:               cobra.NoArgs,
+	DisableAutoGenTag:  true,
+	SilenceUsage:       true,
+	FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return runDfcacheSubcmd(config.CmdStat, args)
+	},
+}
+
+func initStat() {
+	// Add the command to parent
+	rootCmd.AddCommand(statCmd)
+
+	flags := statCmd.Flags()
+	flags.BoolVarP(&dfcacheConfig.LocalOnly, "local", "l", dfcacheConfig.LocalOnly, "only check task exists locally, and don't check other peers in P2P network")
+	if err := viper.BindPFlags(flags); err != nil {
+		panic(errors.Wrap(err, "bind cache stat flags to viper"))
+	}
+}
+
+func runStat(cfg *config.DfcacheConfig, client client.DaemonClient) error {
+	return dfcache.Stat(cfg, client)
+}
diff --git a/cmd/dfcache/main.go b/cmd/dfcache/main.go
new file mode 100644
index 00000000..00fc1654
--- /dev/null
+++ b/cmd/dfcache/main.go
@@ -0,0 +1,25 @@
+/*
+ *     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 main
+
+import (
+	"d7y.io/dragonfly/v2/cmd/dfcache/cmd"
+)
+
+func main() {
+	cmd.Execute()
+}
diff --git a/hack/build.sh b/hack/build.sh
index 68d1b0e4..9bd8d308 100755
--- a/hack/build.sh
+++ b/hack/build.sh
@@ -6,6 +6,7 @@ set -o pipefail
 
 CDN_BINARY_NAME=cdn
 DFGET_BINARY_NAME=dfget
+DFCACHE_BINARY_NAME=dfcache
 SCHEDULER_BINARY_NAME=scheduler
 MANAGER_BINARY_NAME=manager
 
@@ -56,6 +57,10 @@ build-dfget-local() {
     build-local ${DFGET_BINARY_NAME} dfget
 }
 
+build-dfcache-local() {
+    build-local ${DFCACHE_BINARY_NAME} dfcache
+}
+
 build-scheduler-local() {
     build-local ${SCHEDULER_BINARY_NAME} scheduler
 }
@@ -95,6 +100,10 @@ build-dfget-docker() {
     build-docker ${DFGET_BINARY_NAME} dfget
 }
 
+build-dfcache-docker() {
+    build-docker ${DFCACHE_BINARY_NAME} dfcache
+}
+
 build-scheduler-docker() {
     build-docker ${SCHEDULER_BINARY_NAME} scheduler
 }
@@ -122,6 +131,9 @@ main() {
         dfget)
             build-dfget-docker
             ;;
+        dfcache)
+            build-dfcache-docker
+            ;;
         scheduler)
             build-scheduler-docker
             ;;
@@ -133,6 +145,7 @@ main() {
             ;;
         *)
             build-dfget-docker
+            build-dfcache-docker
             build-cdn-docker
             build-scheduler-docker
             build-manager-docker
@@ -147,6 +160,9 @@ main() {
         dfget)
             build-dfget-local
             ;;
+        dfcache)
+            build-dfcache-local
+            ;;
         scheduler)
             build-scheduler-local
             ;;
@@ -158,6 +174,7 @@ main() {
             ;;
         *)
             build-dfget-local
+            build-dfcache-local
             build-cdn-local
             build-scheduler-local
             build-manager-local
diff --git a/internal/dflog/loginit.go b/internal/dflog/loginit.go
index 7fe9bb93..ac3f4865 100644
--- a/internal/dflog/loginit.go
+++ b/internal/dflog/loginit.go
@@ -219,3 +219,20 @@ func createFileLogger(verbose bool, meta []logInitMeta, logDir string) error {
 	startLoggerSignalHandler()
 	return nil
 }
+
+func InitDfcache(console bool, dir string) error {
+	logDir := filepath.Join(dir, "dfcache")
+
+	var meta = []logInitMeta{
+		{
+			fileName:             CoreLogFileName,
+			setSugaredLoggerFunc: SetCoreLogger,
+		},
+		{
+			fileName:             GrpcLogFileName,
+			setSugaredLoggerFunc: SetGrpcLogger,
+		},
+	}
+
+	return createFileLogger(console, meta, logDir)
+}
diff --git a/internal/util/util.go b/internal/util/util.go
index 7b55324c..e3394cad 100644
--- a/internal/util/util.go
+++ b/internal/util/util.go
@@ -16,6 +16,8 @@
 
 package util
 
+import "math"
+
 const (
 	// DefaultPieceSize 4M
 	DefaultPieceSize = 4 * 1024 * 1024
@@ -40,3 +42,8 @@ func ComputePieceSize(length int64) uint32 {
 	}
 	return uint32(mpSize)
 }
+
+// ComputePieceNum returns piece count with given length and pieceSize
+func ComputePieceNum(length int64, pieceSize uint32) int32 {
+	return int32(math.Ceil(float64(length) / float64(pieceSize)))
+}
diff --git a/pkg/rpc/dfdaemon/client/client.go b/pkg/rpc/dfdaemon/client/client.go
index 47576cbb..4131cc35 100644
--- a/pkg/rpc/dfdaemon/client/client.go
+++ b/pkg/rpc/dfdaemon/client/client.go
@@ -79,6 +79,14 @@ type DaemonClient interface {
 
 	CheckHealth(ctx context.Context, target dfnet.NetAddr, opts ...grpc.CallOption) error
 
+	StatTask(ctx context.Context, req *dfdaemon.StatTaskRequest, opts ...grpc.CallOption) error
+
+	ImportTask(ctx context.Context, req *dfdaemon.ImportTaskRequest, opts ...grpc.CallOption) error
+
+	ExportTask(ctx context.Context, req *dfdaemon.ExportTaskRequest, opts ...grpc.CallOption) error
+
+	DeleteTask(ctx context.Context, req *dfdaemon.DeleteTaskRequest, opts ...grpc.CallOption) error
+
 	Close() error
 }
 
@@ -156,3 +164,46 @@ func (dc *daemonClient) CheckHealth(ctx context.Context, target dfnet.NetAddr, o
 	}
 	return
 }
+
+func (dc *daemonClient) StatTask(ctx context.Context, req *dfdaemon.StatTaskRequest, opts ...grpc.CallOption) error {
+	// StatTask is a latency sensitive operation, so we don't retry & wait for daemon to start,
+	// we assume daemon is already running.
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+
+	client, _, err := dc.getDaemonClient(taskID, false)
+	if err != nil {
+		return err
+	}
+	_, err = client.StatTask(ctx, req, opts...)
+	return err
+}
+
+func (dc *daemonClient) ImportTask(ctx context.Context, req *dfdaemon.ImportTaskRequest, opts ...grpc.CallOption) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	client, _, err := dc.getDaemonClient(taskID, false)
+	if err != nil {
+		return err
+	}
+	_, err = client.ImportTask(ctx, req, opts...)
+	return err
+}
+
+func (dc *daemonClient) ExportTask(ctx context.Context, req *dfdaemon.ExportTaskRequest, opts ...grpc.CallOption) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	client, _, err := dc.getDaemonClient(taskID, false)
+	if err != nil {
+		return err
+	}
+	_, err = client.ExportTask(ctx, req, opts...)
+	return err
+}
+
+func (dc *daemonClient) DeleteTask(ctx context.Context, req *dfdaemon.DeleteTaskRequest, opts ...grpc.CallOption) error {
+	taskID := idgen.TaskID(req.Cid, req.UrlMeta)
+	client, _, err := dc.getDaemonClient(taskID, false)
+	if err != nil {
+		return err
+	}
+	_, err = client.DeleteTask(ctx, req, opts...)
+	return err
+}
diff --git a/pkg/rpc/dfdaemon/dfdaemon.pb.go b/pkg/rpc/dfdaemon/dfdaemon.pb.go
index a03926c3..0980fa74 100644
--- a/pkg/rpc/dfdaemon/dfdaemon.pb.go
+++ b/pkg/rpc/dfdaemon/dfdaemon.pb.go
@@ -259,6 +259,311 @@ func (x *DownResult) GetDone() bool {
 	return false
 }
 
+type StatTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// content/cache id of the task
+	Cid     string        `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"`
+	UrlMeta *base.UrlMeta `protobuf:"bytes,2,opt,name=url_meta,json=urlMeta,proto3" json:"url_meta,omitempty"`
+	// check local cache only
+	LocalOnly bool `protobuf:"varint,3,opt,name=local_only,json=localOnly,proto3" json:"local_only,omitempty"`
+}
+
+func (x *StatTaskRequest) Reset() {
+	*x = StatTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StatTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StatTaskRequest) ProtoMessage() {}
+
+func (x *StatTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StatTaskRequest.ProtoReflect.Descriptor instead.
+func (*StatTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *StatTaskRequest) GetCid() string {
+	if x != nil {
+		return x.Cid
+	}
+	return ""
+}
+
+func (x *StatTaskRequest) GetUrlMeta() *base.UrlMeta {
+	if x != nil {
+		return x.UrlMeta
+	}
+	return nil
+}
+
+func (x *StatTaskRequest) GetLocalOnly() bool {
+	if x != nil {
+		return x.LocalOnly
+	}
+	return false
+}
+
+type ImportTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// content/cache id of the task
+	Cid     string        `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"`
+	UrlMeta *base.UrlMeta `protobuf:"bytes,2,opt,name=url_meta,json=urlMeta,proto3" json:"url_meta,omitempty"`
+	// the file to be imported
+	Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"`
+}
+
+func (x *ImportTaskRequest) Reset() {
+	*x = ImportTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ImportTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ImportTaskRequest) ProtoMessage() {}
+
+func (x *ImportTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ImportTaskRequest.ProtoReflect.Descriptor instead.
+func (*ImportTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ImportTaskRequest) GetCid() string {
+	if x != nil {
+		return x.Cid
+	}
+	return ""
+}
+
+func (x *ImportTaskRequest) GetUrlMeta() *base.UrlMeta {
+	if x != nil {
+		return x.UrlMeta
+	}
+	return nil
+}
+
+func (x *ImportTaskRequest) GetPath() string {
+	if x != nil {
+		return x.Path
+	}
+	return ""
+}
+
+type ExportTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// download cache file identified by this cid
+	Cid string `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"`
+	// output path of downloaded file
+	Output string `protobuf:"bytes,2,opt,name=output,proto3" json:"output,omitempty"`
+	// timeout duration
+	Timeout uint64 `protobuf:"varint,3,opt,name=timeout,proto3" json:"timeout,omitempty"`
+	// rate limit in bytes per second
+	Limit   float64       `protobuf:"fixed64,4,opt,name=limit,proto3" json:"limit,omitempty"`
+	UrlMeta *base.UrlMeta `protobuf:"bytes,5,opt,name=url_meta,json=urlMeta,proto3" json:"url_meta,omitempty"`
+	// call system
+	Callsystem string `protobuf:"bytes,6,opt,name=callsystem,proto3" json:"callsystem,omitempty"`
+	// user id
+	Uid int64 `protobuf:"varint,7,opt,name=uid,proto3" json:"uid,omitempty"`
+	// group id
+	Gid int64 `protobuf:"varint,8,opt,name=gid,proto3" json:"gid,omitempty"`
+	// only export from local storage
+	LocalOnly bool `protobuf:"varint,9,opt,name=local_only,json=localOnly,proto3" json:"local_only,omitempty"`
+}
+
+func (x *ExportTaskRequest) Reset() {
+	*x = ExportTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ExportTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExportTaskRequest) ProtoMessage() {}
+
+func (x *ExportTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ExportTaskRequest.ProtoReflect.Descriptor instead.
+func (*ExportTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ExportTaskRequest) GetCid() string {
+	if x != nil {
+		return x.Cid
+	}
+	return ""
+}
+
+func (x *ExportTaskRequest) GetOutput() string {
+	if x != nil {
+		return x.Output
+	}
+	return ""
+}
+
+func (x *ExportTaskRequest) GetTimeout() uint64 {
+	if x != nil {
+		return x.Timeout
+	}
+	return 0
+}
+
+func (x *ExportTaskRequest) GetLimit() float64 {
+	if x != nil {
+		return x.Limit
+	}
+	return 0
+}
+
+func (x *ExportTaskRequest) GetUrlMeta() *base.UrlMeta {
+	if x != nil {
+		return x.UrlMeta
+	}
+	return nil
+}
+
+func (x *ExportTaskRequest) GetCallsystem() string {
+	if x != nil {
+		return x.Callsystem
+	}
+	return ""
+}
+
+func (x *ExportTaskRequest) GetUid() int64 {
+	if x != nil {
+		return x.Uid
+	}
+	return 0
+}
+
+func (x *ExportTaskRequest) GetGid() int64 {
+	if x != nil {
+		return x.Gid
+	}
+	return 0
+}
+
+func (x *ExportTaskRequest) GetLocalOnly() bool {
+	if x != nil {
+		return x.LocalOnly
+	}
+	return false
+}
+
+type DeleteTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// content/cache id of the task
+	Cid     string        `protobuf:"bytes,1,opt,name=cid,proto3" json:"cid,omitempty"`
+	UrlMeta *base.UrlMeta `protobuf:"bytes,2,opt,name=url_meta,json=urlMeta,proto3" json:"url_meta,omitempty"`
+}
+
+func (x *DeleteTaskRequest) Reset() {
+	*x = DeleteTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DeleteTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DeleteTaskRequest) ProtoMessage() {}
+
+func (x *DeleteTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DeleteTaskRequest.ProtoReflect.Descriptor instead.
+func (*DeleteTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *DeleteTaskRequest) GetCid() string {
+	if x != nil {
+		return x.Cid
+	}
+	return ""
+}
+
+func (x *DeleteTaskRequest) GetUrlMeta() *base.UrlMeta {
+	if x != nil {
+		return x.UrlMeta
+	}
+	return nil
+}
+
 var File_pkg_rpc_dfdaemon_dfdaemon_proto protoreflect.FileDescriptor
 
 var file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDesc = []byte{
@@ -306,8 +611,47 @@ var file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDesc = []byte{
 	0x65, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,
 	0x04, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x32, 0x02, 0x28, 0x00, 0x52, 0x0f, 0x63, 0x6f, 0x6d, 0x70,
 	0x6c, 0x65, 0x74, 0x65, 0x64, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x64,
-	0x6f, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x32,
-	0xff, 0x01, 0x0a, 0x06, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x08, 0x44, 0x6f,
+	0x6f, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x64, 0x6f, 0x6e, 0x65, 0x22,
+	0x75, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42,
+	0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x28, 0x0a,
+	0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07,
+	0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+	0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x6f, 0x63,
+	0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x75, 0x0a, 0x11, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
+	0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x63,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
+	0x01, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65,
+	0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e,
+	0x55, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61,
+	0x12, 0x1b, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07,
+	0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0xa5, 0x02,
+	0x0a, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x1f,
+	0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07,
+	0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12,
+	0x21, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
+	0x42, 0x07, 0xfa, 0x42, 0x04, 0x32, 0x02, 0x28, 0x00, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f,
+	0x75, 0x74, 0x12, 0x24, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x01, 0x42, 0x0e, 0xfa, 0x42, 0x0b, 0x12, 0x09, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x28, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f,
+	0x6d, 0x65, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73,
+	0x65, 0x2e, 0x55, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x75, 0x72, 0x6c, 0x4d, 0x65,
+	0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x79, 0x73, 0x74,
+	0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52,
+	0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,
+	0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61,
+	0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x58, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54,
+	0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
+	0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74,
+	0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55,
+	0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x32,
+	0x87, 0x04, 0x0a, 0x06, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x08, 0x44, 0x6f,
 	0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f,
 	0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e,
 	0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73,
@@ -323,10 +667,26 @@ var file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDesc = []byte{
 	0x6b, 0x73, 0x12, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x54,
 	0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x62, 0x61, 0x73,
 	0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x01, 0x30,
-	0x01, 0x42, 0x26, 0x5a, 0x24, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67,
-	0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63,
-	0x2f, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x01, 0x12, 0x3d, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x19, 0x2e,
+	0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73,
+	0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
+	0x12, 0x41, 0x0a, 0x0a, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1b,
+	0x2e, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74,
+	0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
+	0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x61, 0x73,
+	0x6b, 0x12, 0x1b, 0x2e, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x78, 0x70,
+	0x6f, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
+	0x54, 0x61, 0x73, 0x6b, 0x12, 0x1b, 0x2e, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x26, 0x5a, 0x24, 0x64, 0x37, 0x79,
+	0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32,
+	0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x66, 0x64, 0x61, 0x65, 0x6d, 0x6f,
+	0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -341,30 +701,46 @@ func file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescGZIP() []byte {
 	return file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDescData
 }
 
-var file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_pkg_rpc_dfdaemon_dfdaemon_proto_goTypes = []interface{}{
 	(*DownRequest)(nil),           // 0: dfdaemon.DownRequest
 	(*DownResult)(nil),            // 1: dfdaemon.DownResult
-	(*base.UrlMeta)(nil),          // 2: base.UrlMeta
-	(*base.PieceTaskRequest)(nil), // 3: base.PieceTaskRequest
-	(*emptypb.Empty)(nil),         // 4: google.protobuf.Empty
-	(*base.PiecePacket)(nil),      // 5: base.PiecePacket
+	(*StatTaskRequest)(nil),       // 2: dfdaemon.StatTaskRequest
+	(*ImportTaskRequest)(nil),     // 3: dfdaemon.ImportTaskRequest
+	(*ExportTaskRequest)(nil),     // 4: dfdaemon.ExportTaskRequest
+	(*DeleteTaskRequest)(nil),     // 5: dfdaemon.DeleteTaskRequest
+	(*base.UrlMeta)(nil),          // 6: base.UrlMeta
+	(*base.PieceTaskRequest)(nil), // 7: base.PieceTaskRequest
+	(*emptypb.Empty)(nil),         // 8: google.protobuf.Empty
+	(*base.PiecePacket)(nil),      // 9: base.PiecePacket
 }
 var file_pkg_rpc_dfdaemon_dfdaemon_proto_depIdxs = []int32{
-	2, // 0: dfdaemon.DownRequest.url_meta:type_name -> base.UrlMeta
-	0, // 1: dfdaemon.Daemon.Download:input_type -> dfdaemon.DownRequest
-	3, // 2: dfdaemon.Daemon.GetPieceTasks:input_type -> base.PieceTaskRequest
-	4, // 3: dfdaemon.Daemon.CheckHealth:input_type -> google.protobuf.Empty
-	3, // 4: dfdaemon.Daemon.SyncPieceTasks:input_type -> base.PieceTaskRequest
-	1, // 5: dfdaemon.Daemon.Download:output_type -> dfdaemon.DownResult
-	5, // 6: dfdaemon.Daemon.GetPieceTasks:output_type -> base.PiecePacket
-	4, // 7: dfdaemon.Daemon.CheckHealth:output_type -> google.protobuf.Empty
-	5, // 8: dfdaemon.Daemon.SyncPieceTasks:output_type -> base.PiecePacket
-	5, // [5:9] is the sub-list for method output_type
-	1, // [1:5] is the sub-list for method input_type
-	1, // [1:1] is the sub-list for extension type_name
-	1, // [1:1] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
+	6,  // 0: dfdaemon.DownRequest.url_meta:type_name -> base.UrlMeta
+	6,  // 1: dfdaemon.StatTaskRequest.url_meta:type_name -> base.UrlMeta
+	6,  // 2: dfdaemon.ImportTaskRequest.url_meta:type_name -> base.UrlMeta
+	6,  // 3: dfdaemon.ExportTaskRequest.url_meta:type_name -> base.UrlMeta
+	6,  // 4: dfdaemon.DeleteTaskRequest.url_meta:type_name -> base.UrlMeta
+	0,  // 5: dfdaemon.Daemon.Download:input_type -> dfdaemon.DownRequest
+	7,  // 6: dfdaemon.Daemon.GetPieceTasks:input_type -> base.PieceTaskRequest
+	8,  // 7: dfdaemon.Daemon.CheckHealth:input_type -> google.protobuf.Empty
+	7,  // 8: dfdaemon.Daemon.SyncPieceTasks:input_type -> base.PieceTaskRequest
+	2,  // 9: dfdaemon.Daemon.StatTask:input_type -> dfdaemon.StatTaskRequest
+	3,  // 10: dfdaemon.Daemon.ImportTask:input_type -> dfdaemon.ImportTaskRequest
+	4,  // 11: dfdaemon.Daemon.ExportTask:input_type -> dfdaemon.ExportTaskRequest
+	5,  // 12: dfdaemon.Daemon.DeleteTask:input_type -> dfdaemon.DeleteTaskRequest
+	1,  // 13: dfdaemon.Daemon.Download:output_type -> dfdaemon.DownResult
+	9,  // 14: dfdaemon.Daemon.GetPieceTasks:output_type -> base.PiecePacket
+	8,  // 15: dfdaemon.Daemon.CheckHealth:output_type -> google.protobuf.Empty
+	9,  // 16: dfdaemon.Daemon.SyncPieceTasks:output_type -> base.PiecePacket
+	8,  // 17: dfdaemon.Daemon.StatTask:output_type -> google.protobuf.Empty
+	8,  // 18: dfdaemon.Daemon.ImportTask:output_type -> google.protobuf.Empty
+	8,  // 19: dfdaemon.Daemon.ExportTask:output_type -> google.protobuf.Empty
+	8,  // 20: dfdaemon.Daemon.DeleteTask:output_type -> google.protobuf.Empty
+	13, // [13:21] is the sub-list for method output_type
+	5,  // [5:13] is the sub-list for method input_type
+	5,  // [5:5] is the sub-list for extension type_name
+	5,  // [5:5] is the sub-list for extension extendee
+	0,  // [0:5] is the sub-list for field type_name
 }
 
 func init() { file_pkg_rpc_dfdaemon_dfdaemon_proto_init() }
@@ -397,6 +773,54 @@ func file_pkg_rpc_dfdaemon_dfdaemon_proto_init() {
 				return nil
 			}
 		}
+		file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StatTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ImportTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExportTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_dfdaemon_dfdaemon_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DeleteTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -404,7 +828,7 @@ func file_pkg_rpc_dfdaemon_dfdaemon_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_pkg_rpc_dfdaemon_dfdaemon_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   2,
+			NumMessages:   6,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
@@ -438,6 +862,14 @@ type DaemonClient interface {
 	CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
 	// Sync piece tasks with other peers
 	SyncPieceTasks(ctx context.Context, opts ...grpc.CallOption) (Daemon_SyncPieceTasksClient, error)
+	// Check if given task exists in P2P cache system
+	StatTask(ctx context.Context, in *StatTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
+	// Import the given file into P2P cache system
+	ImportTask(ctx context.Context, in *ImportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
+	// Export or download file from P2P cache system
+	ExportTask(ctx context.Context, in *ExportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
+	// Delete file from P2P cache system
+	DeleteTask(ctx context.Context, in *DeleteTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
 }
 
 type daemonClient struct {
@@ -529,6 +961,42 @@ func (x *daemonSyncPieceTasksClient) Recv() (*base.PiecePacket, error) {
 	return m, nil
 }
 
+func (c *daemonClient) StatTask(ctx context.Context, in *StatTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	out := new(emptypb.Empty)
+	err := c.cc.Invoke(ctx, "/dfdaemon.Daemon/StatTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *daemonClient) ImportTask(ctx context.Context, in *ImportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	out := new(emptypb.Empty)
+	err := c.cc.Invoke(ctx, "/dfdaemon.Daemon/ImportTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *daemonClient) ExportTask(ctx context.Context, in *ExportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	out := new(emptypb.Empty)
+	err := c.cc.Invoke(ctx, "/dfdaemon.Daemon/ExportTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *daemonClient) DeleteTask(ctx context.Context, in *DeleteTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	out := new(emptypb.Empty)
+	err := c.cc.Invoke(ctx, "/dfdaemon.Daemon/DeleteTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // DaemonServer is the server API for Daemon service.
 type DaemonServer interface {
 	// Trigger client to download file
@@ -539,6 +1007,14 @@ type DaemonServer interface {
 	CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
 	// Sync piece tasks with other peers
 	SyncPieceTasks(Daemon_SyncPieceTasksServer) error
+	// Check if given task exists in P2P cache system
+	StatTask(context.Context, *StatTaskRequest) (*emptypb.Empty, error)
+	// Import the given file into P2P cache system
+	ImportTask(context.Context, *ImportTaskRequest) (*emptypb.Empty, error)
+	// Export or download file from P2P cache system
+	ExportTask(context.Context, *ExportTaskRequest) (*emptypb.Empty, error)
+	// Delete file from P2P cache system
+	DeleteTask(context.Context, *DeleteTaskRequest) (*emptypb.Empty, error)
 }
 
 // UnimplementedDaemonServer can be embedded to have forward compatible implementations.
@@ -557,6 +1033,18 @@ func (*UnimplementedDaemonServer) CheckHealth(context.Context, *emptypb.Empty) (
 func (*UnimplementedDaemonServer) SyncPieceTasks(Daemon_SyncPieceTasksServer) error {
 	return status.Errorf(codes.Unimplemented, "method SyncPieceTasks not implemented")
 }
+func (*UnimplementedDaemonServer) StatTask(context.Context, *StatTaskRequest) (*emptypb.Empty, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method StatTask not implemented")
+}
+func (*UnimplementedDaemonServer) ImportTask(context.Context, *ImportTaskRequest) (*emptypb.Empty, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ImportTask not implemented")
+}
+func (*UnimplementedDaemonServer) ExportTask(context.Context, *ExportTaskRequest) (*emptypb.Empty, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ExportTask not implemented")
+}
+func (*UnimplementedDaemonServer) DeleteTask(context.Context, *DeleteTaskRequest) (*emptypb.Empty, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DeleteTask not implemented")
+}
 
 func RegisterDaemonServer(s *grpc.Server, srv DaemonServer) {
 	s.RegisterService(&_Daemon_serviceDesc, srv)
@@ -645,6 +1133,78 @@ func (x *daemonSyncPieceTasksServer) Recv() (*base.PieceTaskRequest, error) {
 	return m, nil
 }
 
+func _Daemon_StatTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(StatTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DaemonServer).StatTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/dfdaemon.Daemon/StatTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DaemonServer).StatTask(ctx, req.(*StatTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Daemon_ImportTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ImportTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DaemonServer).ImportTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/dfdaemon.Daemon/ImportTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DaemonServer).ImportTask(ctx, req.(*ImportTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Daemon_ExportTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ExportTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DaemonServer).ExportTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/dfdaemon.Daemon/ExportTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DaemonServer).ExportTask(ctx, req.(*ExportTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Daemon_DeleteTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DeleteTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DaemonServer).DeleteTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/dfdaemon.Daemon/DeleteTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DaemonServer).DeleteTask(ctx, req.(*DeleteTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Daemon_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "dfdaemon.Daemon",
 	HandlerType: (*DaemonServer)(nil),
@@ -657,6 +1217,22 @@ var _Daemon_serviceDesc = grpc.ServiceDesc{
 			MethodName: "CheckHealth",
 			Handler:    _Daemon_CheckHealth_Handler,
 		},
+		{
+			MethodName: "StatTask",
+			Handler:    _Daemon_StatTask_Handler,
+		},
+		{
+			MethodName: "ImportTask",
+			Handler:    _Daemon_ImportTask_Handler,
+		},
+		{
+			MethodName: "ExportTask",
+			Handler:    _Daemon_ExportTask_Handler,
+		},
+		{
+			MethodName: "DeleteTask",
+			Handler:    _Daemon_DeleteTask_Handler,
+		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
diff --git a/pkg/rpc/dfdaemon/dfdaemon.pb.validate.go b/pkg/rpc/dfdaemon/dfdaemon.pb.validate.go
index 844285bb..2dff0a20 100644
--- a/pkg/rpc/dfdaemon/dfdaemon.pb.validate.go
+++ b/pkg/rpc/dfdaemon/dfdaemon.pb.validate.go
@@ -274,3 +274,375 @@ var _ interface {
 	Cause() error
 	ErrorName() string
 } = DownResultValidationError{}
+
+// Validate checks the field values on StatTaskRequest with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *StatTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetCid()) < 1 {
+		return StatTaskRequestValidationError{
+			field:  "Cid",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if v, ok := interface{}(m.GetUrlMeta()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return StatTaskRequestValidationError{
+				field:  "UrlMeta",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	// no validation rules for LocalOnly
+
+	return nil
+}
+
+// StatTaskRequestValidationError is the validation error returned by
+// StatTaskRequest.Validate if the designated constraints aren't met.
+type StatTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e StatTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e StatTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e StatTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e StatTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e StatTaskRequestValidationError) ErrorName() string { return "StatTaskRequestValidationError" }
+
+// Error satisfies the builtin error interface
+func (e StatTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sStatTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = StatTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = StatTaskRequestValidationError{}
+
+// Validate checks the field values on ImportTaskRequest with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *ImportTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetCid()) < 1 {
+		return ImportTaskRequestValidationError{
+			field:  "Cid",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if v, ok := interface{}(m.GetUrlMeta()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return ImportTaskRequestValidationError{
+				field:  "UrlMeta",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	if utf8.RuneCountInString(m.GetPath()) < 1 {
+		return ImportTaskRequestValidationError{
+			field:  "Path",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	return nil
+}
+
+// ImportTaskRequestValidationError is the validation error returned by
+// ImportTaskRequest.Validate if the designated constraints aren't met.
+type ImportTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e ImportTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ImportTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ImportTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ImportTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ImportTaskRequestValidationError) ErrorName() string {
+	return "ImportTaskRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e ImportTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sImportTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = ImportTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = ImportTaskRequestValidationError{}
+
+// Validate checks the field values on ExportTaskRequest with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *ExportTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetCid()) < 1 {
+		return ExportTaskRequestValidationError{
+			field:  "Cid",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if utf8.RuneCountInString(m.GetOutput()) < 1 {
+		return ExportTaskRequestValidationError{
+			field:  "Output",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if m.GetTimeout() < 0 {
+		return ExportTaskRequestValidationError{
+			field:  "Timeout",
+			reason: "value must be greater than or equal to 0",
+		}
+	}
+
+	if m.GetLimit() < 0 {
+		return ExportTaskRequestValidationError{
+			field:  "Limit",
+			reason: "value must be greater than or equal to 0",
+		}
+	}
+
+	if v, ok := interface{}(m.GetUrlMeta()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return ExportTaskRequestValidationError{
+				field:  "UrlMeta",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	// no validation rules for Callsystem
+
+	// no validation rules for Uid
+
+	// no validation rules for Gid
+
+	// no validation rules for LocalOnly
+
+	return nil
+}
+
+// ExportTaskRequestValidationError is the validation error returned by
+// ExportTaskRequest.Validate if the designated constraints aren't met.
+type ExportTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e ExportTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ExportTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ExportTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ExportTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ExportTaskRequestValidationError) ErrorName() string {
+	return "ExportTaskRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e ExportTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sExportTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = ExportTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = ExportTaskRequestValidationError{}
+
+// Validate checks the field values on DeleteTaskRequest with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *DeleteTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetCid()) < 1 {
+		return DeleteTaskRequestValidationError{
+			field:  "Cid",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if v, ok := interface{}(m.GetUrlMeta()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return DeleteTaskRequestValidationError{
+				field:  "UrlMeta",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	return nil
+}
+
+// DeleteTaskRequestValidationError is the validation error returned by
+// DeleteTaskRequest.Validate if the designated constraints aren't met.
+type DeleteTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e DeleteTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e DeleteTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e DeleteTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e DeleteTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e DeleteTaskRequestValidationError) ErrorName() string {
+	return "DeleteTaskRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e DeleteTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sDeleteTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = DeleteTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = DeleteTaskRequestValidationError{}
diff --git a/pkg/rpc/dfdaemon/dfdaemon.proto b/pkg/rpc/dfdaemon/dfdaemon.proto
index 5ee9db3a..c2bedc31 100644
--- a/pkg/rpc/dfdaemon/dfdaemon.proto
+++ b/pkg/rpc/dfdaemon/dfdaemon.proto
@@ -57,6 +57,48 @@ message DownResult{
   bool done = 5;
 }
 
+message StatTaskRequest{
+  // content/cache id of the task
+  string cid = 1 [(validate.rules).string.min_len = 1];
+  base.UrlMeta url_meta = 2;
+  // check local cache only
+  bool local_only = 3;
+}
+
+message ImportTaskRequest{
+  // content/cache id of the task
+  string cid = 1 [(validate.rules).string.min_len = 1];
+  base.UrlMeta url_meta = 2;
+  // the file to be imported
+  string path = 3 [(validate.rules).string.min_len = 1];
+}
+
+message ExportTaskRequest{
+  // download cache file identified by this cid
+  string cid = 1 [(validate.rules).string.min_len = 1];
+  // output path of downloaded file
+  string output = 2 [(validate.rules).string.min_len = 1];
+  // timeout duration
+  uint64 timeout = 3 [(validate.rules).uint64.gte = 0];
+  // rate limit in bytes per second
+  double limit = 4 [(validate.rules).double.gte = 0];
+  base.UrlMeta url_meta = 5;
+  // call system
+  string callsystem = 6;
+  // user id
+  int64 uid = 7;
+  // group id
+  int64 gid = 8;
+  // only export from local storage
+  bool local_only = 9;
+}
+
+message DeleteTaskRequest{
+  // content/cache id of the task
+  string cid = 1 [(validate.rules).string.min_len = 1];
+  base.UrlMeta url_meta = 2;
+}
+
 // Daemon Client RPC Service
 service Daemon{
   // Trigger client to download file
@@ -67,4 +109,12 @@ service Daemon{
   rpc CheckHealth(google.protobuf.Empty)returns(google.protobuf.Empty);
   // Sync piece tasks with other peers
   rpc SyncPieceTasks(stream base.PieceTaskRequest)returns(stream base.PiecePacket);
+  // Check if given task exists in P2P cache system
+  rpc StatTask(StatTaskRequest) returns(google.protobuf.Empty);
+  // Import the given file into P2P cache system
+  rpc ImportTask(ImportTaskRequest) returns(google.protobuf.Empty);
+  // Export or download file from P2P cache system
+  rpc ExportTask(ExportTaskRequest) returns(google.protobuf.Empty);
+  // Delete file from P2P cache system
+  rpc DeleteTask(DeleteTaskRequest) returns(google.protobuf.Empty);
 }
diff --git a/pkg/rpc/dfdaemon/server/server.go b/pkg/rpc/dfdaemon/server/server.go
index 954507a4..c925122d 100644
--- a/pkg/rpc/dfdaemon/server/server.go
+++ b/pkg/rpc/dfdaemon/server/server.go
@@ -43,6 +43,14 @@ type DaemonServer interface {
 	SyncPieceTasks(dfdaemon.Daemon_SyncPieceTasksServer) error
 	// CheckHealth check daemon health
 	CheckHealth(context.Context) error
+	// Check if the given task exists in P2P cache system
+	StatTask(context.Context, *dfdaemon.StatTaskRequest) error
+	// Import the given file into P2P cache system
+	ImportTask(context.Context, *dfdaemon.ImportTaskRequest) error
+	// Export or download file from P2P cache system
+	ExportTask(context.Context, *dfdaemon.ExportTaskRequest) error
+	// Delete file from P2P cache system
+	DeleteTask(context.Context, *dfdaemon.DeleteTaskRequest) error
 }
 
 type proxy struct {
@@ -100,6 +108,22 @@ func (p *proxy) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.E
 	return new(emptypb.Empty), p.server.CheckHealth(ctx)
 }
 
+func (p *proxy) StatTask(ctx context.Context, req *dfdaemon.StatTaskRequest) (*emptypb.Empty, error) {
+	return new(emptypb.Empty), p.server.StatTask(ctx, req)
+}
+
+func (p *proxy) ImportTask(ctx context.Context, req *dfdaemon.ImportTaskRequest) (*emptypb.Empty, error) {
+	return new(emptypb.Empty), p.server.ImportTask(ctx, req)
+}
+
+func (p *proxy) ExportTask(ctx context.Context, req *dfdaemon.ExportTaskRequest) (*emptypb.Empty, error) {
+	return new(emptypb.Empty), p.server.ExportTask(ctx, req)
+}
+
+func (p *proxy) DeleteTask(ctx context.Context, req *dfdaemon.DeleteTaskRequest) (*emptypb.Empty, error) {
+	return new(emptypb.Empty), p.server.DeleteTask(ctx, req)
+}
+
 func send(drc chan *dfdaemon.DownResult, closeDrc func(), stream dfdaemon.Daemon_DownloadServer, errChan chan error) {
 	err := safe.Call(func() {
 		defer closeDrc()
diff --git a/pkg/rpc/scheduler/client/client.go b/pkg/rpc/scheduler/client/client.go
index 6e015b78..4ce5b3f9 100644
--- a/pkg/rpc/scheduler/client/client.go
+++ b/pkg/rpc/scheduler/client/client.go
@@ -25,6 +25,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
+	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/dfnet"
 	"d7y.io/dragonfly/v2/pkg/idgen"
@@ -47,6 +48,7 @@ func GetClientByAddr(addrs []dfnet.NetAddr, opts ...grpc.DialOption) (SchedulerC
 	return sc, nil
 }
 
+//go:generate mockgen -package mocks -source client.go -destination ./mocks/client_mock.go
 // SchedulerClient see scheduler.SchedulerClient
 type SchedulerClient interface {
 	// RegisterPeerTask register peer task to scheduler
@@ -58,6 +60,10 @@ type SchedulerClient interface {
 
 	LeaveTask(context.Context, *scheduler.PeerTarget, ...grpc.CallOption) error
 
+	StatTask(context.Context, *scheduler.StatTaskRequest, ...grpc.CallOption) (*scheduler.Task, error)
+
+	AnnounceTask(context.Context, *scheduler.AnnounceTaskRequest, ...grpc.CallOption) error
+
 	UpdateState([]dfnet.NetAddr)
 
 	GetState() []dfnet.NetAddr
@@ -241,3 +247,113 @@ func (sc *schedulerClient) LeaveTask(ctx context.Context, pt *scheduler.PeerTarg
 	}
 	return
 }
+
+func (sc *schedulerClient) StatTask(ctx context.Context, req *scheduler.StatTaskRequest, opts ...grpc.CallOption) (*scheduler.Task, error) {
+	var schedulerNode string
+	statFunc := func() (interface{}, error) {
+		var client scheduler.SchedulerClient
+		var err error
+		client, schedulerNode, err = sc.getSchedulerClient(req.TaskId, false)
+		if err != nil {
+			return nil, err
+		}
+		return client.StatTask(ctx, req, opts...)
+	}
+
+	res, err := rpc.ExecuteWithRetry(statFunc, 0.1, 0.5, 2, nil)
+	if err != nil {
+		// DfError means scheduler responds request correctly
+		if _, ok := err.(*dferrors.DfError); ok {
+			return nil, err
+		}
+		logger.WithTaskID(req.TaskId).Errorf("StatTask: stat peer task to scheduler %s failed: %v", schedulerNode, err)
+		return sc.retryStatTask(ctx, req, []string{schedulerNode}, err, opts)
+	}
+
+	return res.(*scheduler.Task), nil
+}
+
+func (sc *schedulerClient) retryStatTask(ctx context.Context, req *scheduler.StatTaskRequest, exclusiveNodes []string, cause error,
+	opts []grpc.CallOption) (*scheduler.Task, error) {
+	if status.Code(cause) == codes.Canceled || status.Code(cause) == codes.DeadlineExceeded {
+		return nil, cause
+	}
+
+	var schedulerNode string
+	preNode, err := sc.TryMigrate(req.TaskId, cause, exclusiveNodes)
+	if err != nil {
+		return nil, cause
+	}
+
+	exclusiveNodes = append(exclusiveNodes, preNode)
+	res, err := rpc.ExecuteWithRetry(func() (interface{}, error) {
+		var client scheduler.SchedulerClient
+		var err error
+		client, schedulerNode, err = sc.getSchedulerClient(req.TaskId, true)
+		if err != nil {
+			return nil, err
+		}
+		return client.StatTask(ctx, req, opts...)
+	}, 0.1, 0.5, 2, cause)
+	if err != nil {
+		// DfError means scheduler responds request correctly
+		if _, ok := err.(*dferrors.DfError); ok {
+			return nil, err
+		}
+		logger.WithTaskID(req.TaskId).Errorf("retryStatTask: stat peer task to scheduler %s failed: %v", schedulerNode, err)
+		return sc.retryStatTask(ctx, req, exclusiveNodes, err, opts)
+	}
+
+	return res.(*scheduler.Task), nil
+}
+
+func (sc *schedulerClient) AnnounceTask(ctx context.Context, req *scheduler.AnnounceTaskRequest,
+	opts ...grpc.CallOption) error {
+	var schedulerNode string
+	annFunc := func() (interface{}, error) {
+		var client scheduler.SchedulerClient
+		var err error
+		client, schedulerNode, err = sc.getSchedulerClient(req.TaskId, false)
+		if err != nil {
+			return nil, err
+		}
+		return client.AnnounceTask(ctx, req, opts...)
+	}
+	_, err := rpc.ExecuteWithRetry(annFunc, 0.2, 2.0, 3, nil)
+	if err != nil {
+		logger.WithTaskID(req.TaskId).Errorf("AnnounceTask: announce task to scheduler %s failed: %v", schedulerNode, err)
+		return sc.retryAnnounceTask(ctx, req, []string{schedulerNode}, err, opts)
+	}
+	logger.Infof("announce task success for taskId: %s, scheduler server node: %s", req.TaskId, schedulerNode)
+	return nil
+}
+
+func (sc *schedulerClient) retryAnnounceTask(ctx context.Context, req *scheduler.AnnounceTaskRequest,
+	exclusiveNodes []string, cause error, opts []grpc.CallOption) error {
+	if status.Code(cause) == codes.Canceled || status.Code(cause) == codes.DeadlineExceeded {
+		return cause
+	}
+	var schedulerNode string
+	preNode, err := sc.TryMigrate(req.TaskId, cause, exclusiveNodes)
+	if err != nil {
+		return cause
+	}
+	exclusiveNodes = append(exclusiveNodes, preNode)
+	_, err = rpc.ExecuteWithRetry(func() (interface{}, error) {
+		var client scheduler.SchedulerClient
+		client, schedulerNode, err = sc.getSchedulerClient(req.TaskId, true)
+		if err != nil {
+			return nil, err
+		}
+		return client.AnnounceTask(ctx, req, opts...)
+	}, 0.2, 2.0, 3, cause)
+	if err != nil {
+		logger.WithTaskID(req.TaskId).Errorf("retryAnnounceTask: announce peer task to scheduler %s failed: %v",
+			schedulerNode, err)
+		return sc.retryAnnounceTask(ctx, req, exclusiveNodes, err, opts)
+	}
+	logger.Infof("announce task success for taskID: %s, scheduler: %s", req.TaskId, schedulerNode)
+	return nil
+}
+
+var _ SchedulerClient = (*schedulerClient)(nil)
diff --git a/pkg/rpc/scheduler/client/mocks/client_mock.go b/pkg/rpc/scheduler/client/mocks/client_mock.go
index e400769d..7807c4f6 100644
--- a/pkg/rpc/scheduler/client/mocks/client_mock.go
+++ b/pkg/rpc/scheduler/client/mocks/client_mock.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/rpc/scheduler/client/client.go
+// Source: client.go
 
 // Package mocks is a generated GoMock package.
 package mocks
@@ -38,6 +38,25 @@ func (m *MockSchedulerClient) EXPECT() *MockSchedulerClientMockRecorder {
 	return m.recorder
 }
 
+// AnnounceTask mocks base method.
+func (m *MockSchedulerClient) AnnounceTask(arg0 context.Context, arg1 *scheduler.AnnounceTaskRequest, arg2 ...grpc.CallOption) error {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "AnnounceTask", varargs...)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AnnounceTask indicates an expected call of AnnounceTask.
+func (mr *MockSchedulerClientMockRecorder) AnnounceTask(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnounceTask", reflect.TypeOf((*MockSchedulerClient)(nil).AnnounceTask), varargs...)
+}
+
 // Close mocks base method.
 func (m *MockSchedulerClient) Close() error {
 	m.ctrl.T.Helper()
@@ -144,6 +163,26 @@ func (mr *MockSchedulerClientMockRecorder) ReportPieceResult(arg0, arg1, arg2 in
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportPieceResult", reflect.TypeOf((*MockSchedulerClient)(nil).ReportPieceResult), varargs...)
 }
 
+// StatTask mocks base method.
+func (m *MockSchedulerClient) StatTask(arg0 context.Context, arg1 *scheduler.StatTaskRequest, arg2 ...grpc.CallOption) (*scheduler.Task, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "StatTask", varargs...)
+	ret0, _ := ret[0].(*scheduler.Task)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockSchedulerClientMockRecorder) StatTask(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockSchedulerClient)(nil).StatTask), varargs...)
+}
+
 // UpdateState mocks base method.
 func (m *MockSchedulerClient) UpdateState(arg0 []dfnet.NetAddr) {
 	m.ctrl.T.Helper()
diff --git a/pkg/rpc/scheduler/client/mocks/peer_packet_stream_mock.go b/pkg/rpc/scheduler/client/mocks/peer_packet_stream_mock.go
index 952de067..8b12ead1 100644
--- a/pkg/rpc/scheduler/client/mocks/peer_packet_stream_mock.go
+++ b/pkg/rpc/scheduler/client/mocks/peer_packet_stream_mock.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/rpc/scheduler/client/peer_packet_stream.go
+// Source: peer_packet_stream.go
 
 // Package mocks is a generated GoMock package.
 package mocks
diff --git a/pkg/rpc/scheduler/client/peer_packet_stream.go b/pkg/rpc/scheduler/client/peer_packet_stream.go
index c463bce4..6f9837d9 100644
--- a/pkg/rpc/scheduler/client/peer_packet_stream.go
+++ b/pkg/rpc/scheduler/client/peer_packet_stream.go
@@ -31,6 +31,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 )
 
+//go:generate mockgen -package mocks -source peer_packet_stream.go -destination ./mocks/peer_packet_stream_mock.go
 type PeerPacketStream interface {
 	Recv() (pp *scheduler.PeerPacket, err error)
 	Send(pr *scheduler.PieceResult) (err error)
diff --git a/pkg/rpc/scheduler/scheduler.pb.go b/pkg/rpc/scheduler/scheduler.pb.go
index 4a31a6f8..3052d9f7 100644
--- a/pkg/rpc/scheduler/scheduler.pb.go
+++ b/pkg/rpc/scheduler/scheduler.pb.go
@@ -557,13 +557,16 @@ type PeerPacket struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
+	// task id
 	TaskId string `protobuf:"bytes,2,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
 	// source peer id
 	SrcPid string `protobuf:"bytes,3,opt,name=src_pid,json=srcPid,proto3" json:"src_pid,omitempty"`
 	// concurrent downloading count from main peer
-	ParallelCount int32                  `protobuf:"varint,4,opt,name=parallel_count,json=parallelCount,proto3" json:"parallel_count,omitempty"`
-	MainPeer      *PeerPacket_DestPeer   `protobuf:"bytes,5,opt,name=main_peer,json=mainPeer,proto3" json:"main_peer,omitempty"`
-	StealPeers    []*PeerPacket_DestPeer `protobuf:"bytes,6,rep,name=steal_peers,json=stealPeers,proto3" json:"steal_peers,omitempty"`
+	ParallelCount int32 `protobuf:"varint,4,opt,name=parallel_count,json=parallelCount,proto3" json:"parallel_count,omitempty"`
+	// main peer
+	MainPeer *PeerPacket_DestPeer `protobuf:"bytes,5,opt,name=main_peer,json=mainPeer,proto3" json:"main_peer,omitempty"`
+	// steal peers
+	StealPeers []*PeerPacket_DestPeer `protobuf:"bytes,6,rep,name=steal_peers,json=stealPeers,proto3" json:"steal_peers,omitempty"`
 	// result code
 	Code base.Code `protobuf:"varint,7,opt,name=code,proto3,enum=base.Code" json:"code,omitempty"`
 }
@@ -647,12 +650,18 @@ type PeerResult struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	TaskId         string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
-	PeerId         string `protobuf:"bytes,2,opt,name=peer_id,json=peerId,proto3" json:"peer_id,omitempty"`
-	SrcIp          string `protobuf:"bytes,3,opt,name=src_ip,json=srcIp,proto3" json:"src_ip,omitempty"`
+	// task id
+	TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
+	// peer id
+	PeerId string `protobuf:"bytes,2,opt,name=peer_id,json=peerId,proto3" json:"peer_id,omitempty"`
+	// source host ip
+	SrcIp string `protobuf:"bytes,3,opt,name=src_ip,json=srcIp,proto3" json:"src_ip,omitempty"`
+	// security domain
 	SecurityDomain string `protobuf:"bytes,4,opt,name=security_domain,json=securityDomain,proto3" json:"security_domain,omitempty"`
-	Idc            string `protobuf:"bytes,5,opt,name=idc,proto3" json:"idc,omitempty"`
-	Url            string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"`
+	// host idc
+	Idc string `protobuf:"bytes,5,opt,name=idc,proto3" json:"idc,omitempty"`
+	// task download url
+	Url string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"`
 	// total content length(byte)
 	ContentLength int64 `protobuf:"varint,7,opt,name=content_length,json=contentLength,proto3" json:"content_length,omitempty"`
 	// total network traffic(byte)
@@ -788,7 +797,9 @@ type PeerTarget struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
+	// task id
 	TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
+	// peer id
 	PeerId string `protobuf:"bytes,2,opt,name=peer_id,json=peerId,proto3" json:"peer_id,omitempty"`
 }
 
@@ -838,6 +849,240 @@ func (x *PeerTarget) GetPeerId() string {
 	return ""
 }
 
+type StatTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// task id
+	TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
+}
+
+func (x *StatTaskRequest) Reset() {
+	*x = StatTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *StatTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*StatTaskRequest) ProtoMessage() {}
+
+func (x *StatTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use StatTaskRequest.ProtoReflect.Descriptor instead.
+func (*StatTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_scheduler_scheduler_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *StatTaskRequest) GetTaskId() string {
+	if x != nil {
+		return x.TaskId
+	}
+	return ""
+}
+
+type Task struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// task id
+	Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	// task type
+	Type int32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"`
+	// task content length
+	ContentLength int64 `protobuf:"varint,3,opt,name=content_length,json=contentLength,proto3" json:"content_length,omitempty"`
+	// task total piece count
+	TotalPieceCount int32 `protobuf:"varint,4,opt,name=total_piece_count,json=totalPieceCount,proto3" json:"total_piece_count,omitempty"`
+	// task state
+	State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"`
+	// task peer count
+	PeerCount int32 `protobuf:"varint,6,opt,name=peer_count,json=peerCount,proto3" json:"peer_count,omitempty"`
+	// task contains available peer
+	HasAvailablePeer bool `protobuf:"varint,7,opt,name=hasAvailablePeer,proto3" json:"hasAvailablePeer,omitempty"`
+}
+
+func (x *Task) Reset() {
+	*x = Task{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Task) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Task) ProtoMessage() {}
+
+func (x *Task) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Task.ProtoReflect.Descriptor instead.
+func (*Task) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_scheduler_scheduler_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *Task) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *Task) GetType() int32 {
+	if x != nil {
+		return x.Type
+	}
+	return 0
+}
+
+func (x *Task) GetContentLength() int64 {
+	if x != nil {
+		return x.ContentLength
+	}
+	return 0
+}
+
+func (x *Task) GetTotalPieceCount() int32 {
+	if x != nil {
+		return x.TotalPieceCount
+	}
+	return 0
+}
+
+func (x *Task) GetState() string {
+	if x != nil {
+		return x.State
+	}
+	return ""
+}
+
+func (x *Task) GetPeerCount() int32 {
+	if x != nil {
+		return x.PeerCount
+	}
+	return 0
+}
+
+func (x *Task) GetHasAvailablePeer() bool {
+	if x != nil {
+		return x.HasAvailablePeer
+	}
+	return false
+}
+
+type AnnounceTaskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// task id
+	TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
+	// cache id
+	Cid string `protobuf:"bytes,2,opt,name=cid,proto3" json:"cid,omitempty"`
+	// task url meta
+	UrlMeta *base.UrlMeta `protobuf:"bytes,3,opt,name=url_meta,json=urlMeta,proto3" json:"url_meta,omitempty"`
+	// peer host info
+	PeerHost *PeerHost `protobuf:"bytes,4,opt,name=peer_host,json=peerHost,proto3" json:"peer_host,omitempty"`
+	// task's piece info
+	PiecePacket *base.PiecePacket `protobuf:"bytes,5,opt,name=piece_packet,json=piecePacket,proto3" json:"piece_packet,omitempty"`
+}
+
+func (x *AnnounceTaskRequest) Reset() {
+	*x = AnnounceTaskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AnnounceTaskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AnnounceTaskRequest) ProtoMessage() {}
+
+func (x *AnnounceTaskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[10]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AnnounceTaskRequest.ProtoReflect.Descriptor instead.
+func (*AnnounceTaskRequest) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_scheduler_scheduler_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *AnnounceTaskRequest) GetTaskId() string {
+	if x != nil {
+		return x.TaskId
+	}
+	return ""
+}
+
+func (x *AnnounceTaskRequest) GetCid() string {
+	if x != nil {
+		return x.Cid
+	}
+	return ""
+}
+
+func (x *AnnounceTaskRequest) GetUrlMeta() *base.UrlMeta {
+	if x != nil {
+		return x.UrlMeta
+	}
+	return nil
+}
+
+func (x *AnnounceTaskRequest) GetPeerHost() *PeerHost {
+	if x != nil {
+		return x.PeerHost
+	}
+	return nil
+}
+
+func (x *AnnounceTaskRequest) GetPiecePacket() *base.PiecePacket {
+	if x != nil {
+		return x.PiecePacket
+	}
+	return nil
+}
+
 type PeerPacket_DestPeer struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -854,7 +1099,7 @@ type PeerPacket_DestPeer struct {
 func (x *PeerPacket_DestPeer) Reset() {
 	*x = PeerPacket_DestPeer{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[8]
+		mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[11]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -867,7 +1112,7 @@ func (x *PeerPacket_DestPeer) String() string {
 func (*PeerPacket_DestPeer) ProtoMessage() {}
 
 func (x *PeerPacket_DestPeer) ProtoReflect() protoreflect.Message {
-	mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[8]
+	mi := &file_pkg_rpc_scheduler_scheduler_proto_msgTypes[11]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1050,28 +1295,72 @@ var file_pkg_rpc_scheduler_scheduler_proto_rawDesc = []byte{
 	0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49,
 	0x64, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
 	0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x70, 0x65, 0x65,
-	0x72, 0x49, 0x64, 0x32, 0x9d, 0x02, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
-	0x72, 0x12, 0x49, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x65, 0x65,
-	0x72, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
-	0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x19, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x52, 0x65,
-	0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x46, 0x0a, 0x11,
-	0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c,
-	0x74, 0x12, 0x16, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x69,
-	0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65,
-	0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
-	0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x65,
-	0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64,
-	0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a,
-	0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-	0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76, 0x65,
-	0x54, 0x61, 0x73, 0x6b, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72,
-	0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
-	0x70, 0x74, 0x79, 0x42, 0x27, 0x5a, 0x25, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72,
-	0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72,
-	0x70, 0x63, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x33,
+	0x72, 0x49, 0x64, 0x22, 0x33, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
+	0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x94, 0x02, 0x0a, 0x04, 0x54, 0x61, 0x73,
+	0x6b, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa,
+	0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x04, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28,
+	0x00, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65,
+	0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42,
+	0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+	0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c,
+	0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x74, 0x6f, 0x74,
+	0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x05,
+	0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04,
+	0x72, 0x02, 0x10, 0x01, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0a, 0x70,
+	0x65, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x42,
+	0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f,
+	0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61,
+	0x62, 0x6c, 0x65, 0x50, 0x65, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x68,
+	0x61, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x65, 0x72, 0x22,
+	0xf8, 0x01, 0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
+	0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69, 0x64,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52,
+	0x03, 0x63, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55, 0x72,
+	0x6c, 0x4d, 0x65, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52,
+	0x07, 0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72,
+	0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x63,
+	0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74,
+	0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x70, 0x69,
+	0x65, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x11, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63,
+	0x6b, 0x65, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0b, 0x70,
+	0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x32, 0x9e, 0x03, 0x0a, 0x09, 0x53,
+	0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x12, 0x49, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69,
+	0x73, 0x74, 0x65, 0x72, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73,
+	0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73,
+	0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64,
+	0x75, 0x6c, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73,
+	0x75, 0x6c, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x69, 0x65,
+	0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64,
+	0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
+	0x1a, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65,
+	0x72, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x10, 0x52,
+	0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12,
+	0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72,
+	0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a,
+	0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x15, 0x2e, 0x73, 0x63,
+	0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x53, 0x74,
+	0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
+	0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x54,
+	0x61, 0x73, 0x6b, 0x12, 0x46, 0x0a, 0x0c, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54,
+	0x61, 0x73, 0x6b, 0x12, 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e,
+	0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x27, 0x5a, 0x25, 0x64,
+	0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f,
+	0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x64,
+	0x75, 0x6c, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1086,7 +1375,7 @@ func file_pkg_rpc_scheduler_scheduler_proto_rawDescGZIP() []byte {
 	return file_pkg_rpc_scheduler_scheduler_proto_rawDescData
 }
 
-var file_pkg_rpc_scheduler_scheduler_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
+var file_pkg_rpc_scheduler_scheduler_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
 var file_pkg_rpc_scheduler_scheduler_proto_goTypes = []interface{}{
 	(*PeerTaskRequest)(nil),     // 0: scheduler.PeerTaskRequest
 	(*RegisterResult)(nil),      // 1: scheduler.RegisterResult
@@ -1096,41 +1385,52 @@ var file_pkg_rpc_scheduler_scheduler_proto_goTypes = []interface{}{
 	(*PeerPacket)(nil),          // 5: scheduler.PeerPacket
 	(*PeerResult)(nil),          // 6: scheduler.PeerResult
 	(*PeerTarget)(nil),          // 7: scheduler.PeerTarget
-	(*PeerPacket_DestPeer)(nil), // 8: scheduler.PeerPacket.DestPeer
-	(*base.UrlMeta)(nil),        // 9: base.UrlMeta
-	(*base.HostLoad)(nil),       // 10: base.HostLoad
-	(base.SizeScope)(0),         // 11: base.SizeScope
-	(*base.PieceInfo)(nil),      // 12: base.PieceInfo
-	(base.Code)(0),              // 13: base.Code
-	(*emptypb.Empty)(nil),       // 14: google.protobuf.Empty
+	(*StatTaskRequest)(nil),     // 8: scheduler.StatTaskRequest
+	(*Task)(nil),                // 9: scheduler.Task
+	(*AnnounceTaskRequest)(nil), // 10: scheduler.AnnounceTaskRequest
+	(*PeerPacket_DestPeer)(nil), // 11: scheduler.PeerPacket.DestPeer
+	(*base.UrlMeta)(nil),        // 12: base.UrlMeta
+	(*base.HostLoad)(nil),       // 13: base.HostLoad
+	(base.SizeScope)(0),         // 14: base.SizeScope
+	(*base.PieceInfo)(nil),      // 15: base.PieceInfo
+	(base.Code)(0),              // 16: base.Code
+	(*base.PiecePacket)(nil),    // 17: base.PiecePacket
+	(*emptypb.Empty)(nil),       // 18: google.protobuf.Empty
 }
 var file_pkg_rpc_scheduler_scheduler_proto_depIdxs = []int32{
-	9,  // 0: scheduler.PeerTaskRequest.url_meta:type_name -> base.UrlMeta
+	12, // 0: scheduler.PeerTaskRequest.url_meta:type_name -> base.UrlMeta
 	3,  // 1: scheduler.PeerTaskRequest.peer_host:type_name -> scheduler.PeerHost
-	10, // 2: scheduler.PeerTaskRequest.host_load:type_name -> base.HostLoad
-	11, // 3: scheduler.RegisterResult.size_scope:type_name -> base.SizeScope
+	13, // 2: scheduler.PeerTaskRequest.host_load:type_name -> base.HostLoad
+	14, // 3: scheduler.RegisterResult.size_scope:type_name -> base.SizeScope
 	2,  // 4: scheduler.RegisterResult.single_piece:type_name -> scheduler.SinglePiece
-	12, // 5: scheduler.SinglePiece.piece_info:type_name -> base.PieceInfo
-	12, // 6: scheduler.PieceResult.piece_info:type_name -> base.PieceInfo
-	13, // 7: scheduler.PieceResult.code:type_name -> base.Code
-	10, // 8: scheduler.PieceResult.host_load:type_name -> base.HostLoad
-	8,  // 9: scheduler.PeerPacket.main_peer:type_name -> scheduler.PeerPacket.DestPeer
-	8,  // 10: scheduler.PeerPacket.steal_peers:type_name -> scheduler.PeerPacket.DestPeer
-	13, // 11: scheduler.PeerPacket.code:type_name -> base.Code
-	13, // 12: scheduler.PeerResult.code:type_name -> base.Code
-	0,  // 13: scheduler.Scheduler.RegisterPeerTask:input_type -> scheduler.PeerTaskRequest
-	4,  // 14: scheduler.Scheduler.ReportPieceResult:input_type -> scheduler.PieceResult
-	6,  // 15: scheduler.Scheduler.ReportPeerResult:input_type -> scheduler.PeerResult
-	7,  // 16: scheduler.Scheduler.LeaveTask:input_type -> scheduler.PeerTarget
-	1,  // 17: scheduler.Scheduler.RegisterPeerTask:output_type -> scheduler.RegisterResult
-	5,  // 18: scheduler.Scheduler.ReportPieceResult:output_type -> scheduler.PeerPacket
-	14, // 19: scheduler.Scheduler.ReportPeerResult:output_type -> google.protobuf.Empty
-	14, // 20: scheduler.Scheduler.LeaveTask:output_type -> google.protobuf.Empty
-	17, // [17:21] is the sub-list for method output_type
-	13, // [13:17] is the sub-list for method input_type
-	13, // [13:13] is the sub-list for extension type_name
-	13, // [13:13] is the sub-list for extension extendee
-	0,  // [0:13] is the sub-list for field type_name
+	15, // 5: scheduler.SinglePiece.piece_info:type_name -> base.PieceInfo
+	15, // 6: scheduler.PieceResult.piece_info:type_name -> base.PieceInfo
+	16, // 7: scheduler.PieceResult.code:type_name -> base.Code
+	13, // 8: scheduler.PieceResult.host_load:type_name -> base.HostLoad
+	11, // 9: scheduler.PeerPacket.main_peer:type_name -> scheduler.PeerPacket.DestPeer
+	11, // 10: scheduler.PeerPacket.steal_peers:type_name -> scheduler.PeerPacket.DestPeer
+	16, // 11: scheduler.PeerPacket.code:type_name -> base.Code
+	16, // 12: scheduler.PeerResult.code:type_name -> base.Code
+	12, // 13: scheduler.AnnounceTaskRequest.url_meta:type_name -> base.UrlMeta
+	3,  // 14: scheduler.AnnounceTaskRequest.peer_host:type_name -> scheduler.PeerHost
+	17, // 15: scheduler.AnnounceTaskRequest.piece_packet:type_name -> base.PiecePacket
+	0,  // 16: scheduler.Scheduler.RegisterPeerTask:input_type -> scheduler.PeerTaskRequest
+	4,  // 17: scheduler.Scheduler.ReportPieceResult:input_type -> scheduler.PieceResult
+	6,  // 18: scheduler.Scheduler.ReportPeerResult:input_type -> scheduler.PeerResult
+	7,  // 19: scheduler.Scheduler.LeaveTask:input_type -> scheduler.PeerTarget
+	8,  // 20: scheduler.Scheduler.StatTask:input_type -> scheduler.StatTaskRequest
+	10, // 21: scheduler.Scheduler.AnnounceTask:input_type -> scheduler.AnnounceTaskRequest
+	1,  // 22: scheduler.Scheduler.RegisterPeerTask:output_type -> scheduler.RegisterResult
+	5,  // 23: scheduler.Scheduler.ReportPieceResult:output_type -> scheduler.PeerPacket
+	18, // 24: scheduler.Scheduler.ReportPeerResult:output_type -> google.protobuf.Empty
+	18, // 25: scheduler.Scheduler.LeaveTask:output_type -> google.protobuf.Empty
+	9,  // 26: scheduler.Scheduler.StatTask:output_type -> scheduler.Task
+	18, // 27: scheduler.Scheduler.AnnounceTask:output_type -> google.protobuf.Empty
+	22, // [22:28] is the sub-list for method output_type
+	16, // [16:22] is the sub-list for method input_type
+	16, // [16:16] is the sub-list for extension type_name
+	16, // [16:16] is the sub-list for extension extendee
+	0,  // [0:16] is the sub-list for field type_name
 }
 
 func init() { file_pkg_rpc_scheduler_scheduler_proto_init() }
@@ -1236,6 +1536,42 @@ func file_pkg_rpc_scheduler_scheduler_proto_init() {
 			}
 		}
 		file_pkg_rpc_scheduler_scheduler_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StatTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_scheduler_scheduler_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Task); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_scheduler_scheduler_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AnnounceTaskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_scheduler_scheduler_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PeerPacket_DestPeer); i {
 			case 0:
 				return &v.state
@@ -1258,7 +1594,7 @@ func file_pkg_rpc_scheduler_scheduler_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_pkg_rpc_scheduler_scheduler_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   9,
+			NumMessages:   12,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
@@ -1294,6 +1630,10 @@ type SchedulerClient interface {
 	ReportPeerResult(ctx context.Context, in *PeerResult, opts ...grpc.CallOption) (*emptypb.Empty, error)
 	// LeaveTask makes the peer leaving from scheduling overlay for the task.
 	LeaveTask(ctx context.Context, in *PeerTarget, opts ...grpc.CallOption) (*emptypb.Empty, error)
+	// Checks if any peer has the given task
+	StatTask(ctx context.Context, in *StatTaskRequest, opts ...grpc.CallOption) (*Task, error)
+	// A peer announces that it has the announced task to other peers
+	AnnounceTask(ctx context.Context, in *AnnounceTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
 }
 
 type schedulerClient struct {
@@ -1362,6 +1702,24 @@ func (c *schedulerClient) LeaveTask(ctx context.Context, in *PeerTarget, opts ..
 	return out, nil
 }
 
+func (c *schedulerClient) StatTask(ctx context.Context, in *StatTaskRequest, opts ...grpc.CallOption) (*Task, error) {
+	out := new(Task)
+	err := c.cc.Invoke(ctx, "/scheduler.Scheduler/StatTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *schedulerClient) AnnounceTask(ctx context.Context, in *AnnounceTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	out := new(emptypb.Empty)
+	err := c.cc.Invoke(ctx, "/scheduler.Scheduler/AnnounceTask", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // SchedulerServer is the server API for Scheduler service.
 type SchedulerServer interface {
 	// RegisterPeerTask registers a peer into one task.
@@ -1374,6 +1732,10 @@ type SchedulerServer interface {
 	ReportPeerResult(context.Context, *PeerResult) (*emptypb.Empty, error)
 	// LeaveTask makes the peer leaving from scheduling overlay for the task.
 	LeaveTask(context.Context, *PeerTarget) (*emptypb.Empty, error)
+	// Checks if any peer has the given task
+	StatTask(context.Context, *StatTaskRequest) (*Task, error)
+	// A peer announces that it has the announced task to other peers
+	AnnounceTask(context.Context, *AnnounceTaskRequest) (*emptypb.Empty, error)
 }
 
 // UnimplementedSchedulerServer can be embedded to have forward compatible implementations.
@@ -1392,6 +1754,12 @@ func (*UnimplementedSchedulerServer) ReportPeerResult(context.Context, *PeerResu
 func (*UnimplementedSchedulerServer) LeaveTask(context.Context, *PeerTarget) (*emptypb.Empty, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method LeaveTask not implemented")
 }
+func (*UnimplementedSchedulerServer) StatTask(context.Context, *StatTaskRequest) (*Task, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method StatTask not implemented")
+}
+func (*UnimplementedSchedulerServer) AnnounceTask(context.Context, *AnnounceTaskRequest) (*emptypb.Empty, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method AnnounceTask not implemented")
+}
 
 func RegisterSchedulerServer(s *grpc.Server, srv SchedulerServer) {
 	s.RegisterService(&_Scheduler_serviceDesc, srv)
@@ -1477,6 +1845,42 @@ func _Scheduler_LeaveTask_Handler(srv interface{}, ctx context.Context, dec func
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Scheduler_StatTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(StatTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SchedulerServer).StatTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/scheduler.Scheduler/StatTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SchedulerServer).StatTask(ctx, req.(*StatTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Scheduler_AnnounceTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(AnnounceTaskRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(SchedulerServer).AnnounceTask(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/scheduler.Scheduler/AnnounceTask",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(SchedulerServer).AnnounceTask(ctx, req.(*AnnounceTaskRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Scheduler_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "scheduler.Scheduler",
 	HandlerType: (*SchedulerServer)(nil),
@@ -1493,6 +1897,14 @@ var _Scheduler_serviceDesc = grpc.ServiceDesc{
 			MethodName: "LeaveTask",
 			Handler:    _Scheduler_LeaveTask_Handler,
 		},
+		{
+			MethodName: "StatTask",
+			Handler:    _Scheduler_StatTask_Handler,
+		},
+		{
+			MethodName: "AnnounceTask",
+			Handler:    _Scheduler_AnnounceTask_Handler,
+		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
diff --git a/pkg/rpc/scheduler/scheduler.pb.validate.go b/pkg/rpc/scheduler/scheduler.pb.validate.go
index 42d28a25..b1589113 100644
--- a/pkg/rpc/scheduler/scheduler.pb.validate.go
+++ b/pkg/rpc/scheduler/scheduler.pb.validate.go
@@ -919,6 +919,311 @@ var _ interface {
 	ErrorName() string
 } = PeerTargetValidationError{}
 
+// Validate checks the field values on StatTaskRequest with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *StatTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetTaskId()) < 1 {
+		return StatTaskRequestValidationError{
+			field:  "TaskId",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	return nil
+}
+
+// StatTaskRequestValidationError is the validation error returned by
+// StatTaskRequest.Validate if the designated constraints aren't met.
+type StatTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e StatTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e StatTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e StatTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e StatTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e StatTaskRequestValidationError) ErrorName() string { return "StatTaskRequestValidationError" }
+
+// Error satisfies the builtin error interface
+func (e StatTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sStatTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = StatTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = StatTaskRequestValidationError{}
+
+// Validate checks the field values on Task with the rules defined in the proto
+// definition for this message. If any rules are violated, an error is returned.
+func (m *Task) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetId()) < 1 {
+		return TaskValidationError{
+			field:  "Id",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if m.GetType() < 0 {
+		return TaskValidationError{
+			field:  "Type",
+			reason: "value must be greater than or equal to 0",
+		}
+	}
+
+	if m.GetContentLength() < 1 {
+		return TaskValidationError{
+			field:  "ContentLength",
+			reason: "value must be greater than or equal to 1",
+		}
+	}
+
+	if m.GetTotalPieceCount() < 1 {
+		return TaskValidationError{
+			field:  "TotalPieceCount",
+			reason: "value must be greater than or equal to 1",
+		}
+	}
+
+	if utf8.RuneCountInString(m.GetState()) < 1 {
+		return TaskValidationError{
+			field:  "State",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if m.GetPeerCount() < 0 {
+		return TaskValidationError{
+			field:  "PeerCount",
+			reason: "value must be greater than or equal to 0",
+		}
+	}
+
+	// no validation rules for HasAvailablePeer
+
+	return nil
+}
+
+// TaskValidationError is the validation error returned by Task.Validate if the
+// designated constraints aren't met.
+type TaskValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e TaskValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e TaskValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e TaskValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e TaskValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e TaskValidationError) ErrorName() string { return "TaskValidationError" }
+
+// Error satisfies the builtin error interface
+func (e TaskValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sTask.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = TaskValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = TaskValidationError{}
+
+// Validate checks the field values on AnnounceTaskRequest with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, an error is returned.
+func (m *AnnounceTaskRequest) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	if utf8.RuneCountInString(m.GetTaskId()) < 1 {
+		return AnnounceTaskRequestValidationError{
+			field:  "TaskId",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if utf8.RuneCountInString(m.GetCid()) < 1 {
+		return AnnounceTaskRequestValidationError{
+			field:  "Cid",
+			reason: "value length must be at least 1 runes",
+		}
+	}
+
+	if m.GetUrlMeta() == nil {
+		return AnnounceTaskRequestValidationError{
+			field:  "UrlMeta",
+			reason: "value is required",
+		}
+	}
+
+	if v, ok := interface{}(m.GetUrlMeta()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return AnnounceTaskRequestValidationError{
+				field:  "UrlMeta",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	if v, ok := interface{}(m.GetPeerHost()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return AnnounceTaskRequestValidationError{
+				field:  "PeerHost",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	if m.GetPiecePacket() == nil {
+		return AnnounceTaskRequestValidationError{
+			field:  "PiecePacket",
+			reason: "value is required",
+		}
+	}
+
+	if v, ok := interface{}(m.GetPiecePacket()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return AnnounceTaskRequestValidationError{
+				field:  "PiecePacket",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
+	return nil
+}
+
+// AnnounceTaskRequestValidationError is the validation error returned by
+// AnnounceTaskRequest.Validate if the designated constraints aren't met.
+type AnnounceTaskRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e AnnounceTaskRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e AnnounceTaskRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e AnnounceTaskRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e AnnounceTaskRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e AnnounceTaskRequestValidationError) ErrorName() string {
+	return "AnnounceTaskRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e AnnounceTaskRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sAnnounceTaskRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = AnnounceTaskRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = AnnounceTaskRequestValidationError{}
+
 // Validate checks the field values on PeerPacket_DestPeer with the rules
 // defined in the proto definition for this message. If any rules are
 // violated, an error is returned.
diff --git a/pkg/rpc/scheduler/scheduler.proto b/pkg/rpc/scheduler/scheduler.proto
index ac4086a1..bed4d8b0 100644
--- a/pkg/rpc/scheduler/scheduler.proto
+++ b/pkg/rpc/scheduler/scheduler.proto
@@ -117,24 +117,32 @@ message PeerPacket{
     string peer_id = 3 [(validate.rules).string.min_len = 1];
   }
 
+  // task id
   string task_id = 2 [(validate.rules).string.min_len = 1];
   // source peer id
   string src_pid = 3 [(validate.rules).string.min_len = 1];
-
   // concurrent downloading count from main peer
   int32 parallel_count = 4 [(validate.rules).int32.gte = 1];
+  // main peer
   DestPeer main_peer = 5;
+  // steal peers
   repeated DestPeer steal_peers = 6;
   // result code
   base.Code code = 7;
 }
 
 message PeerResult{
+  // task id
   string task_id = 1 [(validate.rules).string.min_len = 1];
+  // peer id
   string peer_id = 2 [(validate.rules).string.min_len = 1];
+  // source host ip
   string src_ip = 3 [(validate.rules).string.ip = true];
+  // security domain
   string security_domain = 4;
+  // host idc
   string idc = 5;
+  // task download url
   string url = 6 [(validate.rules).string.uri = true];
   // total content length(byte)
   int64 content_length = 7;
@@ -151,10 +159,47 @@ message PeerResult{
 }
 
 message PeerTarget{
+  // task id
   string task_id = 1 [(validate.rules).string.min_len = 1];
+  // peer id
   string peer_id = 2 [(validate.rules).string.min_len = 1];
 }
 
+message StatTaskRequest{
+  // task id
+  string task_id = 1 [(validate.rules).string.min_len = 1];
+}
+
+message Task{
+  // task id
+  string id = 1 [(validate.rules).string.min_len = 1];
+  // task type
+  int32 type = 2 [(validate.rules).int32.gte = 0];
+  // task content length
+  int64 content_length = 3 [(validate.rules).int64.gte = 1];
+  // task total piece count
+  int32 total_piece_count = 4 [(validate.rules).int32.gte = 1];
+  // task state
+  string state = 5 [(validate.rules).string.min_len = 1];
+  // task peer count
+  int32 peer_count = 6 [(validate.rules).int32.gte = 0];
+  // task contains available peer
+  bool hasAvailablePeer = 7;
+}
+
+message AnnounceTaskRequest{
+  // task id
+  string task_id = 1 [(validate.rules).string.min_len = 1];
+  // cache id
+  string cid = 2 [(validate.rules).string.min_len = 1];
+  // task url meta
+  base.UrlMeta url_meta = 3 [(validate.rules).message.required = true];
+  // peer host info
+  PeerHost peer_host = 4;
+  // task's piece info
+  base.PiecePacket piece_packet = 5 [(validate.rules).message.required = true];
+}
+
 // Scheduler System RPC Service
 service Scheduler{
   // RegisterPeerTask registers a peer into one task.
@@ -170,4 +215,10 @@ service Scheduler{
 
   // LeaveTask makes the peer leaving from scheduling overlay for the task.
   rpc LeaveTask(PeerTarget)returns(google.protobuf.Empty);
+
+  // Checks if any peer has the given task
+  rpc StatTask(StatTaskRequest)returns(Task);
+
+  // A peer announces that it has the announced task to other peers
+  rpc AnnounceTask(AnnounceTaskRequest) returns(google.protobuf.Empty);
 }
diff --git a/scheduler/resource/task.go b/scheduler/resource/task.go
index 60f48783..24a9d1bf 100644
--- a/scheduler/resource/task.go
+++ b/scheduler/resource/task.go
@@ -38,6 +38,17 @@ const (
 	FailedPeerCountLimit = 200
 )
 
+const (
+	// TaskTypeNormal is normal type of task,
+	// normal task is a normal p2p task.
+	TaskTypeNormal = iota
+
+	// TaskTypeDfcache is dfcache type of task,
+	// dfcache task is a cache task, and the task url is fake url.
+	// It can only be used for caching and cannot be downloaded back to source.
+	TaskTypeDfcache
+)
+
 const (
 	// Task has been created but did not start running
 	TaskStatePending = "Pending"
@@ -63,6 +74,16 @@ const (
 	TaskEventDownloadFailed = "DownloadFailed"
 )
 
+// Option is a functional option for task
+type Option func(task *Task)
+
+// WithBackToSourceLimit set BackToSourceLimit for task
+func WithBackToSourceLimit(limit int32) Option {
+	return func(task *Task) {
+		task.BackToSourceLimit.Add(limit)
+	}
+}
+
 type Task struct {
 	// ID is task id
 	ID string
@@ -70,6 +91,9 @@ type Task struct {
 	// URL is task download url
 	URL string
 
+	// Type is task type
+	Type int
+
 	// URLMeta is task download url meta
 	URLMeta *base.UrlMeta
 
@@ -115,14 +139,15 @@ type Task struct {
 }
 
 // New task instance
-func NewTask(id, url string, backToSourceLimit int, meta *base.UrlMeta) *Task {
+func NewTask(id, url string, taskType int, meta *base.UrlMeta, options ...Option) *Task {
 	t := &Task{
 		ID:                id,
 		URL:               url,
+		Type:              taskType,
 		URLMeta:           meta,
 		ContentLength:     atomic.NewInt64(0),
 		TotalPieceCount:   atomic.NewInt32(0),
-		BackToSourceLimit: atomic.NewInt32(int32(backToSourceLimit)),
+		BackToSourceLimit: atomic.NewInt32(0),
 		BackToSourcePeers: set.NewSafeSet(),
 		Pieces:            &sync.Map{},
 		Peers:             &sync.Map{},
@@ -157,6 +182,10 @@ func NewTask(id, url string, backToSourceLimit int, meta *base.UrlMeta) *Task {
 		},
 	)
 
+	for _, opt := range options {
+		opt(t)
+	}
+
 	return t
 }
 
@@ -245,6 +274,12 @@ func (t *Task) LoadCDNPeer() (*Peer, bool) {
 	return nil, false
 }
 
+// IsCDNFailed returns whether the cdn in the task failed
+func (t *Task) IsCDNFailed() bool {
+	cdnPeer, ok := t.LoadCDNPeer()
+	return ok && cdnPeer.FSM.Is(PeerStateFailed)
+}
+
 // LoadPiece return piece for a key
 func (t *Task) LoadPiece(key int32) (*base.PieceInfo, bool) {
 	rawPiece, ok := t.Pieces.Load(key)
@@ -288,7 +323,7 @@ func (t *Task) SizeScope() base.SizeScope {
 
 // CanBackToSource represents whether peer can back-to-source
 func (t *Task) CanBackToSource() bool {
-	return int32(t.BackToSourcePeers.Len()) < t.BackToSourceLimit.Load()
+	return int32(t.BackToSourcePeers.Len()) < t.BackToSourceLimit.Load() && t.Type == TaskTypeNormal
 }
 
 // NotifyPeers notify all peers in the task with the state code
diff --git a/scheduler/rpcserver/rpcserver.go b/scheduler/rpcserver/rpcserver.go
index 0d4d1761..63b2301f 100644
--- a/scheduler/rpcserver/rpcserver.go
+++ b/scheduler/rpcserver/rpcserver.go
@@ -87,3 +87,15 @@ func (s *Server) ReportPeerResult(ctx context.Context, req *scheduler.PeerResult
 func (s *Server) LeaveTask(ctx context.Context, req *scheduler.PeerTarget) (*empty.Empty, error) {
 	return new(empty.Empty), s.service.LeaveTask(ctx, req)
 }
+
+// StatTask checks if the given task exists in P2P network
+func (s *Server) StatTask(ctx context.Context, req *scheduler.StatTaskRequest) (*scheduler.Task, error) {
+	// TODO: add metrics
+	return s.service.StatTask(ctx, req)
+}
+
+// AnnounceTask informs scheduler a peer has completed task
+func (s *Server) AnnounceTask(ctx context.Context, req *scheduler.AnnounceTaskRequest) (*empty.Empty, error) {
+	// TODO: add metrics
+	return new(empty.Empty), s.service.AnnounceTask(ctx, req)
+}
diff --git a/scheduler/scheduler/scheduler.go b/scheduler/scheduler/scheduler.go
index 8e5e7fcb..9f432870 100644
--- a/scheduler/scheduler/scheduler.go
+++ b/scheduler/scheduler/scheduler.go
@@ -82,9 +82,7 @@ func (s *scheduler) ScheduleParent(ctx context.Context, peer *resource.Peer, blo
 
 		// If the scheduling exceeds the RetryBackSourceLimit or the latest cdn peer state is PeerStateFailed,
 		// peer will download the task back-to-source
-		cdnPeer, ok := peer.Task.LoadCDNPeer()
-		if (n >= s.config.RetryBackSourceLimit ||
-			ok && cdnPeer.FSM.Is(resource.PeerStateFailed)) &&
+		if (n >= s.config.RetryBackSourceLimit || peer.Task.IsCDNFailed()) &&
 			peer.Task.CanBackToSource() {
 			stream, ok := peer.LoadStream()
 			if !ok {
@@ -97,8 +95,8 @@ func (s *scheduler) ScheduleParent(ctx context.Context, peer *resource.Peer, blo
 				peer.Log.Errorf("send packet failed: %v", err)
 				return
 			}
-			peer.Log.Infof("peer scheduling %d times and cdn peer is %#v, peer downloads back-to-source %d",
-				n, cdnPeer, base.Code_SchedNeedBackSource)
+			peer.Log.Infof("peer scheduling %d times, peer downloads back-to-source %d",
+				n, base.Code_SchedNeedBackSource)
 
 			if err := peer.FSM.Event(resource.PeerEventDownloadFromBackToSource); err != nil {
 				peer.Log.Errorf("peer fsm event failed: %v", err)
diff --git a/scheduler/service/service.go b/scheduler/service/service.go
index a7b8ec02..a0118450 100644
--- a/scheduler/service/service.go
+++ b/scheduler/service/service.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"time"
 
 	"go.opentelemetry.io/otel/trace"
 
@@ -82,8 +83,8 @@ func (s *Service) RegisterPeerTask(ctx context.Context, req *rpcscheduler.PeerTa
 		logger.Errorf("peer %s register is failed: %v", req.PeerId, err)
 		return nil, dferr
 	}
-	host := s.registerHost(ctx, req)
-	peer := s.registerPeer(ctx, req, task, host)
+	host := s.registerHost(ctx, req.PeerHost)
+	peer := s.registerPeer(ctx, req.PeerId, task, host, req.UrlMeta.Tag)
 	peer.Log.Infof("register peer task request: %#v %#v %#v", req, req.UrlMeta, req.HostLoad)
 
 	// Task has been successful
@@ -394,9 +395,113 @@ func (s *Service) LeaveTask(ctx context.Context, req *rpcscheduler.PeerTarget) e
 	return nil
 }
 
+// StatTask checks the current state of the task
+func (s *Service) StatTask(ctx context.Context, req *rpcscheduler.StatTaskRequest) (*rpcscheduler.Task, error) {
+	task, loaded := s.resource.TaskManager().Load(req.TaskId)
+	if !loaded {
+		msg := fmt.Sprintf("task %s not found", req.TaskId)
+		logger.Info(msg)
+		return nil, dferrors.New(base.Code_PeerTaskNotFound, msg)
+	}
+
+	task.Log.Debug("task has been found in P2P network")
+	return &rpcscheduler.Task{
+		Id:               task.ID,
+		Type:             int32(task.Type),
+		ContentLength:    task.ContentLength.Load(),
+		TotalPieceCount:  task.TotalPieceCount.Load(),
+		State:            task.FSM.Current(),
+		PeerCount:        task.PeerCount.Load(),
+		HasAvailablePeer: task.HasAvailablePeer(),
+	}, nil
+}
+
+// AnnounceTask informs scheduler a peer has completed task
+func (s *Service) AnnounceTask(ctx context.Context, req *rpcscheduler.AnnounceTaskRequest) error {
+	taskID := req.TaskId
+	peerID := req.PiecePacket.DstPid
+	pieceInfos := req.PiecePacket.PieceInfos
+	totalPiece := req.PiecePacket.TotalPiece
+	if len(pieceInfos) <= 0 || totalPiece != int32(len(pieceInfos)) {
+		msg := fmt.Sprintf("length of pieces is invalid, total piece id %d, length of piece infos is %d", totalPiece, len(pieceInfos))
+		logger.WithTaskAndPeerID(taskID, peerID).Warn(msg)
+		return dferrors.New(base.Code_BadRequest, msg)
+	}
+
+	task := resource.NewTask(taskID, req.Cid, resource.TaskTypeDfcache, req.UrlMeta)
+	task, _ = s.resource.TaskManager().LoadOrStore(task)
+	host := s.registerHost(ctx, req.PeerHost)
+	peer := s.registerPeer(ctx, peerID, task, host, req.UrlMeta.Tag)
+	peer.Log.Infof("announce peer task request: %#v %#v %#v %#v", req, req.UrlMeta, req.PeerHost, req.PiecePacket)
+
+	// If the task state is not TaskStateSucceeded,
+	// advance the task state to TaskStateSucceeded
+	if !task.FSM.Is(resource.TaskStateSucceeded) {
+		if task.FSM.Is(resource.TaskStatePending) {
+			if err := task.FSM.Event(resource.TaskEventDownload); err != nil {
+				msg := fmt.Sprintf("task fsm Pending -> Download event failed: %s", err)
+				task.Log.Error(msg)
+				return dferrors.New(base.Code_SchedError, msg)
+			}
+		}
+
+		if task.FSM.Is(resource.TaskStateFailed) {
+			if err := task.FSM.Event(resource.TaskEventDownload); err != nil {
+				msg := fmt.Sprintf("task fsm Failed -> Download event failed: %s", err)
+				task.Log.Error(msg)
+				return dferrors.New(base.Code_SchedError, msg)
+			}
+		}
+
+		// Load downloaded piece infos
+		for _, pieceInfo := range pieceInfos {
+			peer.Pieces.Set(uint(pieceInfo.PieceNum))
+			peer.AppendPieceCost(int64(pieceInfo.DownloadCost) * int64(time.Millisecond))
+			task.StorePiece(pieceInfo)
+		}
+
+		s.handleTaskSuccess(ctx, task, &rpcscheduler.PeerResult{
+			TaskId:          taskID,
+			PeerId:          peerID,
+			SrcIp:           req.PeerHost.Ip,
+			Url:             req.Cid,
+			Success:         true,
+			TotalPieceCount: totalPiece,
+			ContentLength:   req.PiecePacket.ContentLength,
+			Code:            base.Code_Success,
+		})
+	}
+
+	// If the peer state is not PeerStateSucceeded,
+	// advance the peer state to PeerStateSucceeded
+	if !peer.FSM.Is(resource.PeerStateSucceeded) {
+		if peer.FSM.Is(resource.PeerStatePending) {
+			if err := peer.FSM.Event(resource.PeerEventRegisterNormal); err != nil {
+				msg := fmt.Sprintf("peer fsm Pending -> Normal event failed: %s", err)
+				task.Log.Error(msg)
+				return dferrors.New(base.Code_SchedError, msg)
+			}
+		}
+
+		if peer.FSM.Is(resource.PeerStateReceivedTiny) ||
+			peer.FSM.Is(resource.PeerStateReceivedSmall) ||
+			peer.FSM.Is(resource.PeerStateReceivedNormal) {
+			if err := peer.FSM.Event(resource.PeerEventDownload); err != nil {
+				msg := fmt.Sprintf("peer fsm Normal -> Download event failed: %s", err)
+				task.Log.Error(msg)
+				return dferrors.New(base.Code_SchedError, msg)
+			}
+
+			s.handlePeerSuccess(ctx, peer)
+		}
+	}
+
+	return nil
+}
+
 // registerTask creates a new task or reuses a previous task
 func (s *Service) registerTask(ctx context.Context, req *rpcscheduler.PeerTaskRequest) (*resource.Task, error) {
-	task := resource.NewTask(idgen.TaskID(req.Url, req.UrlMeta), req.Url, s.config.Scheduler.BackSourceCount, req.UrlMeta)
+	task := resource.NewTask(idgen.TaskID(req.Url, req.UrlMeta), req.Url, resource.TaskTypeNormal, req.UrlMeta, resource.WithBackToSourceLimit(int32(s.config.Scheduler.BackSourceCount)))
 	task, loaded := s.resource.TaskManager().LoadOrStore(task)
 	if loaded && !task.FSM.Is(resource.TaskStateFailed) {
 		task.Log.Infof("task state is %s", task.FSM.Current())
@@ -417,8 +522,7 @@ func (s *Service) registerTask(ctx context.Context, req *rpcscheduler.PeerTaskRe
 }
 
 // registerHost creates a new host or reuses a previous host
-func (s *Service) registerHost(ctx context.Context, req *rpcscheduler.PeerTaskRequest) *resource.Host {
-	rawHost := req.PeerHost
+func (s *Service) registerHost(ctx context.Context, rawHost *rpcscheduler.PeerHost) *resource.Host {
 	host, ok := s.resource.HostManager().Load(rawHost.Uuid)
 	if !ok {
 		// Get scheduler cluster client config by manager
@@ -438,19 +542,19 @@ func (s *Service) registerHost(ctx context.Context, req *rpcscheduler.PeerTaskRe
 }
 
 // registerPeer creates a new peer or reuses a previous peer
-func (s *Service) registerPeer(ctx context.Context, req *rpcscheduler.PeerTaskRequest, task *resource.Task, host *resource.Host) *resource.Peer {
+func (s *Service) registerPeer(ctx context.Context, peerID string, task *resource.Task, host *resource.Host, tag string) *resource.Peer {
 	var options []resource.PeerOption
-	if req.UrlMeta.Tag != "" {
-		options = append(options, resource.WithBizTag(req.UrlMeta.Tag))
+	if tag != "" {
+		options = append(options, resource.WithBizTag(tag))
 	}
 
-	peer, loaded := s.resource.PeerManager().LoadOrStore(resource.NewPeer(req.PeerId, task, host, options...))
+	peer, loaded := s.resource.PeerManager().LoadOrStore(resource.NewPeer(peerID, task, host, options...))
 	if !loaded {
 		peer.Log.Info("create new peer")
 		return peer
 	}
 
-	peer.Log.Info("peer already exists")
+	peer.Log.Infof("peer already exists, state %s", peer.FSM.Current())
 	return peer
 }
 
@@ -622,6 +726,7 @@ func (s *Service) handlePeerFail(ctx context.Context, peer *resource.Peer) {
 // Conditions for the task to switch to the TaskStateSucceeded are:
 // 1. CDN downloads the resource successfully
 // 2. Dfdaemon back-to-source to download successfully
+// 3. Peer announces it has the task
 func (s *Service) handleTaskSuccess(ctx context.Context, task *resource.Task, result *rpcscheduler.PeerResult) {
 	if task.FSM.Is(resource.TaskStateSucceeded) {
 		return


Test patch:
diff --git a/client/daemon/peer/peertask_manager_mock_test.go b/client/daemon/peer/peertask_manager_mock_test.go
index 935f6051..c311b843 100644
--- a/client/daemon/peer/peertask_manager_mock_test.go
+++ b/client/daemon/peer/peertask_manager_mock_test.go
@@ -12,6 +12,7 @@ import (
 	storage "d7y.io/dragonfly/v2/client/daemon/storage"
 	dflog "d7y.io/dragonfly/v2/internal/dflog"
 	base "d7y.io/dragonfly/v2/pkg/rpc/base"
+	scheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	gomock "github.com/golang/mock/gomock"
 )
 
@@ -38,6 +39,34 @@ func (m *MockTaskManager) EXPECT() *MockTaskManagerMockRecorder {
 	return m.recorder
 }
 
+// AnnouncePeerTask mocks base method.
+func (m *MockTaskManager) AnnouncePeerTask(ctx context.Context, meta storage.PeerTaskMetadata, cid string, urlMeta *base.UrlMeta) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AnnouncePeerTask", ctx, meta, cid, urlMeta)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AnnouncePeerTask indicates an expected call of AnnouncePeerTask.
+func (mr *MockTaskManagerMockRecorder) AnnouncePeerTask(ctx, meta, cid, urlMeta interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnouncePeerTask", reflect.TypeOf((*MockTaskManager)(nil).AnnouncePeerTask), ctx, meta, cid, urlMeta)
+}
+
+// GetPieceManager mocks base method.
+func (m *MockTaskManager) GetPieceManager() PieceManager {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPieceManager")
+	ret0, _ := ret[0].(PieceManager)
+	return ret0
+}
+
+// GetPieceManager indicates an expected call of GetPieceManager.
+func (mr *MockTaskManagerMockRecorder) GetPieceManager() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceManager", reflect.TypeOf((*MockTaskManager)(nil).GetPieceManager))
+}
+
 // IsPeerTaskRunning mocks base method.
 func (m *MockTaskManager) IsPeerTaskRunning(id string) bool {
 	m.ctrl.T.Helper()
@@ -84,6 +113,21 @@ func (mr *MockTaskManagerMockRecorder) StartStreamTask(ctx, req interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartStreamTask", reflect.TypeOf((*MockTaskManager)(nil).StartStreamTask), ctx, req)
 }
 
+// StatTask mocks base method.
+func (m *MockTaskManager) StatTask(ctx context.Context, taskID string) (*scheduler.Task, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StatTask", ctx, taskID)
+	ret0, _ := ret[0].(*scheduler.Task)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockTaskManagerMockRecorder) StatTask(ctx, taskID interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockTaskManager)(nil).StatTask), ctx, taskID)
+}
+
 // Stop mocks base method.
 func (m *MockTaskManager) Stop(ctx context.Context) error {
 	m.ctrl.T.Helper()
diff --git a/client/daemon/peer/peertask_manager_test.go b/client/daemon/peer/peertask_manager_test.go
index e5f28349..38d2fa65 100644
--- a/client/daemon/peer/peertask_manager_test.go
+++ b/client/daemon/peer/peertask_manager_test.go
@@ -47,7 +47,6 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	"d7y.io/dragonfly/v2/client/daemon/test"
 	mock_daemon "d7y.io/dragonfly/v2/client/daemon/test/mock/daemon"
-	mock_scheduler "d7y.io/dragonfly/v2/client/daemon/test/mock/scheduler"
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/dfnet"
@@ -58,6 +57,7 @@ import (
 	daemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	schedulerclient "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client"
+	mock_scheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client/mocks"
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/source/httpprotocol"
 	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
diff --git a/client/daemon/peer/peertask_stream_backsource_partial_test.go b/client/daemon/peer/peertask_stream_backsource_partial_test.go
index a1e71637..607e328c 100644
--- a/client/daemon/peer/peertask_stream_backsource_partial_test.go
+++ b/client/daemon/peer/peertask_stream_backsource_partial_test.go
@@ -43,7 +43,6 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	"d7y.io/dragonfly/v2/client/daemon/test"
 	mock_daemon "d7y.io/dragonfly/v2/client/daemon/test/mock/daemon"
-	mock_scheduler "d7y.io/dragonfly/v2/client/daemon/test/mock/scheduler"
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	"d7y.io/dragonfly/v2/internal/dfnet"
 	"d7y.io/dragonfly/v2/pkg/rpc"
@@ -52,6 +51,7 @@ import (
 	daemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	schedulerclient "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client"
+	mock_scheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client/mocks"
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/source/httpprotocol"
 	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
diff --git a/client/daemon/test/mock/daemon/daemon_server.go b/client/daemon/test/mock/daemon/daemon_server.go
index 3b36636e..b8779437 100644
--- a/client/daemon/test/mock/daemon/daemon_server.go
+++ b/client/daemon/test/mock/daemon/daemon_server.go
@@ -50,6 +50,20 @@ func (mr *MockDaemonServerMockRecorder) CheckHealth(arg0 interface{}) *gomock.Ca
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckHealth", reflect.TypeOf((*MockDaemonServer)(nil).CheckHealth), arg0)
 }
 
+// DeleteTask mocks base method.
+func (m *MockDaemonServer) DeleteTask(arg0 context.Context, arg1 *dfdaemon.DeleteTaskRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteTask", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteTask indicates an expected call of DeleteTask.
+func (mr *MockDaemonServerMockRecorder) DeleteTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockDaemonServer)(nil).DeleteTask), arg0, arg1)
+}
+
 // Download mocks base method.
 func (m *MockDaemonServer) Download(arg0 context.Context, arg1 *dfdaemon.DownRequest, arg2 chan<- *dfdaemon.DownResult) error {
 	m.ctrl.T.Helper()
@@ -64,6 +78,20 @@ func (mr *MockDaemonServerMockRecorder) Download(arg0, arg1, arg2 interface{}) *
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockDaemonServer)(nil).Download), arg0, arg1, arg2)
 }
 
+// ExportTask mocks base method.
+func (m *MockDaemonServer) ExportTask(arg0 context.Context, arg1 *dfdaemon.ExportTaskRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExportTask", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ExportTask indicates an expected call of ExportTask.
+func (mr *MockDaemonServerMockRecorder) ExportTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportTask", reflect.TypeOf((*MockDaemonServer)(nil).ExportTask), arg0, arg1)
+}
+
 // GetPieceTasks mocks base method.
 func (m *MockDaemonServer) GetPieceTasks(arg0 context.Context, arg1 *base.PieceTaskRequest) (*base.PiecePacket, error) {
 	m.ctrl.T.Helper()
@@ -79,6 +107,34 @@ func (mr *MockDaemonServerMockRecorder) GetPieceTasks(arg0, arg1 interface{}) *g
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceTasks", reflect.TypeOf((*MockDaemonServer)(nil).GetPieceTasks), arg0, arg1)
 }
 
+// ImportTask mocks base method.
+func (m *MockDaemonServer) ImportTask(arg0 context.Context, arg1 *dfdaemon.ImportTaskRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ImportTask", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ImportTask indicates an expected call of ImportTask.
+func (mr *MockDaemonServerMockRecorder) ImportTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportTask", reflect.TypeOf((*MockDaemonServer)(nil).ImportTask), arg0, arg1)
+}
+
+// StatTask mocks base method.
+func (m *MockDaemonServer) StatTask(arg0 context.Context, arg1 *dfdaemon.StatTaskRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StatTask", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockDaemonServerMockRecorder) StatTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockDaemonServer)(nil).StatTask), arg0, arg1)
+}
+
 // SyncPieceTasks mocks base method.
 func (m *MockDaemonServer) SyncPieceTasks(arg0 dfdaemon.Daemon_SyncPieceTasksServer) error {
 	m.ctrl.T.Helper()
diff --git a/client/daemon/test/mock/daemongrpc/daemon_server_grpc.go b/client/daemon/test/mock/daemongrpc/daemon_server_grpc.go
index 74b58731..df0c61b9 100644
--- a/client/daemon/test/mock/daemongrpc/daemon_server_grpc.go
+++ b/client/daemon/test/mock/daemongrpc/daemon_server_grpc.go
@@ -59,6 +59,26 @@ func (mr *MockDaemonClientMockRecorder) CheckHealth(ctx, in interface{}, opts ..
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckHealth", reflect.TypeOf((*MockDaemonClient)(nil).CheckHealth), varargs...)
 }
 
+// DeleteTask mocks base method.
+func (m *MockDaemonClient) DeleteTask(ctx context.Context, in *dfdaemon.DeleteTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, in}
+	for _, a := range opts {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "DeleteTask", varargs...)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DeleteTask indicates an expected call of DeleteTask.
+func (mr *MockDaemonClientMockRecorder) DeleteTask(ctx, in interface{}, opts ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, in}, opts...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockDaemonClient)(nil).DeleteTask), varargs...)
+}
+
 // Download mocks base method.
 func (m *MockDaemonClient) Download(ctx context.Context, in *dfdaemon.DownRequest, opts ...grpc.CallOption) (dfdaemon.Daemon_DownloadClient, error) {
 	m.ctrl.T.Helper()
@@ -79,6 +99,26 @@ func (mr *MockDaemonClientMockRecorder) Download(ctx, in interface{}, opts ...in
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockDaemonClient)(nil).Download), varargs...)
 }
 
+// ExportTask mocks base method.
+func (m *MockDaemonClient) ExportTask(ctx context.Context, in *dfdaemon.ExportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, in}
+	for _, a := range opts {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "ExportTask", varargs...)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExportTask indicates an expected call of ExportTask.
+func (mr *MockDaemonClientMockRecorder) ExportTask(ctx, in interface{}, opts ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, in}, opts...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportTask", reflect.TypeOf((*MockDaemonClient)(nil).ExportTask), varargs...)
+}
+
 // GetPieceTasks mocks base method.
 func (m *MockDaemonClient) GetPieceTasks(ctx context.Context, in *base.PieceTaskRequest, opts ...grpc.CallOption) (*base.PiecePacket, error) {
 	m.ctrl.T.Helper()
@@ -99,6 +139,46 @@ func (mr *MockDaemonClientMockRecorder) GetPieceTasks(ctx, in interface{}, opts
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceTasks", reflect.TypeOf((*MockDaemonClient)(nil).GetPieceTasks), varargs...)
 }
 
+// ImportTask mocks base method.
+func (m *MockDaemonClient) ImportTask(ctx context.Context, in *dfdaemon.ImportTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, in}
+	for _, a := range opts {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "ImportTask", varargs...)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ImportTask indicates an expected call of ImportTask.
+func (mr *MockDaemonClientMockRecorder) ImportTask(ctx, in interface{}, opts ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, in}, opts...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportTask", reflect.TypeOf((*MockDaemonClient)(nil).ImportTask), varargs...)
+}
+
+// StatTask mocks base method.
+func (m *MockDaemonClient) StatTask(ctx context.Context, in *dfdaemon.StatTaskRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{ctx, in}
+	for _, a := range opts {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "StatTask", varargs...)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockDaemonClientMockRecorder) StatTask(ctx, in interface{}, opts ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{ctx, in}, opts...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockDaemonClient)(nil).StatTask), varargs...)
+}
+
 // SyncPieceTasks mocks base method.
 func (m *MockDaemonClient) SyncPieceTasks(ctx context.Context, opts ...grpc.CallOption) (dfdaemon.Daemon_SyncPieceTasksClient, error) {
 	m.ctrl.T.Helper()
@@ -417,6 +497,21 @@ func (mr *MockDaemonServerMockRecorder) CheckHealth(arg0, arg1 interface{}) *gom
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckHealth", reflect.TypeOf((*MockDaemonServer)(nil).CheckHealth), arg0, arg1)
 }
 
+// DeleteTask mocks base method.
+func (m *MockDaemonServer) DeleteTask(arg0 context.Context, arg1 *dfdaemon.DeleteTaskRequest) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteTask", arg0, arg1)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DeleteTask indicates an expected call of DeleteTask.
+func (mr *MockDaemonServerMockRecorder) DeleteTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTask", reflect.TypeOf((*MockDaemonServer)(nil).DeleteTask), arg0, arg1)
+}
+
 // Download mocks base method.
 func (m *MockDaemonServer) Download(arg0 *dfdaemon.DownRequest, arg1 dfdaemon.Daemon_DownloadServer) error {
 	m.ctrl.T.Helper()
@@ -431,6 +526,21 @@ func (mr *MockDaemonServerMockRecorder) Download(arg0, arg1 interface{}) *gomock
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockDaemonServer)(nil).Download), arg0, arg1)
 }
 
+// ExportTask mocks base method.
+func (m *MockDaemonServer) ExportTask(arg0 context.Context, arg1 *dfdaemon.ExportTaskRequest) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExportTask", arg0, arg1)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExportTask indicates an expected call of ExportTask.
+func (mr *MockDaemonServerMockRecorder) ExportTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExportTask", reflect.TypeOf((*MockDaemonServer)(nil).ExportTask), arg0, arg1)
+}
+
 // GetPieceTasks mocks base method.
 func (m *MockDaemonServer) GetPieceTasks(arg0 context.Context, arg1 *base.PieceTaskRequest) (*base.PiecePacket, error) {
 	m.ctrl.T.Helper()
@@ -446,6 +556,36 @@ func (mr *MockDaemonServerMockRecorder) GetPieceTasks(arg0, arg1 interface{}) *g
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceTasks", reflect.TypeOf((*MockDaemonServer)(nil).GetPieceTasks), arg0, arg1)
 }
 
+// ImportTask mocks base method.
+func (m *MockDaemonServer) ImportTask(arg0 context.Context, arg1 *dfdaemon.ImportTaskRequest) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ImportTask", arg0, arg1)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ImportTask indicates an expected call of ImportTask.
+func (mr *MockDaemonServerMockRecorder) ImportTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportTask", reflect.TypeOf((*MockDaemonServer)(nil).ImportTask), arg0, arg1)
+}
+
+// StatTask mocks base method.
+func (m *MockDaemonServer) StatTask(arg0 context.Context, arg1 *dfdaemon.StatTaskRequest) (*emptypb.Empty, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StatTask", arg0, arg1)
+	ret0, _ := ret[0].(*emptypb.Empty)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockDaemonServerMockRecorder) StatTask(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockDaemonServer)(nil).StatTask), arg0, arg1)
+}
+
 // SyncPieceTasks mocks base method.
 func (m *MockDaemonServer) SyncPieceTasks(arg0 dfdaemon.Daemon_SyncPieceTasksServer) error {
 	m.ctrl.T.Helper()
diff --git a/client/daemon/test/mock/peer/peertask_manager.go b/client/daemon/test/mock/peer/peertask_manager.go
index 0b9f8f23..94d74f8f 100644
--- a/client/daemon/test/mock/peer/peertask_manager.go
+++ b/client/daemon/test/mock/peer/peertask_manager.go
@@ -13,6 +13,7 @@ import (
 	storage "d7y.io/dragonfly/v2/client/daemon/storage"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	base "d7y.io/dragonfly/v2/pkg/rpc/base"
+	scheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	gomock "github.com/golang/mock/gomock"
 )
 
@@ -39,6 +40,34 @@ func (m *MockTaskManager) EXPECT() *MockTaskManagerMockRecorder {
 	return m.recorder
 }
 
+// AnnouncePeerTask mocks base method.
+func (m *MockTaskManager) AnnouncePeerTask(ctx context.Context, meta storage.PeerTaskMetadata, cid string, urlMeta *base.UrlMeta) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AnnouncePeerTask", ctx, meta, cid, urlMeta)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AnnouncePeerTask indicates an expected call of AnnouncePeerTask.
+func (mr *MockTaskManagerMockRecorder) AnnouncePeerTask(ctx, meta, cid, urlMeta interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnouncePeerTask", reflect.TypeOf((*MockTaskManager)(nil).AnnouncePeerTask), ctx, meta, cid, urlMeta)
+}
+
+// GetPieceManager mocks base method.
+func (m *MockTaskManager) GetPieceManager() peer.PieceManager {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetPieceManager")
+	ret0, _ := ret[0].(peer.PieceManager)
+	return ret0
+}
+
+// GetPieceManager indicates an expected call of GetPieceManager.
+func (mr *MockTaskManagerMockRecorder) GetPieceManager() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieceManager", reflect.TypeOf((*MockTaskManager)(nil).GetPieceManager))
+}
+
 // IsPeerTaskRunning mocks base method.
 func (m *MockTaskManager) IsPeerTaskRunning(id string) bool {
 	m.ctrl.T.Helper()
@@ -85,6 +114,21 @@ func (mr *MockTaskManagerMockRecorder) StartStreamTask(ctx, req interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartStreamTask", reflect.TypeOf((*MockTaskManager)(nil).StartStreamTask), ctx, req)
 }
 
+// StatTask mocks base method.
+func (m *MockTaskManager) StatTask(ctx context.Context, taskID string) (*scheduler.Task, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "StatTask", ctx, taskID)
+	ret0, _ := ret[0].(*scheduler.Task)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// StatTask indicates an expected call of StatTask.
+func (mr *MockTaskManagerMockRecorder) StatTask(ctx, taskID interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatTask", reflect.TypeOf((*MockTaskManager)(nil).StatTask), ctx, taskID)
+}
+
 // Stop mocks base method.
 func (m *MockTaskManager) Stop(ctx context.Context) error {
 	m.ctrl.T.Helper()
diff --git a/client/daemon/test/mock/storage/manager.go b/client/daemon/test/mock/storage/manager.go
index 2af3834b..6d666e4e 100644
--- a/client/daemon/test/mock/storage/manager.go
+++ b/client/daemon/test/mock/storage/manager.go
@@ -54,6 +54,21 @@ func (mr *MockTaskStorageDriverMockRecorder) GetPieces(ctx, req interface{}) *go
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieces", reflect.TypeOf((*MockTaskStorageDriver)(nil).GetPieces), ctx, req)
 }
 
+// GetTotalPieces mocks base method.
+func (m *MockTaskStorageDriver) GetTotalPieces(ctx context.Context, req *storage.PeerTaskMetadata) (int32, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTotalPieces", ctx, req)
+	ret0, _ := ret[0].(int32)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTotalPieces indicates an expected call of GetTotalPieces.
+func (mr *MockTaskStorageDriverMockRecorder) GetTotalPieces(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalPieces", reflect.TypeOf((*MockTaskStorageDriver)(nil).GetTotalPieces), ctx, req)
+}
+
 // IsInvalid mocks base method.
 func (m *MockTaskStorageDriver) IsInvalid(req *storage.PeerTaskMetadata) (bool, error) {
 	m.ctrl.T.Helper()
@@ -326,6 +341,21 @@ func (mr *MockManagerMockRecorder) GetPieces(ctx, req interface{}) *gomock.Call
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPieces", reflect.TypeOf((*MockManager)(nil).GetPieces), ctx, req)
 }
 
+// GetTotalPieces mocks base method.
+func (m *MockManager) GetTotalPieces(ctx context.Context, req *storage.PeerTaskMetadata) (int32, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTotalPieces", ctx, req)
+	ret0, _ := ret[0].(int32)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTotalPieces indicates an expected call of GetTotalPieces.
+func (mr *MockManagerMockRecorder) GetTotalPieces(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTotalPieces", reflect.TypeOf((*MockManager)(nil).GetTotalPieces), ctx, req)
+}
+
 // IsInvalid mocks base method.
 func (m *MockManager) IsInvalid(req *storage.PeerTaskMetadata) (bool, error) {
 	m.ctrl.T.Helper()
@@ -428,6 +458,20 @@ func (mr *MockManagerMockRecorder) Store(ctx, req interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Store", reflect.TypeOf((*MockManager)(nil).Store), ctx, req)
 }
 
+// UnregisterTask mocks base method.
+func (m *MockManager) UnregisterTask(ctx context.Context, req storage.CommonTaskRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UnregisterTask", ctx, req)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// UnregisterTask indicates an expected call of UnregisterTask.
+func (mr *MockManagerMockRecorder) UnregisterTask(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnregisterTask", reflect.TypeOf((*MockManager)(nil).UnregisterTask), ctx, req)
+}
+
 // UpdateTask mocks base method.
 func (m *MockManager) UpdateTask(ctx context.Context, req *storage.UpdateTaskRequest) error {
 	m.ctrl.T.Helper()
diff --git a/scheduler/resource/cdn_test.go b/scheduler/resource/cdn_test.go
index ae4b8da9..2fe1c30d 100644
--- a/scheduler/resource/cdn_test.go
+++ b/scheduler/resource/cdn_test.go
@@ -89,7 +89,7 @@ func TestCDN_TriggerTask(t *testing.T) {
 			tc.mock(client.EXPECT())
 
 			cdn := newCDN(peerManager, hostManager, client)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer, result, err := cdn.TriggerTask(context.Background(), mockTask)
 			tc.expect(t, peer, result, err)
 		})
diff --git a/scheduler/resource/host_manager_test.go b/scheduler/resource/host_manager_test.go
index 4f921136..a1f1cf07 100644
--- a/scheduler/resource/host_manager_test.go
+++ b/scheduler/resource/host_manager_test.go
@@ -382,7 +382,7 @@ func TestHostManager_RunGC(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			hostManager, err := newHostManager(mockHostGCConfig, gc)
 			if err != nil {
diff --git a/scheduler/resource/host_test.go b/scheduler/resource/host_test.go
index 646b4494..58d873c1 100644
--- a/scheduler/resource/host_test.go
+++ b/scheduler/resource/host_test.go
@@ -177,7 +177,7 @@ func TestHost_LoadPeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, host)
 
 			host.StorePeer(mockPeer)
@@ -220,7 +220,7 @@ func TestHost_StorePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(tc.peerID, mockTask, host)
 
 			host.StorePeer(mockPeer)
@@ -268,7 +268,7 @@ func TestHost_LoadOrStorePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, host)
 
 			host.StorePeer(mockPeer)
@@ -311,7 +311,7 @@ func TestHost_DeletePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, host)
 
 			host.StorePeer(mockPeer)
@@ -361,7 +361,7 @@ func TestHost_LeavePeers(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, host)
 
 			tc.expect(t, host, mockPeer)
@@ -402,7 +402,7 @@ func TestHost_FreeUploadLoad(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := NewHost(tc.rawHost, tc.options...)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, host)
 
 			tc.expect(t, host, mockPeer)
diff --git a/scheduler/resource/peer_manager_test.go b/scheduler/resource/peer_manager_test.go
index 871c5898..0a35f550 100644
--- a/scheduler/resource/peer_manager_test.go
+++ b/scheduler/resource/peer_manager_test.go
@@ -131,7 +131,7 @@ func TestPeerManager_Load(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			peerManager, err := newPeerManager(mockPeerGCConfig, gc)
 			if err != nil {
@@ -186,7 +186,7 @@ func TestPeerManager_Store(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			peerManager, err := newPeerManager(mockPeerGCConfig, gc)
 			if err != nil {
@@ -239,7 +239,7 @@ func TestPeerManager_LoadOrStore(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			peerManager, err := newPeerManager(mockPeerGCConfig, gc)
 			if err != nil {
@@ -294,7 +294,7 @@ func TestPeerManager_Delete(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			peerManager, err := newPeerManager(mockPeerGCConfig, gc)
 			if err != nil {
@@ -398,7 +398,7 @@ func TestPeerManager_RunGC(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			peerManager, err := newPeerManager(mockPeerGCConfig, gc)
 			if err != nil {
diff --git a/scheduler/resource/peer_test.go b/scheduler/resource/peer_test.go
index 7004189e..be84f453 100644
--- a/scheduler/resource/peer_test.go
+++ b/scheduler/resource/peer_test.go
@@ -92,7 +92,7 @@ func TestPeer_NewPeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			tc.expect(t, NewPeer(tc.id, mockTask, mockHost, tc.options...), mockTask, mockHost)
 		})
 	}
@@ -138,7 +138,7 @@ func TestPeer_LoadChild(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockChildPeer := NewPeer(tc.childID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -197,7 +197,7 @@ func TestPeer_StoreChild(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockChildPeer := NewPeer(tc.childID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -252,7 +252,7 @@ func TestPeer_DeleteChild(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockChildPeer := NewPeer(tc.childID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -302,7 +302,7 @@ func TestPeer_LoadParent(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockParentPeer := NewPeer(tc.parentID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -361,7 +361,7 @@ func TestPeer_StoreParent(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockParentPeer := NewPeer(tc.parentID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -411,7 +411,7 @@ func TestPeer_DeleteParent(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockParentPeer := NewPeer(tc.parentID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -479,7 +479,7 @@ func TestPeer_ReplaceParent(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockOldParentPeer := NewPeer(tc.oldParentID, mockTask, mockHost)
 			mockNewParentPeer := NewPeer(tc.newParentID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
@@ -534,7 +534,7 @@ func TestPeer_Depth(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
 			mockCDNHost := NewHost(mockRawCDNHost, WithIsCDN(true))
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 			parent := NewPeer(idgen.PeerID("127.0.0.2"), mockTask, mockHost)
 			cdnParent := NewPeer(mockCDNPeerID, mockTask, mockCDNHost)
@@ -581,7 +581,7 @@ func TestPeer_IsDescendant(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockChildPeer := NewPeer(tc.childID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -627,7 +627,7 @@ func TestPeer_IsAncestor(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockChildPeer := NewPeer(tc.childID, mockTask, mockHost)
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
@@ -663,7 +663,7 @@ func TestPeer_AppendPieceCost(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
 			tc.expect(t, peer)
@@ -698,7 +698,7 @@ func TestPeer_PieceCosts(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 
 			tc.expect(t, peer)
@@ -738,7 +738,7 @@ func TestPeer_LoadStream(t *testing.T) {
 			stream := mocks.NewMockScheduler_ReportPieceResultServer(ctl)
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 			tc.expect(t, peer, stream)
 		})
@@ -769,7 +769,7 @@ func TestPeer_StoreStream(t *testing.T) {
 			stream := mocks.NewMockScheduler_ReportPieceResultServer(ctl)
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 			tc.expect(t, peer, stream)
 		})
@@ -800,7 +800,7 @@ func TestPeer_DeleteStream(t *testing.T) {
 			stream := mocks.NewMockScheduler_ReportPieceResultServer(ctl)
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := NewPeer(mockPeerID, mockTask, mockHost)
 			tc.expect(t, peer, stream)
 		})
@@ -899,7 +899,7 @@ func TestPeer_DownloadTinyFile(t *testing.T) {
 			mockRawHost.Ip = ip
 			mockRawHost.DownPort = int32(port)
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer = NewPeer(mockPeerID, mockTask, mockHost)
 			tc.expect(t, peer)
 		})
diff --git a/scheduler/resource/task_manager_test.go b/scheduler/resource/task_manager_test.go
index 49e65141..f92e1019 100644
--- a/scheduler/resource/task_manager_test.go
+++ b/scheduler/resource/task_manager_test.go
@@ -130,7 +130,7 @@ func TestTaskManager_Load(t *testing.T) {
 			gc := gc.NewMockGC(ctl)
 			tc.mock(gc.EXPECT())
 
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			taskManager, err := newTaskManager(mockTaskGCConfig, gc)
 			if err != nil {
 				t.Fatal(err)
@@ -183,7 +183,7 @@ func TestTaskManager_Store(t *testing.T) {
 			gc := gc.NewMockGC(ctl)
 			tc.mock(gc.EXPECT())
 
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			taskManager, err := newTaskManager(mockTaskGCConfig, gc)
 			if err != nil {
 				t.Fatal(err)
@@ -234,7 +234,7 @@ func TestTaskManager_LoadOrStore(t *testing.T) {
 			gc := gc.NewMockGC(ctl)
 			tc.mock(gc.EXPECT())
 
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			taskManager, err := newTaskManager(mockTaskGCConfig, gc)
 			if err != nil {
 				t.Fatal(err)
@@ -287,7 +287,7 @@ func TestTaskManager_Delete(t *testing.T) {
 			gc := gc.NewMockGC(ctl)
 			tc.mock(gc.EXPECT())
 
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			taskManager, err := newTaskManager(mockTaskGCConfig, gc)
 			if err != nil {
 				t.Fatal(err)
@@ -363,7 +363,7 @@ func TestTaskManager_RunGC(t *testing.T) {
 			tc.mock(gc.EXPECT())
 
 			mockHost := NewHost(mockRawHost)
-			mockTask := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, mockTask, mockHost)
 			taskManager, err := newTaskManager(mockTaskGCConfig, gc)
 			if err != nil {
diff --git a/scheduler/resource/task_test.go b/scheduler/resource/task_test.go
index 840fe2f5..3e542cd0 100644
--- a/scheduler/resource/task_test.go
+++ b/scheduler/resource/task_test.go
@@ -40,10 +40,10 @@ var (
 			"content-length": "100",
 		},
 	}
-	mockTaskURL               = "http://example.com/foo"
-	mockTaskBackToSourceLimit = 200
-	mockTaskID                = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
-	mockPieceInfo             = &base.PieceInfo{
+	mockTaskBackToSourceLimit int32 = 200
+	mockTaskURL                     = "http://example.com/foo"
+	mockTaskID                      = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
+	mockPieceInfo                   = &base.PieceInfo{
 		PieceNum:    1,
 		RangeStart:  0,
 		RangeSize:   100,
@@ -58,7 +58,7 @@ func TestTask_NewTask(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		expect            func(t *testing.T, task *Task)
 	}{
 		{
@@ -89,7 +89,7 @@ func TestTask_NewTask(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			tc.expect(t, NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta))
+			tc.expect(t, NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit)))
 		})
 	}
 }
@@ -100,7 +100,7 @@ func TestTask_LoadPeer(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		peerID            string
 		expect            func(t *testing.T, peer *Peer, ok bool)
 	}{
@@ -146,7 +146,7 @@ func TestTask_LoadPeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 
 			task.StorePeer(mockPeer)
@@ -162,7 +162,7 @@ func TestTask_StorePeer(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		peerID            string
 		expect            func(t *testing.T, peer *Peer, ok bool)
 	}{
@@ -197,7 +197,7 @@ func TestTask_StorePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			mockPeer := NewPeer(tc.peerID, task, mockHost)
 
 			task.StorePeer(mockPeer)
@@ -213,7 +213,7 @@ func TestTask_LoadOrStorePeer(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		peerID            string
 		expect            func(t *testing.T, task *Task, mockPeer *Peer)
 	}{
@@ -253,7 +253,7 @@ func TestTask_LoadOrStorePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 
 			task.StorePeer(mockPeer)
@@ -268,7 +268,7 @@ func TestTask_DeletePeer(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		peerID            string
 		expect            func(t *testing.T, task *Task)
 	}{
@@ -304,7 +304,7 @@ func TestTask_DeletePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 
 			task.StorePeer(mockPeer)
@@ -320,7 +320,7 @@ func TestTask_HasAvailablePeer(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		expect            func(t *testing.T, task *Task, mockPeer *Peer)
 	}{
 		{
@@ -353,7 +353,7 @@ func TestTask_HasAvailablePeer(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 
 			tc.expect(t, task, mockPeer)
@@ -361,7 +361,7 @@ func TestTask_HasAvailablePeer(t *testing.T) {
 	}
 }
 
-func TestTask_CDNPeers(t *testing.T) {
+func TestTask_LoadCDNPeer(t *testing.T) {
 	tests := []struct {
 		name   string
 		expect func(t *testing.T, task *Task, mockPeer *Peer, mockCDNPeer *Peer)
@@ -416,7 +416,58 @@ func TestTask_CDNPeers(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := NewHost(mockRawHost)
 			mockCDNHost := NewHost(mockRawCDNHost, WithIsCDN(true))
-			task := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
+			mockPeer := NewPeer(mockPeerID, task, mockHost)
+			mockCDNPeer := NewPeer(mockCDNPeerID, task, mockCDNHost)
+
+			tc.expect(t, task, mockPeer, mockCDNPeer)
+		})
+	}
+}
+
+func TestTask_IsCDNFailed(t *testing.T) {
+	tests := []struct {
+		name   string
+		expect func(t *testing.T, task *Task, mockPeer *Peer, mockCDNPeer *Peer)
+	}{
+		{
+			name: "cdn state is PeerStateFailed",
+			expect: func(t *testing.T, task *Task, mockPeer *Peer, mockCDNPeer *Peer) {
+				assert := assert.New(t)
+				task.StorePeer(mockPeer)
+				task.StorePeer(mockCDNPeer)
+				mockCDNPeer.FSM.SetState(PeerStateFailed)
+
+				assert.True(task.IsCDNFailed())
+			},
+		},
+		{
+			name: "can not find cdn peer",
+			expect: func(t *testing.T, task *Task, mockPeer *Peer, mockCDNPeer *Peer) {
+				assert := assert.New(t)
+				task.StorePeer(mockPeer)
+
+				assert.False(task.IsCDNFailed())
+			},
+		},
+		{
+			name: "cdn state is PeerStateSucceeded",
+			expect: func(t *testing.T, task *Task, mockPeer *Peer, mockCDNPeer *Peer) {
+				assert := assert.New(t)
+				task.StorePeer(mockPeer)
+				task.StorePeer(mockCDNPeer)
+				mockCDNPeer.FSM.SetState(PeerStateSucceeded)
+
+				assert.False(task.IsCDNFailed())
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			mockHost := NewHost(mockRawHost)
+			mockCDNHost := NewHost(mockRawCDNHost, WithIsCDN(true))
+			task := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 			mockCDNPeer := NewPeer(mockCDNPeerID, task, mockCDNHost)
 
@@ -431,7 +482,7 @@ func TestTask_LoadPiece(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		pieceInfo         *base.PieceInfo
 		pieceNum          int32
 		expect            func(t *testing.T, piece *base.PieceInfo, ok bool)
@@ -480,7 +531,7 @@ func TestTask_LoadPiece(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 
 			task.StorePiece(tc.pieceInfo)
 			piece, ok := task.LoadPiece(tc.pieceNum)
@@ -495,7 +546,7 @@ func TestTask_StorePiece(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		pieceInfo         *base.PieceInfo
 		pieceNum          int32
 		expect            func(t *testing.T, piece *base.PieceInfo, ok bool)
@@ -532,7 +583,7 @@ func TestTask_StorePiece(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 
 			tc.pieceInfo.PieceNum = tc.pieceNum
 			task.StorePiece(tc.pieceInfo)
@@ -548,7 +599,7 @@ func TestTask_LoadOrStorePiece(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		pieceInfo         *base.PieceInfo
 		pieceNum          int32
 		expect            func(t *testing.T, task *Task, mockPiece *base.PieceInfo)
@@ -590,7 +641,7 @@ func TestTask_LoadOrStorePiece(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 
 			task.StorePiece(tc.pieceInfo)
 			tc.expect(t, task, tc.pieceInfo)
@@ -604,7 +655,7 @@ func TestTask_DeletePiece(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		pieceInfo         *base.PieceInfo
 		pieceNum          int32
 		expect            func(t *testing.T, task *Task)
@@ -642,7 +693,7 @@ func TestTask_DeletePiece(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 
 			task.StorePiece(tc.pieceInfo)
 			task.DeletePiece(tc.pieceNum)
@@ -657,7 +708,7 @@ func TestTask_SizeScope(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		contentLength     int64
 		totalPieceCount   int32
 		expect            func(t *testing.T, task *Task)
@@ -705,7 +756,7 @@ func TestTask_SizeScope(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			task.ContentLength.Store(tc.contentLength)
 			task.TotalPieceCount.Store(tc.totalPieceCount)
 			tc.expect(t, task)
@@ -719,7 +770,7 @@ func TestTask_CanBackToSource(t *testing.T) {
 		id                string
 		urlMeta           *base.UrlMeta
 		url               string
-		backToSourceLimit int
+		backToSourceLimit int32
 		expect            func(t *testing.T, task *Task)
 	}{
 		{
@@ -744,11 +795,23 @@ func TestTask_CanBackToSource(t *testing.T) {
 				assert.Equal(task.CanBackToSource(), false)
 			},
 		},
+		{
+			name:              "task type is TaskTypeDfcache",
+			id:                mockTaskID,
+			urlMeta:           mockTaskURLMeta,
+			url:               mockTaskURL,
+			backToSourceLimit: 1,
+			expect: func(t *testing.T, task *Task) {
+				assert := assert.New(t)
+				task.Type = TaskTypeDfcache
+				assert.Equal(task.CanBackToSource(), false)
+			},
+		},
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			task := NewTask(tc.id, tc.url, tc.backToSourceLimit, tc.urlMeta)
+			task := NewTask(tc.id, tc.url, TaskTypeNormal, tc.urlMeta, WithBackToSourceLimit(tc.backToSourceLimit))
 			tc.expect(t, task)
 		})
 	}
@@ -827,7 +890,7 @@ func TestTask_NotifyPeers(t *testing.T) {
 			stream := rpcschedulermocks.NewMockScheduler_ReportPieceResultServer(ctl)
 
 			mockHost := NewHost(mockRawHost)
-			task := NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := NewTask(mockTaskID, mockTaskURL, TaskTypeNormal, mockTaskURLMeta, WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := NewPeer(mockPeerID, task, mockHost)
 			task.StorePeer(mockPeer)
 			tc.run(t, task, mockPeer, stream, stream.EXPECT())
diff --git a/scheduler/scheduler/evaluator/evaluator_base_test.go b/scheduler/scheduler/evaluator/evaluator_base_test.go
index 95158b1d..34aaac82 100644
--- a/scheduler/scheduler/evaluator/evaluator_base_test.go
+++ b/scheduler/scheduler/evaluator/evaluator_base_test.go
@@ -49,10 +49,10 @@ var (
 			"content-length": "100",
 		},
 	}
-	mockTaskURL               = "http://example.com/foo"
-	mockTaskBackToSourceLimit = 200
-	mockTaskID                = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
-	mockPeerID                = idgen.PeerID("127.0.0.1")
+	mockTaskBackToSourceLimit int32 = 200
+	mockTaskURL                     = "http://example.com/foo"
+	mockTaskID                      = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
+	mockPeerID                      = idgen.PeerID("127.0.0.1")
 )
 
 func TestEvaluatorBase_NewEvaluatorBase(t *testing.T) {
@@ -78,9 +78,9 @@ func TestEvaluatorBase_NewEvaluatorBase(t *testing.T) {
 
 func TestEvaluatorBase_Evaluate(t *testing.T) {
 	parentMockHost := resource.NewHost(mockRawHost)
-	parentMockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	parentMockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 	childMockHost := resource.NewHost(mockRawHost)
-	childMockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	childMockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name            string
@@ -162,7 +162,7 @@ func TestEvaluatorBase_Evaluate(t *testing.T) {
 
 func TestEvaluatorBase_calculatePieceScore(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name            string
@@ -306,7 +306,7 @@ func TestEvaluatorBase_calculateFreeLoadScore(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			host := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, host)
 			tc.mock(host, mockPeer)
 			tc.expect(t, calculateFreeLoadScore(host))
@@ -355,7 +355,7 @@ func TestEvaluatorBase_calculateHostTypeAffinityScore(t *testing.T) {
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			tc.mock(peer)
 			tc.expect(t, calculateHostTypeAffinityScore(peer))
@@ -560,7 +560,7 @@ func TestEvaluatorBase_calculateMultiElementAffinityScore(t *testing.T) {
 
 func TestEvaluatorBase_IsBadNode(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name            string
diff --git a/scheduler/scheduler/scheduler_test.go b/scheduler/scheduler/scheduler_test.go
index 1e984684..e39c7f8d 100644
--- a/scheduler/scheduler/scheduler_test.go
+++ b/scheduler/scheduler/scheduler_test.go
@@ -45,7 +45,7 @@ var (
 		RetryLimit:           2,
 		RetryBackSourceLimit: 1,
 		RetryInterval:        10 * time.Millisecond,
-		BackSourceCount:      mockTaskBackToSourceLimit,
+		BackSourceCount:      int(mockTaskBackToSourceLimit),
 		Algorithm:            evaluator.DefaultAlgorithm,
 	}
 	mockRawHost = &rpcscheduler.PeerHost{
@@ -79,11 +79,11 @@ var (
 			"content-length": "100",
 		},
 	}
-	mockTaskURL               = "http://example.com/foo"
-	mockTaskBackToSourceLimit = 200
-	mockTaskID                = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
-	mockPeerID                = idgen.PeerID("127.0.0.1")
-	mockCDNPeerID             = idgen.CDNPeerID("127.0.0.1")
+	mockTaskBackToSourceLimit int32 = 200
+	mockTaskURL                     = "http://example.com/foo"
+	mockTaskID                      = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
+	mockPeerID                      = idgen.PeerID("127.0.0.1")
+	mockCDNPeerID                   = idgen.CDNPeerID("127.0.0.1")
 )
 
 func TestScheduler_New(t *testing.T) {
@@ -431,7 +431,7 @@ func TestScheduler_ScheduleParent(t *testing.T) {
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			ctx, cancel := context.WithCancel(context.Background())
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			mockCDNHost := resource.NewHost(mockRawCDNHost, resource.WithIsCDN(true))
 			cdnPeer := resource.NewPeer(mockCDNPeerID, mockTask, mockCDNHost)
@@ -673,7 +673,7 @@ func TestScheduler_NotifyAndFindParent(t *testing.T) {
 				peer.FSM.SetState(resource.PeerStateRunning)
 				mockPeer.FSM.SetState(resource.PeerStateRunning)
 				mockHost := resource.NewHost(mockRawHost)
-				mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+				mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 				stealPeer := resource.NewPeer(idgen.PeerID("127.0.0.1"), mockTask, mockHost)
 				stealPeer.FSM.SetState(resource.PeerStateRunning)
 				peer.Task.StorePeer(mockPeer)
@@ -704,7 +704,7 @@ func TestScheduler_NotifyAndFindParent(t *testing.T) {
 			stream := rpcschedulermocks.NewMockScheduler_ReportPieceResultServer(ctl)
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			mockPeer := resource.NewPeer(idgen.PeerID("127.0.0.1"), mockTask, mockHost)
 			blocklist := set.NewSafeSet()
@@ -875,7 +875,7 @@ func TestScheduler_FindParent(t *testing.T) {
 			defer ctl.Finish()
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 
 			var mockPeers []*resource.Peer
@@ -963,7 +963,7 @@ func TestScheduler_constructSuccessPeerPacket(t *testing.T) {
 			defer ctl.Finish()
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			parent := resource.NewPeer(idgen.PeerID("127.0.0.1"), mockTask, mockHost)
diff --git a/scheduler/service/service_test.go b/scheduler/service/service_test.go
index 1575a24f..6bc8e775 100644
--- a/scheduler/service/service_test.go
+++ b/scheduler/service/service_test.go
@@ -54,7 +54,7 @@ var (
 		RetryLimit:           10,
 		RetryBackSourceLimit: 3,
 		RetryInterval:        10 * time.Millisecond,
-		BackSourceCount:      mockTaskBackToSourceLimit,
+		BackSourceCount:      int(mockTaskBackToSourceLimit),
 	}
 	mockRawHost = &rpcscheduler.PeerHost{
 		Uuid:           idgen.HostID("hostname", 8003),
@@ -87,11 +87,11 @@ var (
 			"content-length": "100",
 		},
 	}
-	mockTaskURL               = "http://example.com/foo"
-	mockTaskBackToSourceLimit = 200
-	mockTaskID                = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
-	mockPeerID                = idgen.PeerID("127.0.0.1")
-	mockCDNPeerID             = idgen.CDNPeerID("127.0.0.1")
+	mockTaskURL                     = "http://example.com/foo"
+	mockTaskBackToSourceLimit int32 = 200
+	mockTaskID                      = idgen.TaskID(mockTaskURL, mockTaskURLMeta)
+	mockPeerID                      = idgen.PeerID("127.0.0.1")
+	mockCDNPeerID                   = idgen.CDNPeerID("127.0.0.1")
 )
 
 func TestService_New(t *testing.T) {
@@ -601,7 +601,7 @@ func TestService_RegisterPeerTask(t *testing.T) {
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			mockCDNHost := resource.NewHost(mockRawCDNHost)
 			mockCDNPeer := resource.NewPeer(mockCDNPeerID, mockTask, mockCDNHost)
@@ -860,7 +860,7 @@ func TestService_ReportPieceResult(t *testing.T) {
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			tc.mock(mockPeer, res, peerManager, res.EXPECT(), peerManager.EXPECT(), stream.EXPECT())
 			tc.expect(t, mockPeer, svc.ReportPieceResult(stream))
@@ -1056,7 +1056,7 @@ func TestService_ReportPeerResult(t *testing.T) {
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			tc.mock(mockPeer, res, peerManager, res.EXPECT(), peerManager.EXPECT(), storage.EXPECT())
 			tc.expect(t, mockPeer, svc.ReportPeerResult(context.Background(), tc.req))
@@ -1066,7 +1066,7 @@ func TestService_ReportPeerResult(t *testing.T) {
 
 func TestService_LeaveTask(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name   string
@@ -1602,7 +1602,7 @@ func TestService_registerTask(t *testing.T) {
 
 			taskManager := resource.NewMockTaskManager(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			cdn := resource.NewMockCDN(ctl)
 			tc.run(t, svc, tc.req, mockTask, mockPeer, taskManager, cdn, res.EXPECT(), taskManager.EXPECT(), cdn.EXPECT())
@@ -1693,7 +1693,7 @@ func TestService_registerHost(t *testing.T) {
 			mockHost := resource.NewHost(mockRawHost)
 
 			tc.mock(mockHost, hostManager, res.EXPECT(), hostManager.EXPECT(), dynconfig.EXPECT())
-			host := svc.registerHost(context.Background(), tc.req)
+			host := svc.registerHost(context.Background(), tc.req.PeerHost)
 			tc.expect(t, host)
 		})
 	}
@@ -1752,7 +1752,7 @@ func TestService_triggerCDNTask(t *testing.T) {
 			storage := storagemocks.NewMockStorage(ctl)
 			cdn := resource.NewMockCDN(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			task := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, task, mockHost)
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 
@@ -1831,7 +1831,7 @@ func TestService_handleBeginOfPiece(t *testing.T) {
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			storage := storagemocks.NewMockStorage(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 
@@ -1898,11 +1898,11 @@ func TestService_registerPeer(t *testing.T) {
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig}, res, scheduler, dynconfig, storage)
 			peerManager := resource.NewMockPeerManager(ctl)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			mockPeer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 
 			tc.mock(mockPeer, peerManager, res.EXPECT(), peerManager.EXPECT())
-			peer := svc.registerPeer(context.Background(), tc.req, mockTask, mockHost)
+			peer := svc.registerPeer(context.Background(), tc.req.PeerId, mockTask, mockHost, tc.req.UrlMeta.Tag)
 			tc.expect(t, peer)
 		})
 	}
@@ -1910,7 +1910,7 @@ func TestService_registerPeer(t *testing.T) {
 
 func TestService_handlePieceSuccess(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 	now := time.Now()
 
 	tests := []struct {
@@ -1987,7 +1987,7 @@ func TestService_handlePieceSuccess(t *testing.T) {
 
 func TestService_handlePieceFail(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name   string
@@ -2398,7 +2398,7 @@ func TestService_handlePeerSuccess(t *testing.T) {
 			mockRawHost.Ip = ip
 			mockRawHost.DownPort = int32(port)
 			mockHost := resource.NewHost(mockRawHost)
-			mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 			peer := resource.NewPeer(mockPeerID, mockTask, mockHost)
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig, Metrics: &config.MetricsConfig{EnablePeerHost: true}}, res, scheduler, dynconfig, storage)
 
@@ -2411,7 +2411,7 @@ func TestService_handlePeerSuccess(t *testing.T) {
 
 func TestService_handlePeerFail(t *testing.T) {
 	mockHost := resource.NewHost(mockRawHost)
-	mockTask := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+	mockTask := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 	tests := []struct {
 		name   string
@@ -2563,7 +2563,7 @@ func TestService_handleTaskSuccess(t *testing.T) {
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			storage := storagemocks.NewMockStorage(ctl)
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig, Metrics: &config.MetricsConfig{EnablePeerHost: true}}, res, scheduler, dynconfig, storage)
-			task := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 			tc.mock(task)
 			svc.handleTaskSuccess(context.Background(), task, tc.result)
@@ -2641,7 +2641,7 @@ func TestService_handleTaskFail(t *testing.T) {
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			storage := storagemocks.NewMockStorage(ctl)
 			svc := New(&config.Config{Scheduler: mockSchedulerConfig, Metrics: &config.MetricsConfig{EnablePeerHost: true}}, res, scheduler, dynconfig, storage)
-			task := resource.NewTask(mockTaskID, mockTaskURL, mockTaskBackToSourceLimit, mockTaskURLMeta)
+			task := resource.NewTask(mockTaskID, mockTaskURL, resource.TaskTypeNormal, mockTaskURLMeta, resource.WithBackToSourceLimit(mockTaskBackToSourceLimit))
 
 			tc.mock(task)
 			svc.handleTaskFail(context.Background(), task)