diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /share/extensions/other/clipart/import_sources.py | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | share/extensions/other/clipart/import_sources.py | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/share/extensions/other/clipart/import_sources.py b/share/extensions/other/clipart/import_sources.py new file mode 100644 index 0000000..d98aab3 --- /dev/null +++ b/share/extensions/other/clipart/import_sources.py @@ -0,0 +1,272 @@ +# +# Copyright 2021 Martin Owens <doctormo@gmail.com> +# Copyright 2022 Simon Duerr <dev@simonduerr.eu> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +""" +Base module for all import web search source modules. +""" + +import re +import os +import sys +import logging +import requests +import importlib + +from cachecontrol import CacheControl, CacheControlAdapter +from cachecontrol.caches.file_cache import FileCache +from cachecontrol.heuristics import ExpiresAfter + +from inkex.command import CommandNotFound, ProgramRunError, call +from collections import defaultdict + +LICENSE_ICONS = os.path.join(os.path.dirname(__file__), 'licenses') + +LICENSES = { + "cc-0": { + "name": "CC0", + "modules": ["nocopyright"], + "url": "https://creativecommons.org/publicdomain/zero/1.0/", + "overlay": "cc0.svg", + }, + "cc-by-3.0": { + "name": "CC-BY 3.0 Unported", + "modules": ["by"], + "url": "https://creativecommons.org/licenses/by/3.0/", + "overlay": "cc-by.svg", + }, + "cc-by-4.0": { + "name": "CC-BY 4.0 Unported", + "modules": ["by"], + "url": "https://creativecommons.org/licenses/by/4.0/", + "overlay": "cc-by.svg", + }, + "cc-by-sa-4.0": { + "name": "CC-BY SA 4.0", + "modules": ["by", "sa"], + "url": "https://creativecommons.org/licenses/by-sa/4.0/", + "overlay": "cc-by-sa.svg", + }, + "cc-by-sa-3.0": { + "name": "CC-BY SA 3.0", + "modules": ["by", "sa"], + "url": "https://creativecommons.org/licenses/by-sa/3.0/", + "overlay": "cc-by-sa.svg", + }, + "cc-by-nc-sa-4.0": { + "name": "CC-BY NC SA 4.0", + "modules": ["by", "sa", "nc"], + "url": "https://creativecommons.org/licenses/by-nc-sa/4.0/", + "overlay": "cc-by-nc-sa.svg", + }, + "cc-by-nc-sa-3.0": { + "name": "CC-BY NC SA 3.0", + "modules": ["by", "sa", "nc"], + "url": "https://creativecommons.org/licenses/by-nc-sa/3.0/", + "overlay": "cc-by-nc-sa.svg", + }, + "cc-by-nc-3.0": { + "name": "CC-BY NC 3.0", + "modules": ["by", "nc"], + "url": "https://creativecommons.org/licenses/by-nc/3.0/", + "overlay": "cc-by-nc.svg", + }, + "cc-by-nd-3.0": { + "name": "CC-BY ND 3.0", + "modules": ["by", "nd"], + "url": "https://creativecommons.org/licenses/by-nd/3.0/", + "overlay": "cc-by-nd.svg", + }, + "gpl-2": { + "name": "GPLv2", + "modules": ["retaincopyrightnotice", "sa"], + "url": "https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt", + "overlay": "gpl.svg", + }, + "gpl-3": { + "name": "GPLv3", + "modules": ["retaincopyrightnotice", "sa"], + "url": "https://www.gnu.org/licenses/gpl-3.0.txt", + "overlay": "gpl.svg", + }, + "agpl-3": { + "name": "AGPLv3", + "modules": ["retaincopyrightnotice", "sa"], + "url": "https://www.gnu.org/licenses/agpl-3.0.txt", + "overlay": "gpl.svg", + }, + "mit": { + "name": "MIT", + "modules": ["retaincopyrightnotice"], + "url": "https://mit-license.org/", + "overlay": "mit.svg", + }, + "asl": { + "name": "Apache License", + "modules": ["retaincopyrightnotice"], + "url": "https://www.apache.org/licenses/LICENSE-2.0.txt", + "overlay": "asl.svg", + }, + "bsd": { + "name": "BSD", + "modules": ["retaincopyrightnotice", "noendorsement"], + "url": "https://opensource.org/licenses/BSD-3-Clause", + "overlay": "bsd.svg", + }, +} + + +class RemotePage: + """Lazy access to paging systems""" + + icon = "sources/next_page.svg" + string = property(lambda self: "Next Page") + + def __init__(self, remote, func): + self.func = func + self.remote = remote + + def get_next_page(self): + for info in self.func(): + yield self.remote.result_to_cls(info) + + +class RemoteFile: + """Lazy access to remote files""" + + icon = property(lambda self: self.remote.to_local_file(self.info["thumbnail"])) + get_file = lambda self: self.remote.to_local_file(self.info["file"]) + + def __init__(self, remote, info): + for field in ("name", "thumbnail", "license", "file"): + if field not in info: + raise ValueError(f"Field {field} not provided in RemoteFile package") + self.info = info + self.remote = remote + + @property + def string(self): + return self.info["name"] + + @property + def license(self): + return self.info["license"] + + def get_overlay(self): + return self.license_info["overlay"] + + @property + def license_info(self): + return LICENSES.get(self.license, { + "name": "Unknown", + "url": self.info.get("descriptionurl", ""), + "modules": [], + "overlay": "unknown.svg", + }) + + @property + def author(self): + return self.info["author"] + + +class RemoteSource: + """A remote source of svg images which can be searched and downloaded""" + + # These are the properties that should be overridden in your class + name = None + icon = None + file_cls = RemoteFile + page_cls = RemotePage + is_default = False + is_enabled = True + + @classmethod + def load(cls, name): + """Load the file or directory of remote sources""" + if os.path.isfile(name): + sys.path, sys_path = [os.path.dirname(name)] + sys.path, sys.path + try: + importlib.import_module(os.path.basename(name).rsplit(".", 1)[0]) + except ImportError: + logging.error(f"Failed to load module: {name}") + sys.path = sys_path + elif os.path.isdir(name): + for child in os.listdir(name): + if not child.startswith("_") and child.endswith(".py"): + cls.load(os.path.join(name, child)) + + def search(self, query, tags=[]): + """ + Search for the given query and yield basic informational blocks t hand to file_cls. + + Required fields per yielded object are: name, license, thumbnail and file. + Optional fields are: id, summary, author, created, popularity + """ + raise NotImplementedError( + "You must implement a search function for this remote source!" + ) + + sources = {} + + def __init_subclass__(cls): + if cls != RemoteSource: + cls.sources[cls.__name__] = cls + + def __init__(self, cache_dir): + self.session = requests.session() + self.cache_dir = cache_dir + self.session.mount( + "https://", + CacheControlAdapter( + cache=FileCache(cache_dir), + heuristic=ExpiresAfter(days=5), + ), + ) + + def __del__(self): + self.session.close() + + def file_search(self, query): + """Search for extension packages""" + for info in self.search(query): + yield self.result_to_cls(info) + + def result_to_cls(self, info): + if callable(info): + return self.page_cls(self, info) + return self.file_cls(self, info) + + def to_local_file(self, url): + """Get a remote url and turn it into a local file""" + filepath = os.path.join(self.cache_dir, url.split("/")[-1]) + headers = {"User-Agent": "Inkscape"} + try: + remote = self.session.get( + url, headers=headers + ) # needs UserAgent otherwise many 403 or 429 for wiki commons + except requests.exceptions.RequestException as err: + return None + except ConnectionError as err: + return None + except requests.exceptions.RequestsWarning: + pass + + if remote and remote.status_code == 200: + with open(filepath, "wb") as fhl: + # If we don't have data, return None (instead of empty file) + if fhl.write(remote.content): + return filepath + return None |