feat(core): persist tasks and recover them after restart

This commit is contained in:
krau
2026-08-25 15:05:55 +08:00
parent 54dc4caafe
commit 991f454096
14 changed files with 938 additions and 68 deletions
+86
View File
@@ -0,0 +1,86 @@
package tfile
import (
"github.com/gotd/td/telegram/downloader"
"github.com/gotd/td/tg"
)
// Payloadable is implemented by TGFile implementations that can serialize
// themselves for task recovery.
type Payloadable interface {
Payload() FilePayload
}
// FilePayloadOf returns the serializable form of f.
func FilePayloadOf(f TGFile) (FilePayload, bool) {
p, ok := f.(Payloadable)
if !ok {
return FilePayload{}, false
}
return p.Payload(), true
}
// FilePayload is the minimal serializable representation of a TGFile,
// used to rebuild tasks after a process restart.
type FilePayload struct {
Kind string `json:"kind"` // "document" | "photo"
ID int64 `json:"id"`
AccessHash int64 `json:"access_hash"`
FileReference []byte `json:"file_reference"`
ThumbSize string `json:"thumb_size"`
Size int64 `json:"size"`
Name string `json:"name"`
}
// Payload returns the serializable representation of the file.
func (f *tgFile) Payload() FilePayload {
p := FilePayload{
Size: f.size,
Name: f.name,
}
switch loc := f.location.(type) {
case *tg.InputDocumentFileLocation:
p.Kind = "document"
p.ID = loc.ID
p.AccessHash = loc.AccessHash
p.FileReference = loc.FileReference
p.ThumbSize = loc.ThumbSize
case *tg.InputPhotoFileLocation:
p.Kind = "photo"
p.ID = loc.ID
p.AccessHash = loc.AccessHash
p.FileReference = loc.FileReference
p.ThumbSize = loc.ThumbSize
}
return p
}
// Location rebuilds the Telegram file location from the payload.
func (p FilePayload) Location() tg.InputFileLocationClass {
switch p.Kind {
case "photo":
return &tg.InputPhotoFileLocation{
ID: p.ID,
AccessHash: p.AccessHash,
FileReference: p.FileReference,
ThumbSize: p.ThumbSize,
}
default:
return &tg.InputDocumentFileLocation{
ID: p.ID,
AccessHash: p.AccessHash,
FileReference: p.FileReference,
ThumbSize: p.ThumbSize,
}
}
}
// FileFromPayload rebuilds a TGFile from its serialized payload.
func FileFromPayload(p FilePayload, dler downloader.Client) TGFile {
return &tgFile{
location: p.Location(),
dler: dler,
size: p.Size,
name: p.Name,
}
}
+61
View File
@@ -0,0 +1,61 @@
package tfile
import (
"reflect"
"testing"
"github.com/gotd/td/tg"
)
func TestFilePayloadDocumentRoundTrip(t *testing.T) {
file := NewTGFile(
&tg.InputDocumentFileLocation{
ID: 6287403840090150101,
AccessHash: -8452541528324991878,
FileReference: []byte{0x02, 0x0e, 0x80, 0xd6},
ThumbSize: "",
},
nil,
4194304000,
"常轨脱离Creative凸.7z.001",
)
p, ok := FilePayloadOf(file)
if !ok {
t.Fatalf("FilePayloadOf failed")
}
rebuilt := FileFromPayload(p, nil)
if !reflect.DeepEqual(rebuilt.Location(), file.Location()) {
t.Fatalf("location mismatch:\n got %#v\nwant %#v", rebuilt.Location(), file.Location())
}
if rebuilt.Size() != file.Size() || rebuilt.Name() != file.Name() {
t.Fatalf("size/name mismatch: got %d %q, want %d %q", rebuilt.Size(), rebuilt.Name(), file.Size(), file.Name())
}
if p.Kind != "document" {
t.Fatalf("kind = %q, want document", p.Kind)
}
}
func TestFilePayloadPhotoRoundTrip(t *testing.T) {
file := NewTGFile(
&tg.InputPhotoFileLocation{
ID: 123,
AccessHash: 456,
FileReference: []byte{0xaa, 0xbb},
ThumbSize: "y",
},
nil,
0,
"photo_123.png",
)
p, ok := FilePayloadOf(file)
if !ok {
t.Fatalf("FilePayloadOf failed")
}
if p.Kind != "photo" {
t.Fatalf("kind = %q, want photo", p.Kind)
}
rebuilt := FileFromPayload(p, nil)
if !reflect.DeepEqual(rebuilt.Location(), file.Location()) {
t.Fatalf("location mismatch:\n got %#v\nwant %#v", rebuilt.Location(), file.Location())
}
}