CURA-5035 Re-implemented "installed" view

This commit is contained in:
Ian Paschal 2018-04-11 14:59:59 +02:00
parent 80c21acf79
commit a947b768d3
6 changed files with 192 additions and 110 deletions

View File

@ -44,7 +44,7 @@ Window
ToolboxLoadingPage ToolboxLoadingPage
{ {
id: viewLoading id: viewLoading
visible: manager.viewCategory != "installed" && !dataReady visible: manager.viewCategory != "installed" && manager.viewPage == "loading"
// TODO: Replace !dataReady with manager.viewPage == "loading" // TODO: Replace !dataReady with manager.viewPage == "loading"
} }
ToolboxDownloadsPage ToolboxDownloadsPage
@ -65,8 +65,7 @@ Window
ToolboxInstalledPage ToolboxInstalledPage
{ {
id: installedPluginList id: installedPluginList
visible: manager.viewCategory == "installed" && dataReady visible: manager.viewCategory == "installed"
// TODO: Replace !dataReady with manager.viewPage == "loading"
} }
} }
ToolboxShadow ToolboxShadow

View File

@ -34,7 +34,7 @@ Column
Repeater Repeater
{ {
model: manager.materialShowcaseModel model: manager.pluginsShowcaseModel
delegate: ToolboxDownloadsShowcaseTile {} delegate: ToolboxDownloadsShowcaseTile {}
} }
} }

View File

@ -1,33 +1,102 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// PluginBrowser is released under the terms of the LGPLv3 or higher. // PluginBrowser is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles
import UM 1.1 as UM import UM 1.1 as UM
ScrollView ScrollView
{ {
anchors.fill: parent id: base
ListView frameVisible: true
width: parent.width
height: parent.height
style: UM.Theme.styles.scrollview
Column
{ {
id: pluginList spacing: UM.Theme.getSize("default_margin").height
property var activePlugin
property var filter: "installed"
anchors anchors
{ {
fill: parent right: parent.right
topMargin: UM.Theme.getSize("default_margin").height left: parent.left
bottomMargin: UM.Theme.getSize("default_margin").height leftMargin: UM.Theme.getSize("double_margin").width
leftMargin: UM.Theme.getSize("default_margin").width topMargin: UM.Theme.getSize("double_margin").height
rightMargin: UM.Theme.getSize("default_margin").width bottomMargin: UM.Theme.getSize("double_margin").height
top: parent.top
}
height: childrenRect.height + 4 * UM.Theme.getSize("default_margin").height
Label
{
width: parent.width
text: "Plugins"
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Rectangle
{
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("text_medium")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: childrenRect.height
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
}
Repeater
{
id: materialList
model: manager.packagesModel
delegate: ToolboxInstalledTile {}
}
}
}
Label
{
width: base.width
text: "Materials"
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("medium")
}
Rectangle
{
color: "transparent"
width: parent.width
height: childrenRect.height + 1 * UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("text_medium")
border.width: UM.Theme.getSize("default_lining").width
Column
{
height: childrenRect.height
anchors
{
top: parent.top
right: parent.right
left: parent.left
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_lining").width
bottomMargin: UM.Theme.getSize("default_lining").width
}
Repeater
{
id: pluginList
model: manager.packagesModel
delegate: ToolboxInstalledTile {}
}
}
} }
model: manager.pluginsModel
delegate: ToolboxInstalledTile {}
} }
} }

View File

@ -19,7 +19,7 @@ Component {
// Don't show required plugins as they can't be managed anyway: // Don't show required plugins as they can't be managed anyway:
height: !model.required ? 84 : 0 height: !model.required ? 84 : 0
visible: !model.required ? true : false visible: !model.required ? true : false
color: Qt.rgba(1.0, 0.0, 0.0, 0.1) // color: Qt.rgba(1.0, 0.0, 0.0, 0.1)
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right

View File

@ -14,5 +14,13 @@ Rectangle
id: base id: base
width: parent.width width: parent.width
height: parent.height height: parent.height
color: "red" color: "transparent"
Label
{
text: "Fetching packages..."
anchors
{
centerIn: parent
}
}
} }

View File

