def download_addon(client: HttpClient, id: int) -> Union[DownloadOk, DownloadError]: "Fetch a single add-on from AnkiWeb." try: resp = client.get(aqt.appShared + f"download/{id}?v=2.1&p={current_point_version}") if resp.status_code != 200: return DownloadError(status_code=resp.status_code) data = client.streamContent(resp) fname = re.match("attachment; filename=(.+)", resp.headers["content-disposition"]).group(1) meta = extract_meta_from_download_url(resp.url) return DownloadOk( data=data, filename=fname, mod_time=meta.mod_time, min_point_version=meta.min_point_version, max_point_version=meta.max_point_version, branch_index=meta.branch_index, ) except Exception as e: return DownloadError(exception=e)
def download(self): idx = self.lst.currentIndex() if not idx.isValid(): QMessageBox.show(self, self.windowTitle(), 'Please select a language.') return url = idx.data(Qt.UserRole) client = HttpClient() resp = client.get(url) if resp.status_code != 200: QMessageBox.information( self, self.windowTitle(), 'Downloading %s data failed.' % self.mode_str) return data = client.streamContent(resp) dir_path = os.path.join(addon_path, 'user_files', 'db', self.mode_str) os.makedirs(dir_path, exist_ok=True) dst_path = os.path.join(dir_path, '%s.json' % self.dst_lang) with open(dst_path, 'wb') as f: f.write(data) if self.mode == self.Mode.Freq: msg = 'Imported frequency data for "%s".\n\nNote that the frequency data is only applied to newly imported dictionaries for this language.' % self.dst_lang else: msg = 'Imported conjugation data for "%s".' % self.dst_lang QMessageBox.information(self, self.windowTitle(), msg) self.accept()
def download_index(server_url=DEFAULT_SERVER): server_url = normalize_url(server_url) index_url = server_url + '/index.json' client = HttpClient() resp = client.get(index_url) if resp.status_code != 200: return None data = client.streamContent(resp) return json.loads(data)
def check_for_updates(mgr: AddonManager, on_done: Callable[[HttpClient, List[Dict]], None]): client = HttpClient() def check(): return fetch_update_info(client, mgr.ankiweb_addons()) def update_info_received(future: Future): # if syncing/in profile screen, defer message delivery if not mgr.mw.col: mgr.mw.progress.timer( 1000, lambda: update_info_received(future), False, requiresCollection=False, ) return if future.exception(): # swallow network errors print(str(future.exception())) result = [] else: result = future.result() on_done(client, result) mgr.mw.taskman.run_in_background(check, update_info_received)
def download_addon(client: HttpClient, id: int) -> Union[DownloadOk, DownloadError]: "Fetch a single add-on from AnkiWeb." try: resp = client.get(aqt.appShared + f"download/{id}?v=2.1&p={pointVersion}") if resp.status_code != 200: return DownloadError(status_code=resp.status_code) data = client.streamContent(resp) fname = re.match( "attachment; filename=(.+)", resp.headers["content-disposition"] ).group(1) return DownloadOk(data=data, filename=fname) except Exception as e: return DownloadError(exception=e)
def _retrieveURL(self, url): "Download file into media folder and return local filename or None." # urllib doesn't understand percent-escaped utf8, but requires things like # '#' to be escaped. url = urllib.parse.unquote(url) if url.lower().startswith("file://"): url = url.replace("%", "%25") url = url.replace("#", "%23") local = True else: local = False # fetch it into a temporary folder self.mw.progress.start(immediate=not local, parent=self.parentWindow) ct = None try: if local: req = urllib.request.Request( url, None, {"User-Agent": "Mozilla/5.0 (compatible; Anki)"}) filecontents = urllib.request.urlopen(req).read() else: reqs = HttpClient() reqs.timeout = 30 r = reqs.get(url) if r.status_code != 200: showWarning( _("Unexpected response code: %s") % r.status_code) return filecontents = r.content ct = r.headers.get("content-type") except urllib.error.URLError as e: showWarning(_("An error occurred while opening %s") % e) return except requests.exceptions.RequestException as e: showWarning(_("An error occurred while opening %s") % e) return finally: self.mw.progress.finish() # strip off any query string url = re.sub(r"\?.*?$", "", url) fname = os.path.basename(urllib.parse.unquote(url)) if ct: fname = self.mw.col.media.add_extension_based_on_mime(fname, ct) return self.mw.col.media.write_data(fname, filecontents)
def _fetch_update_info_batch(client: HttpClient, chunk: Iterable[str]) -> Iterable[Dict]: """Get update info from AnkiWeb. Chunk must not contain more than 25 ids.""" resp = client.get(aqt.appShared + "updates/" + ",".join(chunk) + "?v=3") if resp.status_code == 200: return resp.json() else: raise Exception("Unexpected response code from AnkiWeb: {}".format( resp.status_code))
def download_addons( parent: QWidget, mgr: AddonManager, ids: List[int], on_done: Callable[[List[DownloadLogEntry]], None], client: Optional[HttpClient] = None, ) -> None: if client is None: client = HttpClient() downloader = DownloaderInstaller(parent, mgr, client) downloader.download(ids, on_done=on_done)
def _retrieveURL(self, url: str) -> Optional[str]: "Download file into media folder and return local filename or None." # urllib doesn't understand percent-escaped utf8, but requires things like # '#' to be escaped. url = urllib.parse.unquote(url) if url.lower().startswith("file://"): url = url.replace("%", "%25") url = url.replace("#", "%23") local = True else: local = False # fetch it into a temporary folder self.mw.progress.start(immediate=not local, parent=self.parentWindow) content_type = None error_msg: Optional[str] = None try: if local: req = urllib.request.Request( url, None, {"User-Agent": "Mozilla/5.0 (compatible; Anki)"}) with urllib.request.urlopen(req) as response: filecontents = response.read() else: with HttpClient() as client: client.timeout = 30 with client.get(url) as response: if response.status_code != 200: error_msg = tr( TR.QT_MISC_UNEXPECTED_RESPONSE_CODE, val=response.status_code, ) return None filecontents = response.content content_type = response.headers.get("content-type") except (urllib.error.URLError, requests.exceptions.RequestException) as e: error_msg = tr(TR.EDITING_AN_ERROR_OCCURRED_WHILE_OPENING, val=str(e)) return None finally: self.mw.progress.finish() if error_msg: showWarning(error_msg) # strip off any query string url = re.sub(r"\?.*?$", "", url) fname = os.path.basename(urllib.parse.unquote(url)) if not fname.strip(): fname = "paste" if content_type: fname = self.mw.col.media.add_extension_based_on_mime( fname, content_type) return self.mw.col.media.write_data(fname, filecontents)
def _retrieveURL(url: str): # url = urllib.parse.unquote(url) url = html.unescape(url) if url.lower().startswith("file://"): url = url.replace("%", "%25") url = url.replace("#", "%23") local = True else: local = False # fetch it into a temporary folder mw.progress.start(immediate=not local) content_type = None error_msg = None try: if local: req = urllib.request.Request( url, None, {"User-Agent": "Mozilla/5.0 (compatible; Anki)"}) with urllib.request.urlopen(req) as response: filecontents = response.read() else: with HttpClient() as client: client.timeout = 30 # print(f"_retrieveURL(): url = '{url}'") with client.get(url) as response: # print(f"_retrieveURL(): response.status_code = '{response.status_code}'") if response.status_code != 200: error_msg = (_("Unexpected response code: %s") % response.status_code) return None filecontents = response.content content_type = response.headers.get("content-type") except (urllib.error.URLError, requests.exceptions.RequestException) as e: error_msg = _("An error occurred while opening %s") % e return None finally: mw.progress.finish() if error_msg: showWarning(error_msg) # strip off any query string fname = os.path.basename(urllib.parse.unquote(url)) if not fname.strip(): fname = "paste" if content_type: fname = mw.col.media.add_extension_based_on_mime(fname, content_type) return mw.col.media.write_data(fname, filecontents)
def install(self, id, course): # check, and ignore, if addin has already been installed if not self.isInstalled(): # to prevent scam installations require user to authorise download # id must be looked up on Anki addin website and hardcoded in command file prompt = 'Install {0} add-in?\nUse Help to view add-in details'.format( self.name) help = 'https://ankiweb.net/shared/info/{0}'.format(id) if askUser(prompt, defaultno=True, help='https://ankiweb.net/shared/info/{0}'.format(id), title=course.name): from anki.httpclient import HttpClient (id, result) = addons.download_and_install_addon( mw.addonManager, HttpClient(), id) if isinstance(result, addons.DownloadError): raise Exception('{0} download failed: {1}'.format( self.name, result))
def run(self): from .dictionaryManager import importDict client = HttpClient() num_dicts = 0 num_installed = 0 def update_dict_progress(amt): progress = 0 if num_dicts > 0: progress = num_installed / num_dicts progress += amt / num_dicts progress_percent = round(progress * 100) self.progress_update.emit(progress_percent) for l in self.install_index: num_dicts += len(l.get('dictionaries', [])) self.log_update.emit('Installing %d dictionaries...' % num_dicts) freq_path = os.path.join(addon_path, 'user_files', 'db', 'frequency') os.makedirs(freq_path, exist_ok=True) conj_path = os.path.join(addon_path, 'user_files', 'db', 'conjugation') os.makedirs(conj_path, exist_ok=True) for l in self.install_index: if self.cancel_requested: return lname = self.force_lang if not lname: lname = l.get('name_en') if not lname: continue # Create Language try: aqt.mw.miDictDB.addLanguages([lname]) except Exception as e: # Lanugage already exists pass # Install frequency data if self.install_freq: furl = l.get('frequency_url') if furl: self.log_update.emit( 'Installing %s frequency data...' % lname) furl = self.construct_url(furl) dl_resp = client.get(furl) if dl_resp.status_code == 200: fdata = client.streamContent(dl_resp) dst_path = os.path.join(freq_path, '%s.json' % lname) with open(dst_path, 'wb') as f: f.write(fdata) else: self.log_update.emit( ' ERROR: Download failed (%d).' % dl_resp.status_code) # Install conjugation data if self.install_conj: curl = l.get('conjugation_url') if curl: self.log_update.emit( 'Installing %s conjugation data...' % lname) curl = self.construct_url(curl) dl_resp = client.get(curl) if dl_resp.status_code == 200: cdata = client.streamContent(dl_resp) dst_path = os.path.join(conj_path, '%s.json' % lname) with open(dst_path, 'wb') as f: f.write(cdata) else: self.log_update.emit( ' ERROR: Download failed (%d).' % dl_resp.status_code) # Install dictionaries for d in l.get('dictionaries', []): if self.cancel_requested: return dname = d.get('name') durl = self.construct_url(d.get('url')) self.log_update.emit('Installing %s...' % dname) self.log_update.emit(' Downloading %s...' % durl) dl_resp = client.get(durl) if dl_resp.status_code == 200: update_dict_progress(0.5) self.log_update.emit(' Importing...') ddata = client.streamContent(dl_resp) try: importDict(lname, io.BytesIO(ddata), dname) except ValueError as e: self.log_update.emit(' ERROR: %s' % str(e)) else: self.log_update.emit(' ERROR: Download failed (%d).' % dl_resp.status_code) update_dict_progress(1.0) num_installed += 1 # Only once language can be installed when language is forced if self.force_lang: # Should never happen break self.progress_update.emit(100) self.log_update.emit('All done.')
return path # not a valid local file or directory, so assume that it points to a URL return name def importAnkiScript(download=False): """private function to get a reference to the Anki Script addon, installing it if required""" if ankiscript := find_addon_by_name('Anki Script'): return ankiscript if download: id = '828860704' (id, result) = addons.download_and_install_addon(mw.addonManager, HttpClient(), id) if isinstance(result, addons.DownloadError): raise Exception('Anki Script download failed: {0}'.format(result)) # now can try again to import return importAnkiScript(download=False) raise Exception('Cannot find Anki Script addin') # https://forums.ankiweb.net/t/accessing-the-module-of-another-add-on/5936/2 def find_addon_by_name(addon_name: str) -> Optional[ModuleType]: for name in mw.addonManager.allAddons(): if mw.addonManager.addonName(name) == addon_name: try: return import_module(name)