Skip to content

Commit f26c6b0

Browse files
committed
added stashed_files method, new logic to try upload error recovery
1 parent 384f740 commit f26c6b0

File tree

4 files changed

+46
-10
lines changed

4 files changed

+46
-10
lines changed

pwiki/gquery.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,19 @@ def search(wiki: Wiki, phrase: str, ns: list[Union[NS, str]] = [], limit: Union[
244244
"""
245245
return GQuery._list_cont(wiki, limit, ListCont.SEARCH, {"srsearch": phrase} | ({"srnamespace": wiki.ns_manager.create_filter(ns)} if ns else {}))
246246

247+
@staticmethod
248+
def stashed_files(wiki: Wiki, limit: Union[int, str] = 1) -> Generator[list[tuple[str, int, str]], None, None]:
249+
"""Fetch the user's stashed files. PRECONDITION: You must be logged in for this to work
250+
251+
Args:
252+
wiki (Wiki): The Wiki object to use
253+
limit (Union[int, str], optional): The maxmimum number of elements to fetch each iteration. Defaults to 1.
254+
255+
Returns:
256+
Generator[list[tuple[str, int]], None, None]: A `Generator` which yields a `list` of 3-`tuple` where each tuple is of the form (file key, file size, status). Known values for status: `"finished"`, `"chunks"`
257+
"""
258+
return GQuery._list_cont(wiki, limit, ListCont.STASHED_FILES)
259+
247260
@staticmethod
248261
def user_uploads(wiki: Wiki, user: str, limit: Union[int, str] = 1) -> Generator[list[str], None, None]:
249262
"""Gets the uploads of a user.

pwiki/query_constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ class ListCont:
7575
CATEGORY_MEMBERS = QConstant("categorymembers", limit_key="cmlimit")
7676
CONTRIBS = QConstant("usercontribs", limit_key="uclimit", retrieve_results=lambda l: [Contrib(e) for e in l])
7777
DUPLICATE_FILES = QConstant("querypage", {"qppage": "ListDuplicatedFiles"}, "qplimit", lambda q: [e["title"] for e in q["results"]])
78-
LOGS = QConstant("logevents", {"leprop": "title|type|user|timestamp|comment|tags"}, "lelimit", retrieve_results=lambda l: [Log(e) for e in l])
78+
LOGS = QConstant("logevents", {"leprop": "title|type|user|timestamp|comment|tags"}, "lelimit", lambda l: [Log(e) for e in l])
7979
PREFIX_INDEX = QConstant("allpages", limit_key="aplimit")
8080
RANDOM = QConstant("random", {"rnfilterredir": "nonredirects"}, "rnlimit")
8181
SEARCH = QConstant("search", {"srprop": ""}, "srlimit")
82+
STASHED_FILES = QConstant("mystashedfiles", {"msfprop": "size"}, "msflimit", lambda l: [(e["filekey"], e["size"], e["status"]) for e in l])
8283
USER_UPLOADS = QConstant("allimages", {"aisort": "timestamp"}, "ailimit")

pwiki/waction.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from time import sleep
99
from typing import TYPE_CHECKING
1010

11+
from .ns import NS
1112
from .oquery import OQuery
1213
from .query_utils import chunker
1314
from .utils import has_error, make_params, mine_for, read_error
@@ -167,7 +168,7 @@ def purge(wiki: Wiki, titles: Iterable[str]) -> bool:
167168
return all(WAction._post_action(wiki, "purge", {"titles": "|".join(chunk)}) for chunk in chunker(titles, wiki.prop_title_max))
168169

169170
@staticmethod
170-
def unstash(wiki: Wiki, filekey: str, title: str, desc: str = "", summary: str = "", max_retries: int = 5, retry_interval: int = 5) -> bool:
171+
def unstash(wiki: Wiki, filekey: str, title: str, desc: str = "", summary: str = "", max_retries: int = 5, retry_interval: int = 30) -> bool:
171172
"""Attempt to unstash a file uploaded to the file stash.
172173
173174
Args:
@@ -177,7 +178,7 @@ def unstash(wiki: Wiki, filekey: str, title: str, desc: str = "", summary: str =
177178
desc (str, optional): The text to go on the file description page. Defaults to "".
178179
summary (str, optional): The upload log summary to use. Defaults to "".
179180
max_retries (int, optional): The maximum number of retry in the event of failure (assuming the server expereinced an error). Defaults to 5.
180-
retry_interval (int, optional): The number of seconds to wait in between retries. Set 0 to disable. Defaults to 5.
181+
retry_interval (int, optional): The number of seconds to wait in between retries. Set 0 to disable. Defaults to 30.
181182
182183
Returns:
183184
bool: True if unstashing was successful
@@ -186,7 +187,7 @@ def unstash(wiki: Wiki, filekey: str, title: str, desc: str = "", summary: str =
186187

187188
tries = 0
188189
status = False
189-
while tries < max_retries and not (status := bool(WAction._action_and_validate(wiki, "upload", {"filename": title, "text": desc, "comment": summary, "filekey": filekey, "ignorewarnings": 1}, timeout=360))):
190+
while tries < max_retries and not (status := bool(WAction._action_and_validate(wiki, "upload", {"filename": title, "text": desc, "comment": summary, "filekey": filekey, "ignorewarnings": 1}, timeout=360)) or wiki.exists(wiki.convert_ns(title, NS.FILE))):
190191
log.warning("%s: Unstash failed, this is a attempt %d of %d. Sleeping %ds...", wiki, tries + 1, max_retries, retry_interval)
191192
sleep(retry_interval)
192193
tries += 1
@@ -224,12 +225,24 @@ def upload_only(wiki: Wiki, path: Path, title: str, max_retries: int = 5) -> str
224225
chunk_count += 1
225226
pl["offset"] = _CHUNKSIZE * chunk_count
226227
pl["filekey"] = filekey
227-
continue
228-
229-
err_count += 1
230-
log.warning("%s: Encountered error while uploading, this was %d/%d", wiki, err_count, max_retries)
231-
if err_count > max_retries:
232-
log.error("%s: Exceeded error threshold, abort.", wiki)
228+
else:
229+
err_count += 1
230+
log.warning("%s: Encountered error while uploading, this was %d/%d", wiki, err_count, max_retries)
231+
if err_count > max_retries:
232+
log.error("%s: Exceeded error threshold, abort.", wiki)
233+
return
234+
235+
if chunk_count == total_chunks - 1: # a poorly configured MediaWiki installation may fail to acknowledge the final chunk, but we can attempt recovery on our end
236+
for i in range(max_retries):
237+
log.info("%s: Attempting to unmangle filekey, '%s'. Attempt %d/%d, but first sleeping 30s...", wiki, pl["filekey"], i+1, max_retries)
238+
sleep(30)
239+
240+
if t := next((e for e in wiki.stashed_files() if e[0] == pl["filekey"] or e[1] == fsize), None):
241+
if t[2] == "finished":
242+
log.info("%s: Found a matching filekey: '%s'", wiki, t[0])
243+
return t[0]
244+
else:
245+
log.error("No matching filekey found, unable to recover from MediaWiki error!")
233246
return
234247

235248
return pl.get("filekey")

pwiki/wiki.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,15 @@ def search(self, phrase: str, ns: list[Union[NS, str]] = []) -> list[str]:
697697
log.info("%s: Searching the wiki for '%s'", self, phrase)
698698
return flatten_generator(GQuery.search(self, phrase, ns, MAX))
699699

700+
def stashed_files(self) -> list[tuple[str, int, str]]:
701+
"""Fetch the user's stashed files. PRECONDITION: You must be logged in for this to work
702+
703+
Returns:
704+
list[tuple[str, int]]: a `list` of 3-`tuple` where each tuple is of the form (file key, file size, status). Known values for status: `"finished"`, `"chunks"`
705+
"""
706+
log.info("%s: fetching user's stashed files...", self)
707+
return flatten_generator(GQuery.stashed_files(self, MAX))
708+
700709
def templates_on_page(self, title: str) -> list[str]:
701710
"""Fetch templates transcluded on a page.
702711

0 commit comments

Comments
 (0)