@ -22,6 +22,7 @@ import platform
import zipfile import zipfile
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.CuraPackageManager import CuraPackageManager
from .AuthorsModel import AuthorsModel from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel from .PackagesModel import PackagesModel
@ -33,6 +34,7 @@ class Toolbox(QObject, Extension):
super().__init__(parent) super().__init__(parent)
self._plugin_registry = Application.getInstance().getPluginRegistry() self._plugin_registry = Application.getInstance().getPluginRegistry()
self._package_manager = None
self._packages_version = self._plugin_registry.APIVersion self._packages_version = self._plugin_registry.APIVersion
self._api_version = 1 self._api_version = 1
self._api_url = "https://api-staging.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version) self._api_url = "https://api-staging.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version)
@ -48,11 +50,14 @@ class Toolbox(QObject, Extension):
self._network_manager = None self._network_manager = None
self._packages_metadata = [] # Stores the remote information of the packages self._packages_metadata = []
self._packages_model = None # Model that list the remote available packages self._packages_model = None
self._showcase_model = None self._plugins_showcase_model = None
self._plugins_installed_model = None
self._materials_showcase_model = None
self._materials_installed_model = None
self._authors_model = None self._authors_model = None
self._installed_model = None
# These properties are for keeping track of the UI state: # These properties are for keeping track of the UI state:
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ -66,7 +71,7 @@ class Toolbox(QObject, Extension):
# View page defines which type of page layout to use. For example, # View page defines which type of page layout to use. For example,
# possible values include "overview", "detail" or "author". # possible values include "overview", "detail" or "author".
# Formerly self._detail_view. # Formerly self._detail_view.
self._view_page = "overview" self._view_page = "loading"
# View selection defines what is currently selected and should be # View selection defines what is currently selected and should be
# used in filtering. This could be an author name (if _view_page is set # used in filtering. This could be an author name (if _view_page is set
@ -155,9 +160,19 @@ class Toolbox(QObject, Extension):
@pyqtSlot() @pyqtSlot()
def browsePackages(self): def browsePackages(self):
self._createNetworkManager() self._package_manager = Application.getInstance().getCuraPackageManager()
self.requestShowcase() # Create the network manager:
self.requestPackages() # This was formerly its own function but really had no reason to be as
# it was never called more than once ever.
if self._network_manager:
self._network_manager.finished.disconnect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
self._requestShowcase()
self._requestPackages()
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()
@ -224,8 +239,8 @@ class Toolbox(QObject, Extension):
return self._plugins_model return self._plugins_model
@pyqtProperty(QObject, notify = showcaseMetadataChanged) @pyqtProperty(QObject, notify = showcaseMetadataChanged)
def materialShowcaseModel(self): def pluginsShowcaseModel(self):
return self._showcase_model return self._plugins_showcase_model
@pyqtProperty(QObject, notify = packagesMetadataChanged) @pyqtProperty(QObject, notify = packagesMetadataChanged)
def packagesModel(self): def packagesModel(self):
@ -239,6 +254,18 @@ class Toolbox(QObject, Extension):
def dataReady(self): def dataReady(self):
return self._packages_model is not None return self._packages_model is not None
@pyqtProperty(bool, notify = restartRequiredChanged)
def restartRequired(self):
return self._restart_required
@pyqtSlot()
def restart(self):
CuraApplication.getInstance().windowClosed()
# Checks
# --------------------------------------------------------------------------
def _checkCanUpgrade(self, id, version): def _checkCanUpgrade(self, id, version):
# Scan plugin server data for plugin with the given id: # Scan plugin server data for plugin with the given id:
for plugin in self._packages_metadata: for plugin in self._packages_metadata:
@ -250,57 +277,32 @@ class Toolbox(QObject, Extension):
return True return True
return False return False
def _checkAlreadyInstalled(self, id): def _checkInstalled(self, id):
metadata = self._plugin_registry.getMetaData(id) if id in self._will_uninstall:
# We already installed this plugin, but the registry just doesn't know it yet.
if id in self._newly_installed_plugin_ids:
return True
# We already uninstalled this plugin, but the registry just doesn't know it yet:
elif id in self._newly_uninstalled_plugin_ids:
return False return False
elif metadata != {}: if id in self._package_manager.getInstalledPackages():
return True return True
else: if id in self._will_install:
return False return True
return False
def _checkInstallStatus(self, plugin_id):
if plugin_id in self._plugin_registry.getInstalledPlugins():
return "installed"
else:
return "uninstalled"
def _checkEnabled(self, id): def _checkEnabled(self, id):
if id in self._plugin_registry.getActivePlugins(): if id in self._plugin_registry.getActivePlugins():
return True return True
return False return False
def _createNetworkManager(self):
if self._network_manager:
self._network_manager.finished.disconnect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
self._network_manager = QNetworkAccessManager()
self._network_manager.finished.connect(self._onRequestFinished)
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
@pyqtProperty(bool, notify = restartRequiredChanged)
def restartRequired(self):
return self._restart_required
@pyqtSlot()
def restart(self):
CuraApplication.getInstance().windowClosed()
# Make API Calls # Make API Calls
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
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)) url = QUrl("{base_url}/packages".format(base_url = self._api_url))
self._get_packages_request = QNetworkRequest(url) 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)) url = QUrl("{base_url}/showcase".format(base_url = self._api_url))
self._get_showcase_request = QNetworkRequest(url) self._get_showcase_request = QNetworkRequest(url)
@ -381,6 +383,7 @@ class Toolbox(QObject, Extension):
self._authors_metadata.append(package["author"]) self._authors_metadata.append(package["author"])
self._authors_model.setMetaData(self._authors_metadata) self._authors_model.setMetaData(self._authors_metadata)
self.authorsMetadataChanged.emit() self.authorsMetadataChanged.emit()
self.setViewPage("overview")
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
@ -390,12 +393,12 @@ class Toolbox(QObject, Extension):
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._showcase_model: if not self._plugins_showcase_model:
self._showcase_model = PackagesModel() self._plugins_showcase_model = PackagesModel()
self._showcase_metadata = json_data["data"] self._showcase_metadata = json_data["data"]
print(self._showcase_metadata) print(self._showcase_metadata)
self._showcase_model.setPackagesMetaData(self._showcase_metadata) self._plugins_showcase_model.setPackagesMetaData(self._showcase_metadata)
for package in self._showcase_model.items: for package in self._plugins_showcase_model.items:
print(package) print(package)
self.showcaseMetadataChanged.emit() self.showcaseMetadataChanged.emit()
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
@ -406,63 +409,66 @@ class Toolbox(QObject, Extension):
pass pass
def _onDownloadProgress(self, bytes_sent, bytes_total): def _onDownloadProgress(self, bytes_sent, bytes_total):
print("Downloading bytes:", bytes_total)
if bytes_total > 0: if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100 new_progress = bytes_sent / bytes_total * 100
self.setDownloadProgress(new_progress) self.setDownloadProgress(new_progress)
if new_progress == 100.0: if new_progress == 100.0:
Logger.log("i", "Toolbox: Download complete.")
self.setIsDownloading(False) self.setIsDownloading(False)
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress) self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
# must not delete the temporary file on Windows # must not delete the temporary file on Windows
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curaplugin", delete = False) self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
file_path = self._temp_plugin_file.name file_path = self._temp_plugin_file.name
# write first and close, otherwise on Windows, it cannot read the file # write first and close, otherwise on Windows, it cannot read the file
self._temp_plugin_file.write(self._download_reply.readAll()) self._temp_plugin_file.write(self._download_reply.readAll())
self._temp_plugin_file.close() self._temp_plugin_file.close()
self._onDownloadComplete(file_path) self._onDownloadComplete(file_path)
return return
def _onDownloadComplete(self, file_path): def _onDownloadComplete(self, file_path):
with zipfile.ZipFile(file_path, "r") as zip_ref: Logger.log("i", "Toolbox: Download complete.")
plugin_id = None print(file_path)
for file in zip_ref.infolist(): if self._package_manager.isPackageFile(file_path):
if file.filename.endswith("/"): self._package_manager.install(file_path)
plugin_id = file.filename.strip("/") return
break else:
Logger.log("w", "Toolbox: Package was not a valid CuraPackage.")
if plugin_id is None: # with zipfile.ZipFile(file_path, "r") as zip_ref:
msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from <filename>{0}</filename>", file_path) # plugin_id = None
msg_title = i18n_catalog.i18nc("@info:tile", "Warning") # for file in zip_ref.infolist():
self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title) # if file.filename.endswith("/"):
return # plugin_id = file.filename.strip("/")
# break
# find a potential license file #
plugin_root_dir = plugin_id + "/" # if plugin_id is None:
license_file = None # msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from <filename>{0}</filename>", file_path)
for f in zip_ref.infolist(): # msg_title = i18n_catalog.i18nc("@info:tile", "Warning")
# skip directories (with file_size = 0) and files not in the plugin directory # self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title)
if f.file_size == 0 or not f.filename.startswith(plugin_root_dir): # return
continue #
file_name = os.path.basename(f.filename).lower() # # find a potential license file
file_base_name, file_ext = os.path.splitext(file_name) # plugin_root_dir = plugin_id + "/"
if file_base_name in ["license", "licence"]: # license_file = None
license_file = f.filename # for f in zip_ref.infolist():
break # # skip directories (with file_size = 0) and files not in the plugin directory
# if f.file_size == 0 or not f.filename.startswith(plugin_root_dir):
# show a dialog for user to read and accept/decline the license # continue
if license_file is not None: # file_name = os.path.basename(f.filename).lower()
Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id) # file_base_name, file_ext = os.path.splitext(file_name)
license_content = zip_ref.read(license_file).decode('utf-8') # if file_base_name in ["license", "licence"]:
self.openLicenseDialog(plugin_id, license_content, file_path) # license_file = f.filename
return # break
#
# there is no license file, directly install the plugin # # show a dialog for user to read and accept/decline the license
self.installPlugin(file_path) # if license_file is not None:
return # Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id)
# license_content = zip_ref.read(license_file).decode('utf-8')
# self.openLicenseDialog(plugin_id, license_content, file_path)
# return
#
# # there is no license file, directly install the plugin
# self.installPlugin(file_path)
# return