mirror of
https://github.com/tiennm99/telegram-exporter.git
synced 2026-10-14 02:16:21 +00:00
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.
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
package report
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/tiennm99dev/telegram-exporter/internal/verify"
|
|
)
|
|
|
|
// writeSurvey states what the chat holds and how much of it is already archived,
|
|
// broken out by why each outstanding file is outstanding. The single "N to
|
|
// fetch" it replaces hid the difference between never-fetched, empty,
|
|
// wrong-size, and unarchivable — which is the difference between a run that will
|
|
// converge and one that cannot.
|
|
func Survey(w io.Writer, r verify.Report) {
|
|
fmt.Fprintf(w, "\n chat holds %s media, %s\n",
|
|
humanCount(r.Expected), humanBytes(r.Bytes))
|
|
fmt.Fprintf(w, " archived %s\n", humanCount(r.Present))
|
|
|
|
for _, row := range []struct {
|
|
label string
|
|
n int
|
|
}{
|
|
{"never fetched", len(r.Absent) - len(r.Unsafe)},
|
|
{"zero-byte", len(r.ZeroByte)},
|
|
{"wrong size", len(r.Mismatched)},
|
|
{"unarchivable", len(r.Unsafe)},
|
|
} {
|
|
if row.n > 0 {
|
|
fmt.Fprintf(w, " %-13s %s\n", row.label, humanCount(row.n))
|
|
}
|
|
}
|
|
}
|
|
|
|
// PlanInfo is what the run is about to do, stated before it starts.
|
|
type PlanInfo struct {
|
|
Files int
|
|
Bytes int64
|
|
Largest int64
|
|
Budget int64
|
|
Staging string
|
|
Threads int
|
|
Downloads, Uploads int
|
|
Destination string
|
|
}
|
|
|
|
// writePlan prints the settings that decide how long the run takes and how much
|
|
// disk it uses, so an operator can stop it before a multi-hour transfer rather
|
|
// than discover the wrong cap partway through.
|
|
func Plan(w io.Writer, p PlanInfo) {
|
|
cap := "uncapped"
|
|
if p.Budget > 0 {
|
|
cap = "capped at " + humanBytes(p.Budget)
|
|
}
|
|
fmt.Fprintf(w, "\n fetching %s files, %s (largest %s)\n",
|
|
humanCount(p.Files), humanBytes(p.Bytes), humanBytes(p.Largest))
|
|
fmt.Fprintf(w, " into %s\n", p.Destination)
|
|
fmt.Fprintf(w, " staging %s, %s\n", p.Staging, cap)
|
|
fmt.Fprintf(w, " concurrency %d download(s) x %d thread(s), %d upload(s)\n\n",
|
|
p.Downloads, p.Threads, p.Uploads)
|
|
}
|