Files
telegram-exporter/cmd/uidemo/main.go
T
tiennm99 b05247abed fix: retry a failed upload instead of abandoning the file
pikpak commits an upload as a server-side async task, and rclone polls that
task only as long as its low-level retries last. A task still in
PHASE_TYPE_PENDING when that budget runs out is reported as "can't verify the
task is completed" — and checking the live archive ten minutes later, those
tasks had not committed: the objects were simply absent.

The transfer itself was fine; only the confirmation timed out. Abandoning the
file meant re-downloading it from Telegram on the next pass, which for this
archive can be two gigabytes, so a run against a slow remote paid for the same
bytes repeatedly. The staged copy is still on disk when a move fails — rclone
says so in the same breath — so another attempt costs seconds instead.

Every attempt after the first checks the remote before re-uploading. A pending
task may have committed during the backoff, and pikpak allows two files under
one name, so re-uploading blind is how one file becomes two — which
verification then reports as an ambiguous basename on every future run.

rclone's log now goes through the renderer. It writes to stderr on its own
schedule, so its error lines were landing mid-redraw and shredding the display
exactly when there was most to read.

Also: "capped at uncapped" now reads "uncapped", and the ETA column fits the
three-digit hour counts a slow remote produces.
2026-09-06 21:54:38 +07:00

85 lines
2.5 KiB
Go

// Command uidemo renders the sync progress UI with fake data, so the layout can
// be checked without a Telegram session or a multi-hour transfer.
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/iyear/tdl/core/tmedia"
"github.com/rclone/rclone/fs"
"github.com/tiennm99dev/telegram-exporter/internal/pipeline"
"github.com/tiennm99dev/telegram-exporter/internal/report"
"github.com/tiennm99dev/telegram-exporter/internal/tgsource"
"github.com/tiennm99dev/telegram-exporter/internal/verify"
)
func item(id int, name string, size int64) tgsource.Item {
return tgsource.Item{MessageID: id, Name: name, Media: &tmedia.Media{Size: size}}
}
func main() {
files := []tgsource.Item{
item(4242, "1234567890_4242_1000000000000000001.mp4", 96<<20),
item(4243, "1234567890_4243_1000000000000000002.mp4", 1900<<20),
item(4244, "1234567890_4244_short.jpg", 2<<20),
}
var total int64
for _, f := range files {
total += f.Size()
}
fmt.Fprintln(os.Stderr, "PikPak root 'mychannel' has 1438 GiB free")
fmt.Fprintln(os.Stderr, "reading mychannel")
fmt.Fprintln(os.Stderr, " 12,000 messages read in 4m31s")
fmt.Fprintln(os.Stderr, "indexing PikPak root 'mychannel'")
fmt.Fprintln(os.Stderr, " 11,406 objects listed in 2m10s")
survey := verify.Report{Expected: 12000, Present: 11400, Bytes: 518 << 30}
for i := range 2607 {
survey.Absent = append(survey.Absent, 4242+i)
}
for i := range 6 {
survey.Mismatched = append(survey.Mismatched,
verify.Mismatch{MessageID: 14290 + i, Want: 2 << 30, Got: 221 << 20})
}
report.Survey(os.Stderr, survey)
report.Plan(os.Stderr, report.PlanInfo{
Files: 2613, Bytes: 79 << 30, Largest: 2 << 30, Budget: 40 << 30,
Staging: "./staging", Threads: 4, Downloads: 2, Uploads: 2,
Destination: "PikPak root 'mychannel'",
})
ev := report.Events(os.Stderr, 2613, 79<<30)
if live, ok := ev.(*report.Live); ok {
defer report.CaptureRcloneLog(context.Background(), live.LogWriter())()
}
st := pipeline.Stats{}
for _, f := range files {
ev.DownloadStart(f)
}
for step := range 30 {
if step == 12 {
fs.Errorf(nil, "1234567890_4245_1000000000000000003.jpg: Failed to copy: "+
"can't verify the task is completed")
}
for _, f := range files {
ev.DownloadBytes(f, f.Size()*int64(step+1)/30)
}
st.BytesDone += total / 30
ev.Stats(st)
time.Sleep(60 * time.Millisecond)
}
for i, f := range files {
ev.DownloadDone(f, nil)
ev.UploadStart(f)
st.Done = i + 1
ev.Stats(st)
time.Sleep(400 * time.Millisecond)
ev.UploadDone(f, nil)
}
ev.Finish(st)
}