CURA-5035 Enjoy Lipu

This commit is contained in:
Ian Paschal 2018-04-12 15:43:25 +02:00
parent e13c45daee
commit f510603cb5
4 changed files with 206 additions and 64 deletions

View file

@ -18,7 +18,7 @@ class AuthorsModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._authors_metadata = None self._metadata = None
self.addRoleName(AuthorsModel.NameRole, "name") self.addRoleName(AuthorsModel.NameRole, "name")
self.addRoleName(AuthorsModel.EmailRole, "email") self.addRoleName(AuthorsModel.EmailRole, "email")
@ -29,13 +29,13 @@ class AuthorsModel(ListModel):
self._filter = {} # type: Dict[str,str] self._filter = {} # type: Dict[str,str]
def setMetadata(self, data): def setMetadata(self, data):
self._authors_metadata = data self._metadata = data
self._update() self._update()
def _update(self): def _update(self):
items = [] items = []
for author in self._authors_metadata: for author in self._metadata:
items.append({ items.append({
"name": author["name"], "name": author["name"],
"email": author["email"], "email": author["email"],

View file

@ -25,7 +25,7 @@ class PackagesModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._packages_metadata = None self._metadata = None
self.addRoleName(PackagesModel.IdRole, "id") self.addRoleName(PackagesModel.IdRole, "id")
self.addRoleName(PackagesModel.TypeRole, "type") self.addRoleName(PackagesModel.TypeRole, "type")
@ -43,13 +43,14 @@ class PackagesModel(ListModel):
self._filter = {} # type: Dict[str,str] self._filter = {} # type: Dict[str,str]
def setMetadata(self, data): def setMetadata(self, data):
self._packages_metadata = data self._metadata = data
self._update() self._update()
def _update(self): def _update(self):
items = [] items = []
for package in self._packages_metadata: for package in self._metadata:
print(package)
items.append({ items.append({
"id": package["package_id"], "id": package["package_id"],
"type": package["package_type"], "type": package["package_type"],

View file

@ -0,0 +1,110 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import re
from typing import Dict
from PyQt5.QtCore import Qt, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
class ShowcaseModel(ListModel):
IdRole = Qt.UserRole + 1
TypeRole = Qt.UserRole + 2
NameRole = Qt.UserRole + 3
VersionRole = Qt.UserRole + 4
AuthorNameRole = Qt.UserRole + 5
AuthorEmailRole = Qt.UserRole + 6
DescriptionRole = Qt.UserRole + 7
IconURLRole = Qt.UserRole + 8
ImageURLsRole = Qt.UserRole + 9
DownloadURLRole = Qt.UserRole + 10
LastUpdatedRole = Qt.UserRole + 11
def __init__(self, parent = None):
super().__init__(parent)
self._metadata = None
self.addRoleName(ShowcaseModel.IdRole, "id")
self.addRoleName(ShowcaseModel.TypeRole, "type")
self.addRoleName(ShowcaseModel.NameRole, "name")
self.addRoleName(ShowcaseModel.VersionRole, "version")
self.addRoleName(ShowcaseModel.AuthorNameRole, "author_name")
self.addRoleName(ShowcaseModel.AuthorEmailRole, "author_email")
self.addRoleName(ShowcaseModel.DescriptionRole, "description")
self.addRoleName(ShowcaseModel.IconURLRole, "icon_url")
self.addRoleName(ShowcaseModel.ImageURLsRole, "image_urls")
self.addRoleName(ShowcaseModel.DownloadURLRole, "download_url")
self.addRoleName(ShowcaseModel.LastUpdatedRole, "last_updated")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str,str]
def setMetadata(self, data):
self._metadata = data
self._update()
def _update(self):
items = []
for package in self._metadata:
print(package)
items.append({
"id": package["id"],
"type": package["type"],
"name": package["name"],
"version": package["package_version"],
"author_name": package["author"]["name"],
"author_email": package["author"]["email"],
"description": package["description"],
"icon_url": package["icon_url"] if "icon_url" in package else None,
"image_urls": package["image_urls"],
"download_url": package["download_url"],
"last_updated": package["last_updated"]
})
# Filter on all the key-word arguments.
for key, value in self._filter.items():
if "*" in value:
key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
else:
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)
items = filter(key_filter, items)
# Execute all filters.
filtered_items = list(items)
filtered_items.sort(key = lambda k: k["name"])
self.setItems(filtered_items)
## Set the filter of this model based on a string.
# \param filter_dict \type{Dict} Dictionary to do the filtering by.
def setFilter(self, filter_dict: Dict[str, str]) -> None:
if filter_dict != self._filter:
self._filter = filter_dict
self._update()
@pyqtProperty("QVariantMap", fset = setFilter, constant = True)
def filter(self) -> Dict[str, str]:
return self._filter
# Check to see if a container matches with a regular expression
def _matchRegExp(self, metadata, property_name, value):
if property_name not in metadata:
return False
value = re.escape(value) #Escape for regex patterns.
value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match.
if self._ignore_case:
value_pattern = re.compile(value, re.IGNORECASE)
else:
value_pattern = re.compile(value)
return value_pattern.match(str(metadata[property_name]))
# Check to see if a container matches with a string
def _matchString(self, metadata, property_name, value):
if property_name not in metadata:
return False
return value.lower() == str(metadata[property_name]).lower()

View file

@ -21,6 +21,7 @@ from cura.Utils.VersionTools import compareSemanticVersions
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from .AuthorsModel import AuthorsModel from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel from .PackagesModel import PackagesModel
from .ShowcaseModel import ShowcaseModel
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
@ -55,13 +56,18 @@ class Toolbox(QObject, Extension):
) )
) )
] ]
self._request_urls = {
"authors": None,
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
"plugins_showcase": QUrl("{base_url}/showcase".format(base_url = self._api_url)),
"materials_showcase": None
}
# Data: # Data:
self._authors_metadata = []
self._packages_metadata = []
self._metadata = { self._metadata = {
"authors": [], "authors": [],
"packages": [], "packages": [],
"plugins_showcase": [],
"materials_showcase": [ "materials_showcase": [
{ {
"name": "DSM", "name": "DSM",
@ -79,19 +85,13 @@ class Toolbox(QObject, Extension):
} }
# Models: # Models:
self._authors_model = None
self._packages_model = None
self._plugins_showcase_model = None
self._plugins_installed_model = None
self._materials_showcase_model = None
self._materials_installed_model = None
self._models = { self._models = {
"authors": None, "authors": AuthorsModel(self),
"packages": None, "packages": PackagesModel(self),
"plugins_showcase": None, "plugins_showcase": PackagesModel(self),
"plugins_installed": None, "plugins_installed": PackagesModel(self),
"materials_showcase": None, "materials_showcase": AuthorsModel(self),
"materials_installed": None "materials_installed": PackagesModel(self)
} }
# These properties are for keeping track of the UI state: # These properties are for keeping track of the UI state:
@ -185,8 +185,9 @@ class Toolbox(QObject, Extension):
self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
self._requestShowcase() self.makeRequestByType("packages")
self._requestPackages() self.makeRequestByType("plugins_showcase")
if not self._dialog: if not self._dialog:
self._dialog = self._createDialog("Toolbox.qml") self._dialog = self._createDialog("Toolbox.qml")
self._dialog.show() self._dialog.show()
@ -239,23 +240,6 @@ class Toolbox(QObject, Extension):
def restart(self): def restart(self):
CuraApplication.getInstance().windowClosed() CuraApplication.getInstance().windowClosed()
# @pyqtProperty(QObject, notify = metadataChanged)
# def pluginsModel(self):
# self._plugins_model = PluginsModel(None, self._view_category)
# # self._plugins_model.update()
#
# # Check each plugin the registry for matching plugin from server
# # metadata, and if found, compare the versions. Higher version sets
# # 'can_upgrade' to 'True':
# for plugin in self._plugins_model.items:
# if self._checkCanUpgrade(plugin["id"], plugin["version"]):
# plugin["can_upgrade"] = True
#
# for item in self._packages_metadata:
# if item["id"] == plugin["id"]:
# plugin["update_url"] = item["file_location"]
# return self._plugins_model
# Checks # Checks
@ -288,17 +272,21 @@ class Toolbox(QObject, Extension):
# Make API Calls # Make API Calls
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
def makeRequestByType(self, type):
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
request = QNetworkRequest(self._request_urls[type])
request.setRawHeader(*self._request_header)
self._network_manager.get(request)
def _requestPackages(self): def _requestPackages(self):
Logger.log("i", "Toolbox: Requesting package list from server.") Logger.log("i", "Toolbox: Requesting package list from server.")
url = QUrl("{base_url}/packages".format(base_url = self._api_url)) self._get_packages_request = QNetworkRequest(self._request_urls["packages"])
self._get_packages_request = QNetworkRequest(url)
self._get_packages_request.setRawHeader(*self._request_header) self._get_packages_request.setRawHeader(*self._request_header)
self._network_manager.get(self._get_packages_request) self._network_manager.get(self._get_packages_request)
def _requestShowcase(self): def _requestShowcase(self):
Logger.log("i", "Toolbox: Requesting showcase list from server.") Logger.log("i", "Toolbox: Requesting showcase list from server.")
url = QUrl("{base_url}/showcase".format(base_url = self._api_url)) self._get_showcase_request = QNetworkRequest(self._request_urls["plugins_showcase"])
self._get_showcase_request = QNetworkRequest(url)
self._get_showcase_request.setRawHeader(*self._request_header) self._get_showcase_request.setRawHeader(*self._request_header)
self._network_manager.get(self._get_showcase_request) self._network_manager.get(self._get_showcase_request)
@ -341,7 +329,7 @@ class Toolbox(QObject, Extension):
# TODO: This function is sooooo ugly. Needs a rework: # TODO: This function is sooooo ugly. Needs a rework:
def _onRequestFinished(self, reply): def _onRequestFinished(self, reply):
reply_url = reply.url().toString()
if reply.error() == QNetworkReply.TimeoutError: if reply.error() == QNetworkReply.TimeoutError:
Logger.log("w", "Got a timeout.") Logger.log("w", "Got a timeout.")
# Reset everything. # Reset everything.
@ -357,7 +345,27 @@ class Toolbox(QObject, Extension):
return return
if reply.operation() == QNetworkAccessManager.GetOperation: if reply.operation() == QNetworkAccessManager.GetOperation:
if reply_url == "{base_url}/packages".format(base_url = self._api_url):
# TODO: In the future use the following to build any model from any
# request. Right now this doesn't work though as the packages
# request is also responsible for populating other models.
# for type, url in self._request_urls.items():
# if reply.url() == url:
# try:
# json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# if not self._models[type]:
# Logger.log("e", "Could not find the %s model.", type)
# break
# self._metadata[type] = json_data["data"]
# self._models[type].setMetadata(self._metadata[type])
# self.metadataChanged.emit()
# self.setViewPage("overview")
# return
# except json.decoder.JSONDecodeError:
# Logger.log("w", "Toolbox: Received invalid JSON for %s.", type)
# break
if reply.url() == self._request_urls["packages"]:
try: try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
print(json_data) print(json_data)
@ -369,14 +377,14 @@ class Toolbox(QObject, Extension):
self.metadataChanged.emit() self.metadataChanged.emit()
# Create authors model with all authors: # Create authors model with all authors:
if not self._authors_model: if not self._models["authors"]:
self._authors_model = AuthorsModel() self._models["authors"] = AuthorsModel()
# TODO: Replace this with a proper API call: # TODO: Replace this with a proper API call:
for package in self._metadata["packages"]: for package in self._metadata["packages"]:
package["author"]["type"] = package["package_type"] package["author"]["type"] = package["package_type"]
if package["author"] not in self._authors_metadata: if package["author"] not in self._metadata["authors"]:
self._metadata["authors"].append(package["author"]) self._metadata["authors"].append(package["author"])
self._models["author"].setMetadata(self._metadata["authors"]) self._models["authors"].setMetadata(self._metadata["authors"])
self.metadataChanged.emit() self.metadataChanged.emit()
if not self._models["materials_showcase"]: if not self._models["materials_showcase"]:
@ -386,24 +394,28 @@ class Toolbox(QObject, Extension):
self.metadataChanged.emit() self.metadataChanged.emit()
self.setViewPage("overview") self.setViewPage("overview")
return
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for package list.") Logger.log("w", "Toolbox: Received invalid JSON for package list.")
return return
elif reply_url == "{base_url}/showcase".format(base_url = self._api_url): if reply.url() == self._request_urls["plugins_showcase"]:
try: try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
# Create packages model with all packages: # Create packages model with all packages:
if not self._plugins_showcase_model: if not self._models["plugins_showcase"]:
self._plugins_showcase_model = PackagesModel() self._models["plugins_showcase"] = PackagesModel()
self._showcase_metadata = json_data["data"] self._metadata["plugins_showcase"] = json_data["data"]
print(self._showcase_metadata) self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
self._plugins_showcase_model.setPackagesMetaData(self._showcase_metadata) for package in self._models["plugins_showcase"].items:
for package in self._plugins_showcase_model.items:
print(package) print(package)
self.metadataChanged.emit() self.metadataChanged.emit()
self.setViewPage("overview")
return
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for showcase.") Logger.log("w", "Toolbox: Received invalid JSON for showcase.")
return return
@ -495,6 +507,8 @@ class Toolbox(QObject, Extension):
# Expose Models: # Expose Models:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# TODO: Maybe replace this with simply exposing self._models to Qt and then
# setting model: toolbox.models.foobar instead of toolbox.foobarModel
@pyqtProperty(QObject, notify = metadataChanged) @pyqtProperty(QObject, notify = metadataChanged)
def authorsModel(self): def authorsModel(self):
return self._models["authors"] return self._models["authors"]
@ -523,30 +537,47 @@ class Toolbox(QObject, Extension):
# Filter Models: # Filter Models:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
def filterModelByProp(self, modelType, filterType, parameter):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", modelType)
return
self._models[modelType].setFilter({ filterType: parameter })
self.filterChanged.emit()
@pyqtSlot()
def removeFilters(self, modelType):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", modelType)
return
self._models[modelType].setFilter({})
self.filterChanged.emit()
# TODO: Eventually dump everything below here:
@pyqtSlot(str, str) @pyqtSlot(str, str)
def filterPackages(self, filterType, parameter): def filterPackages(self, filterType, parameter):
if not self._packages_model: if not self._models["packages"]:
return return
self._packages_model.setFilter({ filterType: parameter }) self._models["packages"].setFilter({ filterType: parameter })
self.filterChanged.emit() self.filterChanged.emit()
@pyqtSlot() @pyqtSlot()
def unfilterPackages(self): def unfilterPackages(self):
if not self._packages_model: if not self._models["packages"]:
return return
self._packages_model.setFilter({}) self._models["packages"].setFilter({})
self.filterChanged.emit() self.filterChanged.emit()
@pyqtSlot(str, str) @pyqtSlot(str, str)
def filterAuthors(self, filterType, parameter): def filterAuthors(self, filterType, parameter):
if not self._authors_model: if not self._models["authors"]:
return return
self._authors_model.setFilter({ filterType: parameter }) self._models["authors"].setFilter({ filterType: parameter })
self.filterChanged.emit() self.filterChanged.emit()
@pyqtSlot() @pyqtSlot()
def unfilterAuthors(self): def unfilterAuthors(self):
if not self._authors_model: if not self._models["authors"]:
return return
self._authors_model.setFilter({}) self._models["authors"].setFilter({})
self.filterChanged.emit() self.filterChanged.emit()