mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-01 00:32:05 +00:00
fix(pixiv): keep a frame-zip download hiccup retryable
download_media_to_file's non-transport errors all folded into PixivError::Api, and Api is permanent in pixiv_error_is_retryable — so one 429/5xx, stalled transfer or temp-file write failure while fetching the ugoira frame zip permanently failed the whole post, while the bot's own upload downloads classify the very same classes as retryable (classify_download_error). A new PixivError::Transient carries those classes into the existing retry policy (and into startup validation's 'stays enabled' branch); Http keeps its arm and everything else stays permanent. The retryable table and the startup-validation loop pin both halves.
This commit is contained in:
@@ -39,6 +39,11 @@ pub enum PixivError {
|
|||||||
Status(u16),
|
Status(u16),
|
||||||
#[error("pixiv api error: {0}")]
|
#[error("pixiv api error: {0}")]
|
||||||
Api(String),
|
Api(String),
|
||||||
|
/// A bad moment while preparing media: a transient download status
|
||||||
|
/// (429 / 5xx), a stalled transfer or a temp-file write failure. A retry
|
||||||
|
/// can change the answer, so the pixiv retry policy re-fetches these.
|
||||||
|
#[error("transient pixiv error: {0}")]
|
||||||
|
Transient(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Native pixiv app-API client.
|
/// Native pixiv app-API client.
|
||||||
@@ -228,6 +233,14 @@ impl PixivAPI {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
FetchError::Http(e) => PixivError::Http(e),
|
FetchError::Http(e) => PixivError::Http(e),
|
||||||
|
// A bad moment (429/5xx, a stalled transfer, a temp-file
|
||||||
|
// write failure) must stay retryable: folding it into Api
|
||||||
|
// made one hiccup permanently fail the whole ugoira post,
|
||||||
|
// while the bot's own upload downloads retry the same
|
||||||
|
// classes.
|
||||||
|
transient @ (FetchError::Transient(_) | FetchError::Io(_)) => {
|
||||||
|
PixivError::Transient(format!("frame zip download failed: {transient}"))
|
||||||
|
}
|
||||||
other => PixivError::Api(format!("frame zip download failed: {other}")),
|
other => PixivError::Api(format!("frame zip download failed: {other}")),
|
||||||
})?;
|
})?;
|
||||||
let frame_delays = metadata.frames.iter().map(|f| f.delay).collect::<Vec<_>>();
|
let frame_delays = metadata.frames.iter().map(|f| f.delay).collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub fn is_retryable(err: &FetchError) -> bool {
|
|||||||
/// rejected credential is not.
|
/// rejected credential is not.
|
||||||
fn pixiv_error_is_retryable(err: &PixivError) -> bool {
|
fn pixiv_error_is_retryable(err: &PixivError) -> bool {
|
||||||
match err {
|
match err {
|
||||||
PixivError::Http(_) => true,
|
PixivError::Http(_) | PixivError::Transient(_) => true,
|
||||||
PixivError::Status(code) if *code == 429 || *code >= 500 => true,
|
PixivError::Status(code) if *code == 429 || *code >= 500 => true,
|
||||||
PixivError::Status(_) | PixivError::Api(_) | PixivError::Json(_) | PixivError::NoAuth => {
|
PixivError::Status(_) | PixivError::Api(_) | PixivError::Json(_) | PixivError::NoAuth => {
|
||||||
false
|
false
|
||||||
@@ -443,7 +443,11 @@ mod tests {
|
|||||||
// it calls `disable()`, a process-wide flag with no reset, so a test
|
// it calls `disable()`, a process-wide flag with no reset, so a test
|
||||||
// touching it would order-couple every other pixiv test (the predicate
|
// touching it would order-couple every other pixiv test (the predicate
|
||||||
// it keys on is covered by the table below).
|
// it keys on is covered by the table below).
|
||||||
for err in [PixivError::Status(429), PixivError::Status(503)] {
|
for err in [
|
||||||
|
PixivError::Status(429),
|
||||||
|
PixivError::Status(503),
|
||||||
|
PixivError::Transient("frame zip download failed: transient".into()),
|
||||||
|
] {
|
||||||
let enabled_before = api::enabled();
|
let enabled_before = api::enabled();
|
||||||
let message = startup_validation(Err(err)).unwrap_err();
|
let message = startup_validation(Err(err)).unwrap_err();
|
||||||
assert!(message.contains("stays enabled"), "{message}");
|
assert!(message.contains("stays enabled"), "{message}");
|
||||||
@@ -458,11 +462,15 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_retryable_classifies_transient_and_permanent() {
|
fn is_retryable_classifies_transient_and_permanent() {
|
||||||
// Transient: network errors, explicit transient, pixiv 429/5xx.
|
// Transient: network errors, explicit transient, pixiv 429/5xx, and a
|
||||||
|
// failed media download (the frame zip's own bad moment).
|
||||||
assert!(is_retryable(&FetchError::Transient("429".into())));
|
assert!(is_retryable(&FetchError::Transient("429".into())));
|
||||||
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(429))));
|
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(429))));
|
||||||
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(500))));
|
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(500))));
|
||||||
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(503))));
|
assert!(is_retryable(&FetchError::Pixiv(PixivError::Status(503))));
|
||||||
|
assert!(is_retryable(&FetchError::Pixiv(PixivError::Transient(
|
||||||
|
"frame zip download failed: transient: media status 429".into()
|
||||||
|
))));
|
||||||
// Permanent: pixiv 4xx (bad/expired token, forbidden, not found),
|
// Permanent: pixiv 4xx (bad/expired token, forbidden, not found),
|
||||||
// api/auth errors, unparseable bodies, not-found/blocked/sensitive.
|
// api/auth errors, unparseable bodies, not-found/blocked/sensitive.
|
||||||
assert!(!is_retryable(&FetchError::Pixiv(PixivError::Status(400))));
|
assert!(!is_retryable(&FetchError::Pixiv(PixivError::Status(400))));
|
||||||
|
|||||||
Reference in New Issue
Block a user