You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
5.9 KiB
Python

import logging
import requests
from urllib.parse import urlparse
from PyQt5.QtWidgets import QMessageBox
DOMAIN_UNKNOWN = -1
class DomainIdentifier:
"""
Makeshift-automated enum class to allow runtime expansion of an enum.
Create an instance to get a new unique domain identifier that can resolve to an int.
TODO find better way :D
"""
__domain_list = []
def __init__(self, name: str):
self.__name = name
self.__domain_number = len(DomainIdentifier.__domain_list)
if DomainIdentifier.get_identifier(name) is not None:
raise Exception("The domain \"{0}\" is already in the list! Tried to create a duplicate!".format(name))
DomainIdentifier.__domain_list.append(self)
@property
def name(self) -> str:
return self.__name
@property
def identifier(self) -> int:
return self.__domain_number
@staticmethod
def get_identifier(name: str) -> "DomainIdentifier":
for d in DomainIdentifier.__domain_list:
if d.name == name:
return d
return None
class DomainLinkGenerator:
"""
Base class for classes that generate a link to the file on their domain given a sample of a filename pattern and
implement a method to scrape the available metadata.
"""
def __init__(self, domain: DomainIdentifier):
self.__identifier = domain
def match_file_name(self, file_name) -> bool:
"""
Checks if a given file name is plausible to be used by the given domain
:param file_name:
:return:
"""
raise NotImplementedError
def get_domain_name(self) -> str:
return self.__identifier.name
def get_identifier(self) -> int:
"""
Return the Identifier for this Predictors domain.
:return:
"""
return self.__identifier.identifier
def construct_link(self, file_name: str) -> str:
"""
Construct a link by inserting the file_name into the known link pattern
:param file_name:
:return:
"""
raise NotImplementedError
def scrape_tags(self, url: str, headers: dict, file_name: str) -> list:
"""
Scrape the tags from the given url for all tags associated with the work.
The file_name can also be used to check the given url against prediction results.
:param url:
:param headers:
:param file_name:
:return:
"""
raise NotImplementedError
class LinkGenerator:
"""
Predict and generate valid links to the file
by matching the given file names against known patterns of the origin domain.
"""
__instance = None # Singleton holder
def __init__(self):
self.__link_generators = []
import ArtNet.web.domains # implements return_all_domains() which returns instances of all domains
# return_all_domains() is to return a list of all DomainLinkGenerator instances that are to be used
for p in ArtNet.web.domains.return_all_domains():
self.register_domain_predictor(p)
@staticmethod
def get_instance() -> "LinkGenerator":
"""
Gets the current instance
:return:
"""
if LinkGenerator.__instance is None:
LinkGenerator.__instance = LinkGenerator()
return LinkGenerator.__instance
def register_domain_predictor(self, predictor: DomainLinkGenerator):
"""
Register another DomainValidator to be used by this LinkPredictor
:param predictor:
:param domain: int identifier for the domain
:return:
"""
if predictor not in self.__link_generators:
self.__link_generators.append(predictor)
def predict_domain(self, file_name: str) -> int:
"""
Predict the possible domains of the given file by guessing via the filename
:param file_name:
:return:
"""
for g in self.__link_generators:
try:
if g.match_file_name(file_name): # TODO stop accepting first match
return g.get_identifier()
except NotImplementedError:
pass
return DOMAIN_UNKNOWN
def construct_link(self, file_name: str, domain: int) -> str:
"""
Construct a valid link to access the web page where the file name (hopefully) originated from.
:param file_name:
:param domain:
:return:
"""
for g in self.__link_generators:
if g.get_identifier() == domain: # TODO stop accepting first match
try:
return g.construct_link(file_name)
except NotImplementedError:
return None
return None
def scrape_tags(self, url: str, domain: int, file_name: str) -> dict:
"""
Scrapes the tags from the given url
:param url:
:param domain:
:param file_name:
:return:
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:76.0) Gecko/20100101 Firefox/76.0"
}
url_domain = urlparse(url).netloc
for g in self.__link_generators:
if g.get_identifier() == domain or g.get_domain_name() == url_domain:
try:
try:
return g.scrape_tags(url=url, headers=headers, file_name=file_name)
except requests.exceptions.ConnectionError as e:
logging.warning(f"Encountered connection error when trying to scrape tags from \"{url}\".\n"
f"See also:\n{e}")
QMessageBox.warning(None, "Connection Error",
f"The http connection to \"{url}\" ran into an error. Check your network.\n"
f"\n{e}")
break
except NotImplementedError:
pass
return None