diff options
16 files changed, 675 insertions, 81 deletions
diff --git a/src/hydrilla/item_infos.py b/src/hydrilla/item_infos.py index 9dfad14..c8c5deb 100644 --- a/src/hydrilla/item_infos.py +++ b/src/hydrilla/item_infos.py @@ -156,6 +156,7 @@ class ItemInfoBase(ABC, ItemIdentity, Categorizable): source_copyright: tuple[FileSpecifier, ...] = dc.field(hash=False, compare=False) uuid: t.Optional[str] = dc.field(hash=False, compare=False) long_name: str = dc.field(hash=False, compare=False) + description: str = dc.field(hash=False, compare=False) allows_eval: bool = dc.field(hash=False, compare=False) allows_cors_bypass: bool = dc.field(hash=False, compare=False) required_mappings: tuple[ItemSpecifier, ...] = dc.field(hash=False, compare=False) @@ -209,6 +210,7 @@ class ItemInfoBase(ABC, ItemIdentity, Categorizable): identifier = item_obj['identifier'], uuid = item_obj.get('uuid'), long_name = item_obj['long_name'], + description = item_obj['description'], allows_eval = eval_perm, allows_cors_bypass = cors_bypass_perm, required_mappings = required_mappings, diff --git a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po index bb93bdf..c479412 100644 --- a/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po +++ b/src/hydrilla/locales/en_US/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: hydrilla 2.0\n" "Report-Msgid-Bugs-To: koszko@koszko.org\n" -"POT-Creation-Date: 2022-09-29 12:29+0200\n" +"POT-Creation-Date: 2022-10-04 21:19+0200\n" "PO-Revision-Date: 2022-02-12 00:00+0000\n" "Last-Translator: Wojtek Kosior <koszko@koszko.org>\n" "Language: en_US\n" @@ -137,7 +137,7 @@ msgstr "" msgid "err.item_info.filename_invalid_{}" msgstr "Item definition conatains an illegal path: {}" -#: src/hydrilla/item_infos.py:482 +#: src/hydrilla/item_infos.py:484 #, python-brace-format msgid "uuid_mismatch_{identifier}" msgstr "Two different uuids were specified for item '{identifier}'." @@ -455,43 +455,67 @@ msgstr "" msgid "web_ui.home.prune_orphans_button" msgstr "Prune orphans" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:25 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:69 #: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:30 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:35 msgid "web_ui.err.file_installation_error" msgstr "Failed to install needed items from repository." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:29 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:73 msgid "web_ui.err.uninstall_disallowed" msgstr "This item is required and cannot be uninstalled." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:33 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:77 #: src/hydrilla/proxy/web_ui/templates/prompts/package_suggestion.html.jinja:34 #: src/hydrilla/proxy/web_ui/templates/repos/show_single.html.jinja:39 msgid "web_ui.err.repo_communication_error" msgstr "Couldn't communicate with repository." -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:38 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:82 +msgid "web_ui.items.single_version.identifier_label" +msgstr "Identifier" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:90 msgid "web_ui.items.single_version.version_label" msgstr "Version" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:58 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:99 +msgid "web_ui.items.single_version.uuid_label" +msgstr "UUID" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:109 +msgid "web_ui.items.single_version.description_label" +msgstr "Description" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:118 +msgid "web_ui.items.single_version.licenses_label" +msgstr "License and copyright files" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:124 +msgid "web_ui.items.single_version.no_license_files" +msgstr "There are no designated files with legal information." + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:131 +msgid "web_ui.items.single_version.reqired_mappings_label" +msgstr "Required packages" + +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:158 msgid "web_ui.items.single_version.install_uninstall_label" msgstr "Installation status" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:65 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:165 msgid "web_ui.items.single_version.retry_install_button" msgstr "Retry installation" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:69 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:169 msgid "web_ui.items.single_version.leave_uninstalled_button" msgstr "Leave uninstalled" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:73 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:173 msgid "web_ui.items.single_version.install_button" msgstr "Install" -#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:75 +#: src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja:175 msgid "web_ui.items.single_version.uninstall_button" msgstr "Uninstall" @@ -543,23 +567,35 @@ msgstr "Library is not currently installed." msgid "web_ui.items.single_version.library.version_list_heading" msgstr "Other available versions of the library" +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:58 +msgid "web_ui.items.single_version.library.scripts_label" +msgstr "Scripts" + #: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:64 +msgid "web_ui.items.single_version.library.no_script_files" +msgstr "There are no JavaScript files in this library." + +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:71 +msgid "web_ui.items.single_version.library.deps_label" +msgstr "Dependencies" + +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:86 msgid "web_ui.items.single_version.library.enabled_label" msgstr "Usage status" -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:68 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:90 msgid "web_ui.items.single_version.library.item_required" msgstr "This library version is required by an enabled package." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:73 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:95 msgid "web_ui.items.single_version.library.item_not_activated" msgstr "This library version is not used by any package enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:75 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:97 msgid "web_ui.items.single_version.library.item_will_be_asked_about" msgstr "This library version is not used by any package enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:78 +#: src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja:100 msgid "web_ui.items.single_version.library.item_auto_activated" msgstr "" "This library version is used by some package. The package has not been " @@ -606,101 +642,109 @@ msgstr "Package is not currently installed." msgid "web_ui.items.single_version.package.version_list_heading" msgstr "Other available versions of the package" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:64 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:58 +msgid "web_ui.items.single_version.package.payloads_label" +msgstr "Payloads" + +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:91 +msgid "web_ui.items.single_version.package.no_payloads" +msgstr "This package has no payloads." + +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:97 msgid "web_ui.items.single_version.package.enabled_label" msgstr "Usage status" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:70 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:103 msgid "web_ui.items.single_version.unenable_button" msgstr "Forget" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:71 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:104 msgid "web_ui.items.single_version.disable_button" msgstr "Disable" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:72 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:105 msgid "web_ui.items.single_version.enable_button" msgstr "Enable" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:81 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:114 msgid "web_ui.items.single_version.package.item_not_activated" msgstr "This package is not enabled. This version won't be used." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:83 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:116 msgid "web_ui.items.single_version.package.item_will_be_asked_about" msgstr "" "This package is not currently enabled. You will be asked whether to " "enable this version of it when you visit a website where it can be used." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:86 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:119 msgid "web_ui.items.single_version.package.item_auto_activated" msgstr "" "This package version has not been explicitly enabled but it is going to " "be used automatically." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:90 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:123 msgid "web_ui.items.single_version.package.item_disabled" msgstr "All versions of the package have been explicitly disabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:94 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:127 msgid "web_ui.items.single_version.package.item_enabled" msgstr "The package has been enabled by the user." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:109 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:142 msgid "web_ui.items.single_version.package.pinning_label" msgstr "Enabled package pinning" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:115 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:148 msgid "web_ui.items.single_version.unpin_button" msgstr "Unpin" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:120 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:153 msgid "web_ui.items.single_version.not_pinned" msgstr "The package is not pinned to any version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:125 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:158 msgid "web_ui.items.single_version.pinned_repo_local" msgstr "The package is pinned to only use locally installed versions." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:128 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:161 msgid "web_ui.items.single_version.pinned_repo_{}" msgstr "The package is pinned to only use versions from repository '{}'." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:139 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:172 msgid "web_ui.items.single_version.pin_local_repo_button" msgstr "Pin to local packages" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:144 -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:157 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:177 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:190 msgid "web_ui.items.single_version.pin_repo_button" msgstr "Pin to repository" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:151 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:184 msgid "web_ui.items.single_version.repin_repo_button" msgstr "Pin to this repository" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:165 -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:176 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:198 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:209 msgid "web_ui.items.single_version.pin_ver_button" msgstr "Pin to this version" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:168 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:201 msgid "web_ui.items.single_version.pinned_ver" msgstr "The package is pinned to this version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:171 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:204 msgid "web_ui.items.single_version.repin_ver_button" msgstr "Pin to this version" -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:173 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:206 msgid "web_ui.items.single_version.pinned_other_ver" msgstr "The package is pinned to a different version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:181 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:214 msgid "web_ui.items.single_version.active_ver_is_this_one" msgstr "This is the currently active version." -#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:185 +#: src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja:218 msgid "web_ui.items.single_version.active_ver_is_{}" msgstr "Currently active version is '{}'." diff --git a/src/hydrilla/proxy/policies/payload_resource.py b/src/hydrilla/proxy/policies/payload_resource.py index 3872037..30b28f2 100644 --- a/src/hydrilla/proxy/policies/payload_resource.py +++ b/src/hydrilla/proxy/policies/payload_resource.py @@ -91,7 +91,7 @@ class PayloadResourcePolicy(PayloadAwarePolicy): return http_messages.ProducedResponse( 200, - ((b'Content-Type', file_data.type.encode()),), + ((b'Content-Type', file_data.mime_type.encode()),), file_data.contents ) diff --git a/src/hydrilla/proxy/state.py b/src/hydrilla/proxy/state.py index 559a546..4142f7f 100644 --- a/src/hydrilla/proxy/state.py +++ b/src/hydrilla/proxy/state.py @@ -259,6 +259,13 @@ class RepoIterationRef(Ref): @dc.dataclass(frozen=True) +class FileData: + mime_type: str + name: str + contents: bytes + + +@dc.dataclass(frozen=True) class MappingDisplayInfo(item_infos.CorrespondsToMappingDCMixin): ref: 'MappingRef' identifier: str @@ -300,6 +307,10 @@ class MappingStore(Store[MappingRef]): def get_display_infos(self) -> t.Sequence[MappingDisplayInfo]: ... + @abstractmethod + def get_by_identifier(self, identifier: str) -> MappingRef: + ... + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class MappingVersionRef(Ref, item_infos.CorrespondsToMappingDCMixin): @abstractmethod @@ -323,6 +334,23 @@ class MappingVersionRef(Ref, item_infos.CorrespondsToMappingDCMixin): ... @abstractmethod + def get_license_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_upstream_license_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_required_mapping(self, identifier: str) -> 'MappingVersionRef': + ... + + @abstractmethod + def get_payload_resource(self, pattern: str, identifier: str) \ + -> 'ResourceVersionRef': + ... + + @abstractmethod def get_item_display_info(self) -> RichMappingDisplayInfo: ... @@ -359,6 +387,10 @@ class ResourceStore(Store[ResourceRef]): def get_display_infos(self) -> t.Sequence[ResourceDisplayInfo]: ... + @abstractmethod + def get_by_identifier(self, identifier: str) -> ResourceRef: + ... + @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class ResourceVersionRef(Ref, item_infos.CorrespondsToResourceDCMixin): @@ -371,6 +403,26 @@ class ResourceVersionRef(Ref, item_infos.CorrespondsToResourceDCMixin): ... @abstractmethod + def get_license_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_resource_file(self, name: str) -> FileData: + ... + + @abstractmethod + def get_upstream_license_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_upstream_resource_file_url(self, name: str) -> str: + ... + + @abstractmethod + def get_dependency(self, identifier: str) -> ResourceVersionRef: + ... + + @abstractmethod def get_item_display_info(self) -> RichResourceDisplayInfo: ... @@ -409,12 +461,6 @@ class PayloadDisplayInfo: pattern: str has_problems: bool -@dc.dataclass(frozen=True) -class FileData: - type: str - name: str - contents: bytes - @dc.dataclass(frozen=True, unsafe_hash=True) # type: ignore[misc] class PayloadRef(Ref): """....""" diff --git a/src/hydrilla/proxy/state_impl/items.py b/src/hydrilla/proxy/state_impl/items.py index d312db9..010b8ca 100644 --- a/src/hydrilla/proxy/state_impl/items.py +++ b/src/hydrilla/proxy/state_impl/items.py @@ -39,12 +39,30 @@ import typing as t import dataclasses as dc from contextlib import contextmanager +from urllib.parse import urljoin from ... import item_infos from .. import state as st from . import base +def _get_item_id(cursor: sqlite3.Cursor, item_type: str, identifier: str) \ + -> str: + cursor.execute( + 'SELECT item_id FROM items WHERE identifier = ? AND type = ?;', + (identifier, item_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (item_id,), = rows + + return str(item_id) + + def _get_parent_item_id(cursor: sqlite3.Cursor, version_id: str) -> str: cursor.execute( ''' @@ -66,6 +84,7 @@ def _get_parent_item_id(cursor: sqlite3.Cursor, version_id: str) -> str: return str(item_id) + def _set_installed_status(cursor: sqlite3.Cursor, id: str, new_status: str) \ -> None: cursor.execute( @@ -138,6 +157,71 @@ def _uninstall_version(ref: VersionRefVar) -> t.Optional[VersionRefVar]: return ref if version_still_present else None +def _get_file(ref: VersionRefVar, name: str, file_type: str = 'L') \ + -> st.FileData: + with ref.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + f.data, fu.mime_type + FROM + item_versions AS iv + JOIN items AS i USING (item_id) + JOIN file_uses AS fu USING (item_version_id) + JOIN files AS f USING (file_id) + WHERE + (iv.item_version_id = ? AND iv.installed = 'I') AND + i.type = ? AND + (fu.name = ? AND fu.type = ?) AND + f.data IS NOT NULL; + ''', + (ref.id, ref.type.value[0].upper(), name, file_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (data, mime_type), = rows + + return st.FileData(mime_type, name, data) + + +def _get_upstream_file_url( + ref: VersionRefVar, + name: str, + file_type: str = 'L' +) -> str: + with ref.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + f.sha256, r.url + FROM + item_versions AS iv + JOIN repo_iterations AS ri USING(repo_iteration_id) + JOIN repos AS r USING(repo_id) + JOIN file_uses AS fu USING(item_version_id) + JOIN files AS f USING(file_id) + WHERE + iv.item_version_id = ? AND + (fu.name = ? AND fu.type = ?) AND + r.url IS NOT NULL; + ''', + (ref.id, name, file_type) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (sha256, repo_url), = rows + + return urljoin(repo_url, f'file/sha256/{sha256}') + + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteMappingRef(st.MappingRef): state: base.HaketiloStateWithFields = dc.field(hash=False, compare=False) @@ -405,6 +489,12 @@ class ConcreteMappingStore(st.MappingStore): return sorted(result, key=(lambda di: di.identifier)) + def get_by_identifier(self, identifier: str) -> st.MappingRef: + with self.state.cursor() as cursor: + item_id = _get_item_id(cursor, 'M', identifier) + + return ConcreteMappingRef(item_id, self.state) + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteMappingVersionRef(st.MappingVersionRef): @@ -472,6 +562,76 @@ class ConcreteMappingVersionRef(st.MappingVersionRef): mapping_ref.update_status(enabled, frozen, id_to_pass) + def get_license_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'L') + + def get_upstream_license_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'L') + + def get_required_mapping(self, identifier: str) \ + -> 'ConcreteMappingVersionRef': + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv2.item_version_id + FROM + item_versions AS iv1 + JOIN resolved_required_mappings AS rrm + ON iv1.item_version_id = + rrm.requiring_mapping_id + JOIN item_versions AS iv2 + ON rrm.required_mapping_id = + iv2.item_version_id + JOIN items AS i + ON iv2.item_id = i.item_id + WHERE + iv1.item_version_id = ? AND + i.identifier = ?; + ''', + (self.id, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (required_id,), = rows + + return ConcreteMappingVersionRef(str(required_id), self.state) + + def get_payload_resource(self, pattern: str, identifier: str) \ + -> 'ConcreteResourceVersionRef': + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv.item_version_id + FROM + payloads AS p + JOIN resolved_depended_resources AS rdd + USING(payload_id) + JOIN item_versions AS iv + ON rdd.resource_item_id = iv.item_version_id + JOIN items AS i + USING (item_id) + WHERE + (p.mapping_item_id = ? AND p.pattern = ?) AND + i.identifier = ?; + ''', + (self.id, pattern, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (resource_ver_id,), = rows + + return ConcreteResourceVersionRef(str(resource_ver_id), self.state) + def get_item_display_info(self) -> st.RichMappingDisplayInfo: with self._mapping_ref() as mapping_ref: return mapping_ref.get_display_info() @@ -578,6 +738,12 @@ class ConcreteResourceStore(st.ResourceStore): return sorted(result, key=(lambda di: di.identifier)) + def get_by_identifier(self, identifier: str) -> st.ResourceRef: + with self.state.cursor() as cursor: + item_id = _get_item_id(cursor, 'R', identifier) + + return ConcreteResourceRef(item_id, self.state) + @dc.dataclass(frozen=True, unsafe_hash=True) class ConcreteResourceVersionRef(st.ResourceVersionRef): @@ -589,6 +755,49 @@ class ConcreteResourceVersionRef(st.ResourceVersionRef): def uninstall(self) -> t.Optional['ConcreteResourceVersionRef']: return _uninstall_version(self) + def get_license_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'L') + + def get_resource_file(self, name: str) -> st.FileData: + return _get_file(self, name, 'W') + + def get_upstream_license_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'L') + + def get_upstream_resource_file_url(self, name: str) -> str: + return _get_upstream_file_url(self, name, 'W') + + def get_dependency(self, identifier: str) -> st.ResourceVersionRef: + with self.state.cursor() as cursor: + cursor.execute( + ''' + SELECT + iv.item_version_id + FROM + resolved_depended_resources AS rdd1 + JOIN payloads AS p + ON rdd1.payload_id = p.payload_id + JOIN resolved_depended_resources AS rdd2 + ON p.payload_id = rdd2.payload_id + JOIN item_versions AS iv + ON rdd2.resource_item_id = iv.item_version_id + JOIN items AS i + USING (item_id) + WHERE + rdd1.resource_item_id = ? AND i.identifier = ?; + ''', + (self.id, identifier) + ) + + rows = cursor.fetchall() + + if rows == []: + raise st.MissingItemError() + + (dep_id,), = rows + + return ConcreteResourceVersionRef(str(dep_id), self.state) + def get_item_display_info(self) -> st.RichResourceDisplayInfo: with self.state.cursor() as cursor: resource_id = _get_parent_item_id(cursor, self.id) diff --git a/src/hydrilla/proxy/state_impl/payloads.py b/src/hydrilla/proxy/state_impl/payloads.py index 74b8121..ebc7152 100644 --- a/src/hydrilla/proxy/state_impl/payloads.py +++ b/src/hydrilla/proxy/state_impl/payloads.py @@ -264,7 +264,7 @@ class ConcretePayloadRef(st.PayloadRef): (data, mime_type), = result - return st.FileData(type=mime_type, name=file_name, contents=data) + return st.FileData(mime_type=mime_type, name=file_name, contents=data) @dc.dataclass(frozen=True) diff --git a/src/hydrilla/proxy/web_ui/items.py b/src/hydrilla/proxy/web_ui/items.py index f34b89b..467be2f 100644 --- a/src/hydrilla/proxy/web_ui/items.py +++ b/src/hydrilla/proxy/web_ui/items.py @@ -34,6 +34,8 @@ from __future__ import annotations import typing as t +from urllib.parse import unquote + import flask import werkzeug @@ -231,3 +233,130 @@ def alter_library_version(item_version_id: str) -> werkzeug.Response: @bp.route('/packages/viewversion/<string:item_version_id>', methods=['POST']) def alter_package_version(item_version_id: str) -> werkzeug.Response: return alter_item_version(item_version_id, item_infos.ItemType.MAPPING) + +def show_file( + item_version_id: str, + item_type: item_infos.ItemType, + file_type: str, + name: str, +) -> werkzeug.Response: + if file_type not in ('license', 'web_resource'): + flask.abort(404) + + try: + store = item_version_store(_app.get_haketilo_state(), item_type) + item_version_ref = store.get(item_version_id) + + try: + if file_type == 'license': + file_data = item_version_ref.get_license_file(name) + else: + assert isinstance(item_version_ref, st.ResourceVersionRef) + file_data = item_version_ref.get_resource_file(name) + + return werkzeug.Response( + file_data.contents, + mimetype = file_data.mime_type + ) + except st.MissingItemError: + if file_type == 'license': + url = item_version_ref.get_upstream_license_file_url(name) + else: + assert isinstance(item_version_ref, st.ResourceVersionRef) + url = item_version_ref.get_upstream_resource_file_url(name) + + return flask.redirect(url) + + except st.MissingItemError: + flask.abort(404) + +@bp.route('/packages/viewversion/<string:item_version_id>/<string:file_type>/<path:name>') +def show_mapping_file(item_version_id: str, file_type: str, name: str) \ + -> werkzeug.Response: + item_type = item_infos.ItemType.MAPPING + return show_file(item_version_id, item_type, file_type, name) + +@bp.route('/libraries/viewversion/<string:item_version_id>/<string:file_type>/<path:name>') +def show_resource_file(item_version_id: str, file_type: str, name: str) \ + -> werkzeug.Response: + item_type = item_infos.ItemType.RESOURCE + return show_file(item_version_id, item_type, file_type, name) + +@bp.route('/libraries/viewdep/<string:item_version_id>/<string:dep_identifier>') +def show_library_dep(item_version_id: str, dep_identifier: str) \ + -> werkzeug.Response: + state = _app.get_haketilo_state() + + try: + store = state.resource_version_store() + dep_id = store.get(item_version_id).get_dependency(dep_identifier).id + url = flask.url_for('.show_library_version', item_version_id=dep_id) + except st.MissingItemError: + try: + versionless_store = state.resource_store() + item_ref = versionless_store.get_by_identifier(dep_identifier) + url = flask.url_for('.show_library', item_id=item_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) + +@bp.route('/<string:item_type>/viewrequired/<string:item_version_id>/<string:required_identifier>') +def show_required_mapping( + item_type: str, + item_version_id: str, + required_identifier: str +) -> werkzeug.Response: + state = _app.get_haketilo_state() + + if item_type not in ('package', 'library'): + flask.abort(404) + + found = False + + if item_type == 'package': + try: + ref = state.mapping_version_store().get(item_version_id) + mapping_ver_id = ref.get_required_mapping(required_identifier).id + url = flask.url_for( + '.show_package_version', + item_version_id = mapping_ver_id + ) + found = True + except st.MissingItemError: + pass + + if not found: + try: + versionless_store = state.mapping_store() + mapping_ref = versionless_store\ + .get_by_identifier(required_identifier) + url = flask.url_for('.show_package', item_id=mapping_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) + +@bp.route('/package/viewpayload/<string:item_version_id>/<string:pattern>/<string:lib_identifier>') +def show_payload(item_version_id: str, pattern: str, lib_identifier: str) \ + -> werkzeug.Response: + state = _app.get_haketilo_state() + + try: + ref = state.mapping_version_store().get(item_version_id) + + try: + resource_ver_ref = \ + ref.get_payload_resource(unquote(pattern), lib_identifier) + url = flask.url_for( + '.show_library_version', + item_version_id = resource_ver_ref.id + ) + except st.MissingItemError: + resource_ref = \ + state.resource_store().get_by_identifier(lib_identifier) + url = flask.url_for('.show_library', item_id=resource_ref.id) + except st.MissingItemError: + flask.abort(404) + + return flask.redirect(url) diff --git a/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja b/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja index cbdf225..063cd01 100644 --- a/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja +++ b/src/hydrilla/proxy/web_ui/templates/include/item_list_style.css.jinja @@ -18,36 +18,40 @@ I, Wojtek Kosior, thereby promise not to sue for violation of this file's licenses. Although I request that you do not make use of this code in a proprietary work, I am not going to enforce this in court. #} -ul#item_list { +ul.item-list { padding: 0; } -ul#item_list > li { +ul.item-list > li { list-style-type: none; max-width: 100%; white-space: nowrap; margin: 0; } -ul#item_list > li > :only-child { +ul.item-list > li > :only-child { display: block; padding: 5px; overflow-x: scroll; border-bottom: 2px solid #999; } -ul#item_list > li.entry-line-dashed > :only-child { +ul.item-list > li.entry-line-dashed > :only-child { border-bottom-style: dashed } -ul#item_list > li.entry-line-green > :only-child { +ul.item-list > li.entry-line-green > :only-child { border-color: #4caf50; } -ul#item_list > li.entry-line-blue > :only-child { +ul.item-list > li.entry-line-blue > :only-child { border-color: #504caf; } -ul#item_list > li.entry-line-red > :only-child { +ul.item-list > li.entry-line-red > :only-child { border-color: #af504c; } + +ul.item-list > li.invisible-entry-line > :only-child { + border-color: #fff; +} diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja index f048f14..5f8b102 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_view.html.jinja @@ -20,7 +20,7 @@ code in a proprietary work, I am not going to enforce this in court. #} {% extends "base.html.jinja" %} -{% macro versioned_identifier_with_repo(info) -%} +{% macro version_with_repo(info) -%} {{ info.info.version_string }} {%- if not info.is_local %} @ @@ -56,7 +56,7 @@ code in a proprietary work, I am not going to enforce this in court. {% endblock %} </h4> - <ul id="item_list"> + <ul class="item-list"> {% for info in display_info.all_versions %} {% if version_display_info is not defined or @@ -88,7 +88,7 @@ code in a proprietary work, I am not going to enforce this in court. ) %} <a href="{{ href }}"> - <div> {{ versioned_identifier_with_repo(info) }} </div> + <div> {{ version_with_repo(info) }} </div> </a> </li> {% endif %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja index c7574f1..bb32be4 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/item_viewversion.html.jinja @@ -20,6 +20,50 @@ code in a proprietary work, I am not going to enforce this in court. #} {% extends "items/item_view.html.jinja" %} +{% macro item_file_list(file_specs, file_type) %} + <ul class="item-list has-colored-links"> + {% for spec in file_specs %} + <li class="invisible-entry-line"> + {% + set url = url_for( + '.show_{}_file'.format(version_display_info.type.value), + item_version_id = version_display_info.ref.id, + file_type = file_type, + name = spec.name + ) + %} + <div> + <a href="{{ url }}"> + {{ spec.name }} + </a> + </div> + </li> + {% endfor %} + </ul> +{% endmacro %} + +{% macro item_link_list(item_specs, make_url) %} + <ul class="item-list has-colored-links"> + {% for spec in item_specs %} + <li class="invisible-entry-line"> + <div> + <a href="{{ make_url(spec) }}"> + {{ spec.identifier }} + </a> + </div> + </li> + {% endfor %} + </ul> +{% endmacro %} + +{% block style %} + {{ super() }} + + .has-colored-links a { + color: #557b8e; + } +{% endblock %} + {% block main_info %} {% if file_installation_error is defined %} {{ error_note(_('web_ui.err.file_installation_error')) }} @@ -35,19 +79,75 @@ code in a proprietary work, I am not going to enforce this in court. {{ super() }} + {{ label(_('web_ui.items.single_version.identifier_label')) }} + + <p> + {{ version_display_info.info.identifier }} + </p> + + <div class="horizontal-separator"></div> + {{ label(_('web_ui.items.single_version.version_label')) }} <p> - {{ versioned_identifier_with_repo(version_display_info) }} + {{ version_with_repo(version_display_info) }} </p> <div class="horizontal-separator"></div> - {% block main_info_bulk %} + {% if version_display_info.info.uuid is not none %} + {{ label(_('web_ui.items.single_version.uuid_label')) }} + + <p> + {{ version_display_info.info.uuid }} + </p> + + <div class="horizontal-separator"></div> + {% endif %} + + {% if version_display_info.info.description %} + {{ label(_('web_ui.items.single_version.description_label')) }} + <p> - TODO: add more info... + {{ version_display_info.info.description }} </p> - {% endblock %} + + <div class="horizontal-separator"></div> + {% endif %} + + {{ label(_('web_ui.items.single_version.licenses_label')) }} + + {% if version_display_info.info.source_copyright %} + {{ item_file_list(version_display_info.info.source_copyright, 'license') }} + {% else %} + <p> + {{ _('web_ui.items.single_version.no_license_files') }} + </p> + {% endif %} + + <div class="horizontal-separator"></div> + + {% if version_display_info.info.required_mappings %} + {{ label(_('web_ui.items.single_version.reqired_mappings_label')) }} + + {% macro make_mapping_url(spec) -%} + {{ + url_for( + '.show_required_mapping', + item_type = version_display_info.type.alt_name, + item_version_id = version_display_info.ref.id, + required_identifier = spec.identifier + ) + }} + {%- endmacro %} + + {% set required_specs = version_display_info.info.required_mappings %} + {{ item_link_list(required_specs, make_mapping_url) }} + + <div class="horizontal-separator"></div> + {% endif %} + + {% block main_info_rest required %}{% endblock %} {% if settings.advanced_user and diff --git a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja index 2d5ec29..0a72b64 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/libraries.html.jinja @@ -27,7 +27,7 @@ code in a proprietary work, I am not going to enforce this in court. {% include 'include/item_list_style.css.jinja' %} - ul#item_list > li > a { + ul.item-list > li > a { display: flex !important; flex-direction: column; justify-content: center; @@ -38,7 +38,7 @@ code in a proprietary work, I am not going to enforce this in court. {% block main %} <h3>{{ _('web_ui.libraries.heading') }}</h3> - <ul id="item_list"> + <ul class="item-list"> {% for info in display_infos %} <li> <a href="{{ url_for('.show_library', item_id=info.ref.id) }}"> diff --git a/src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja index 0454391..eb77fe6 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/library_viewversion.html.jinja @@ -54,13 +54,35 @@ code in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.items.single_version.library.version_list_heading') }} {% endblock %} -{% block main_info_bulk %} - <p> - TODO: add more info... - </p> +{% block main_info_rest %} + {{ label(_('web_ui.items.single_version.library.scripts_label')) }} + + {% if version_display_info.info.scripts %} + {{ item_file_list(version_display_info.info.scripts, 'web_resource') }} + {% else %} + <p> + {{ _('web_ui.items.single_version.library.no_script_files') }} + </p> + {% endif %} <div class="horizontal-separator"></div> + {% if version_display_info.info.dependencies %} + {{ label(_('web_ui.items.single_version.library.deps_label')) }} + + {% macro make_dep_url(spec) -%} + {{ + url_for( + '.show_library_dep', + item_version_id = version_display_info.ref.id, + dep_identifier = spec.identifier + ) + }} + {%- endmacro %} + + {{ item_link_list(version_display_info.info.dependencies, make_dep_url) }} + {% endif %} + {{ label(_('web_ui.items.single_version.library.enabled_label')) }} <p> @@ -78,4 +100,4 @@ code in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.items.single_version.library.item_auto_activated') }} {% endif %} </p> -{% endblock %} +{% endblock main_info_rest %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja index 699469e..bc33c42 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/package_viewversion.html.jinja @@ -54,10 +54,50 @@ code in a proprietary work, I am not going to enforce this in court. {{ _('web_ui.items.single_version.package.version_list_heading') }} {% endblock %} -{% block main_info_bulk %} - <p> - TODO: add more info... - </p> +{% block main_info_rest %} + {{ label(_('web_ui.items.single_version.package.payloads_label')) }} + + {% if version_display_info.info.payloads|length > 0 %} + <ul class="item-list has-colored-links"> + {% set by_lib = {} %} + {% + for pattern_struct, spec in version_display_info.info.payloads.items() + if pattern_struct.orig_url not in processed_patterns + %} + {% set pattern = pattern_struct.orig_url %} + {% do by_lib.setdefault(spec.identifier, []).append(pattern) %} + {% endfor %} + {% for lib_identifier, patterns in by_lib|dictsort %} + <li class="invisible-entry-line"> + <div> + {% set encoded = patterns[0]|urlencode|replace('/', '%2F') %} + {% + set url = url_for( + '.show_payload', + item_version_id = version_display_info.ref.id, + pattern = encoded, + lib_identifier = lib_identifier + ) + %} + <div> + <a href="{{ url }}"> + {{ lib_identifier }} + </a> + </div> + {% for pattern in patterns|unique|sort(attribute='identifier') %} + <div class="small-print"> + {{ pattern }} + </div> + {% endfor %} + </div> + </li> + {% endfor %} + </ul> + {% else %} + <p> + {{ _('web_ui.items.single_version.package.no_payloads') }} + </p> + {% endif %} <div class="horizontal-separator"></div> @@ -183,9 +223,7 @@ code in a proprietary work, I am not going to enforce this in court. {% else %} {{ _('web_ui.items.single_version.active_ver_is_{}') - .format( - versioned_identifier_with_repo(display_info.active_version) - ) + .format(version_with_repo(display_info.active_version)) }} {% endif %} </p> @@ -198,4 +236,4 @@ code in a proprietary work, I am not going to enforce this in court. ]) }} {% endif %}{# display_info.enabled == EnabledStatus.ENABLED #} -{% endblock main_info_bulk %} +{% endblock main_info_rest %} diff --git a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja index 29228a2..bc6b5bb 100644 --- a/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/items/packages.html.jinja @@ -27,7 +27,7 @@ code in a proprietary work, I am not going to enforce this in court. {% include 'include/item_list_style.css.jinja' %} - ul#item_list > li > a { + ul.item-list > li > a { display: flex !important; flex-direction: column; justify-content: center; @@ -38,7 +38,7 @@ code in a proprietary work, I am not going to enforce this in court. {% block main %} <h3>{{ _('web_ui.packages.heading') }}</h3> - <ul id="item_list"> + <ul class="item-list"> {% for info in display_infos %} {% set entry_classes = [] %} diff --git a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja index 15f821b..e670b59 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos/index.html.jinja @@ -40,7 +40,7 @@ code in a proprietary work, I am not going to enforce this in court. <h4>{{ _('web_ui.repos.repo_list_heading') }}</h4> - <ul id="item_list"> + <ul class="item-list"> {% for info in display_infos %} {% set entry_classes = [] %} diff --git a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja index 71712fe..799eaba 100644 --- a/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/rules/index.html.jinja @@ -40,7 +40,7 @@ code in a proprietary work, I am not going to enforce this in court. <h4>{{ _('web_ui.rules.rule_list_heading') }}</h4> - <ul id="item_list"> + <ul class="item-list"> {% for info in display_infos %} {% if info.allow_scripts %} |