mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
13
COPYING
13
COPYING
@@ -481,10 +481,19 @@ License notice for Python
|
||||
-------------------------
|
||||
https://www.python.org/download/releases/3.4.2/license/
|
||||
|
||||
License for busybox
|
||||
--------------------
|
||||
License notice for BusyBox
|
||||
---------------------------
|
||||
BusyBox is distributed under version 2 of the General Public License
|
||||
https://busybox.net/license.html
|
||||
|
||||
Source code is available here:
|
||||
https://github.com/GNS3/busybox
|
||||
|
||||
|
||||
Licence notice for zipstream
|
||||
-----------------------------
|
||||
zipstream is distributed under version 3 of the General Public License
|
||||
https://github.com/allanlei/python-zipstream/blob/master/LICENSE
|
||||
|
||||
Source code is available here:
|
||||
https://pypi.python.org/pypi/zipstream
|
||||
|
||||
@@ -50,6 +50,7 @@ from .utils.progress_dialog import ProgressDialog
|
||||
from .utils.process_files_worker import ProcessFilesWorker
|
||||
from .utils.wait_for_connection_worker import WaitForConnectionWorker
|
||||
from .utils.wait_for_vm_worker import WaitForVMWorker
|
||||
from .utils.export_project_worker import ExportProjectWorker
|
||||
from .utils.message_box import MessageBox
|
||||
from .ports.port import Port
|
||||
from .items.node_item import NodeItem
|
||||
@@ -58,7 +59,6 @@ from .items.shape_item import ShapeItem
|
||||
from .items.image_item import ImageItem
|
||||
from .items.note_item import NoteItem
|
||||
from .topology import Topology
|
||||
from .utils.download_project import DownloadProjectWorker
|
||||
from .project import Project
|
||||
from .http_client import HTTPClient
|
||||
from .progress import Progress
|
||||
@@ -205,7 +205,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiOpenApplianceAction.triggered.connect(self.openApplianceActionSlot)
|
||||
self.uiSaveProjectAction.triggered.connect(self._saveProjectActionSlot)
|
||||
self.uiSaveProjectAsAction.triggered.connect(self._saveProjectAsActionSlot)
|
||||
self.uiDownloadRemoteProject.triggered.connect(self._downloadRemoteProjectActionSlot)
|
||||
self.uiExportProjectAction.triggered.connect(self._exportProjectActionSlot)
|
||||
self.uiImportExportConfigsAction.triggered.connect(self._importExportConfigsActionSlot)
|
||||
self.uiScreenshotAction.triggered.connect(self._screenshotActionSlot)
|
||||
self.uiSnapshotAction.triggered.connect(self._snapshotActionSlot)
|
||||
@@ -1558,19 +1558,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
|
||||
QtCore.QTimer.singleShot(counter, callback)
|
||||
|
||||
def _downloadRemoteProjectActionSlot(self):
|
||||
if self._project.temporary():
|
||||
QtWidgets.QMessageBox.warning(self, "Download project", "You cannot download a temporary project")
|
||||
return
|
||||
|
||||
def _exportProjectActionSlot(self):
|
||||
running_nodes = self._running_nodes()
|
||||
if running_nodes:
|
||||
nodes = "\n".join(running_nodes)
|
||||
MessageBox(self, "Download project", "Please stop the following nodes before downloading the project", nodes)
|
||||
MessageBox(self, "Download project", "Please stop the following nodes before exporting the project", nodes)
|
||||
return
|
||||
|
||||
download_worker = DownloadProjectWorker(self, self._project, Servers.instance())
|
||||
progress_dialog = ProgressDialog(download_worker, "Download remote project", "Downloading project files...", "Cancel", parent=self)
|
||||
export_worker = ExportProjectWorker(self, self._project)
|
||||
progress_dialog = ProgressDialog(export_worker, "Export project", "Exporting project files...", "Cancel", parent=self)
|
||||
progress_dialog.show()
|
||||
progress_dialog.exec_()
|
||||
|
||||
|
||||
@@ -105,6 +105,12 @@ class Project(QtCore.QObject):
|
||||
|
||||
self._temporary = temporary
|
||||
|
||||
def servers(self):
|
||||
"""
|
||||
:returns: List of server where GNS3 is running
|
||||
"""
|
||||
return self._created_servers
|
||||
|
||||
def id(self):
|
||||
"""
|
||||
Get project identifier
|
||||
|
||||
@@ -91,12 +91,8 @@ background-none;
|
||||
<addaction name="uiOpenProjectAction"/>
|
||||
<addaction name="uiSaveProjectAction"/>
|
||||
<addaction name="uiSaveProjectAsAction"/>
|
||||
<addaction name="uiExportProjectAction"/>
|
||||
<addaction name="uiImportProjectAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="uiMoveLocalProjectToCloudAction"/>
|
||||
<addaction name="uiMoveCloudProjectToLocalAction"/>
|
||||
<addaction name="uiDownloadRemoteProject"/>
|
||||
<addaction name="uiExportProjectAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="uiOpenApplianceAction"/>
|
||||
<addaction name="uiImportExportConfigsAction"/>
|
||||
@@ -1154,38 +1150,6 @@ background-none;
|
||||
<string>Fit in view</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiExportProjectAction">
|
||||
<property name="text">
|
||||
<string>Backup project to cloud</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiImportProjectAction">
|
||||
<property name="text">
|
||||
<string>Restore backup from cloud</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiMoveLocalProjectToCloudAction">
|
||||
<property name="text">
|
||||
<string>Move local project to cloud</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiMoveCloudProjectToLocalAction">
|
||||
<property name="text">
|
||||
<string>Move cloud project to local</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiDarkStyleAction">
|
||||
<property name="text">
|
||||
<string>Dark Style</string>
|
||||
@@ -1257,6 +1221,20 @@ background-none;
|
||||
<string>GNS3 &Doctor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExport_project">
|
||||
<property name="text">
|
||||
<string>Export project</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="uiExportProjectAction">
|
||||
<property name="icon">
|
||||
<iconset resource="../../resources/resources.qrc">
|
||||
<normaloff>:/icons/save.svg</normaloff>:/icons/save.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Export project</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -208,22 +208,22 @@ class Ui_MainWindow(object):
|
||||
self.uiOnlineHelpAction.setObjectName("uiOnlineHelpAction")
|
||||
self.uiScreenshotAction = QtWidgets.QAction(MainWindow)
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/camera-photo.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiScreenshotAction.setIcon(icon4)
|
||||
self.uiScreenshotAction.setObjectName("uiScreenshotAction")
|
||||
self.uiStartAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiStartAllAction.setEnabled(True)
|
||||
icon5 = QtGui.QIcon()
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon5.addPixmap(QtGui.QPixmap(":/icons/start.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiStartAllAction.setIcon(icon5)
|
||||
self.uiStartAllAction.setObjectName("uiStartAllAction")
|
||||
self.uiStopAllAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiStopAllAction.setEnabled(True)
|
||||
icon6 = QtGui.QIcon()
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon6.addPixmap(QtGui.QPixmap(":/icons/stop.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiStopAllAction.setIcon(icon6)
|
||||
self.uiStopAllAction.setObjectName("uiStopAllAction")
|
||||
self.uiConsoleAllAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -237,14 +237,14 @@ class Ui_MainWindow(object):
|
||||
self.uiAboutQtAction.setObjectName("uiAboutQtAction")
|
||||
self.uiZoomInAction = QtWidgets.QAction(MainWindow)
|
||||
icon8 = QtGui.QIcon()
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon8.addPixmap(QtGui.QPixmap(":/icons/zoom-in.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiZoomInAction.setIcon(icon8)
|
||||
self.uiZoomInAction.setObjectName("uiZoomInAction")
|
||||
self.uiZoomOutAction = QtWidgets.QAction(MainWindow)
|
||||
icon9 = QtGui.QIcon()
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon9.addPixmap(QtGui.QPixmap(":/icons/zoom-out.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiZoomOutAction.setIcon(icon9)
|
||||
self.uiZoomOutAction.setObjectName("uiZoomOutAction")
|
||||
self.uiZoomResetAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -261,8 +261,8 @@ class Ui_MainWindow(object):
|
||||
self.uiPreferencesAction.setObjectName("uiPreferencesAction")
|
||||
self.uiSuspendAllAction = QtWidgets.QAction(MainWindow)
|
||||
icon11 = QtGui.QIcon()
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon11.addPixmap(QtGui.QPixmap(":/icons/pause.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiSuspendAllAction.setIcon(icon11)
|
||||
self.uiSuspendAllAction.setObjectName("uiSuspendAllAction")
|
||||
self.uiAddNoteAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -290,15 +290,15 @@ class Ui_MainWindow(object):
|
||||
self.uiDrawRectangleAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDrawRectangleAction.setCheckable(True)
|
||||
icon16 = QtGui.QIcon()
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon16.addPixmap(QtGui.QPixmap(":/icons/rectangle.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDrawRectangleAction.setIcon(icon16)
|
||||
self.uiDrawRectangleAction.setObjectName("uiDrawRectangleAction")
|
||||
self.uiDrawEllipseAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDrawEllipseAction.setCheckable(True)
|
||||
icon17 = QtGui.QIcon()
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon17.addPixmap(QtGui.QPixmap(":/icons/ellipse.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiDrawEllipseAction.setIcon(icon17)
|
||||
self.uiDrawEllipseAction.setObjectName("uiDrawEllipseAction")
|
||||
self.uiShowPortNamesAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -344,41 +344,41 @@ class Ui_MainWindow(object):
|
||||
self.uiDefaultStyleAction.setObjectName("uiDefaultStyleAction")
|
||||
self.uiBrowseRoutersAction = QtWidgets.QAction(MainWindow)
|
||||
icon23 = QtGui.QIcon()
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon23.addPixmap(QtGui.QPixmap(":/icons/router.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseRoutersAction.setIcon(icon23)
|
||||
self.uiBrowseRoutersAction.setObjectName("uiBrowseRoutersAction")
|
||||
self.uiBrowseSwitchesAction = QtWidgets.QAction(MainWindow)
|
||||
icon24 = QtGui.QIcon()
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon24.addPixmap(QtGui.QPixmap(":/icons/switch.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseSwitchesAction.setIcon(icon24)
|
||||
self.uiBrowseSwitchesAction.setObjectName("uiBrowseSwitchesAction")
|
||||
self.uiBrowseEndDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon25 = QtGui.QIcon()
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon25.addPixmap(QtGui.QPixmap(":/icons/PC.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseEndDevicesAction.setIcon(icon25)
|
||||
self.uiBrowseEndDevicesAction.setObjectName("uiBrowseEndDevicesAction")
|
||||
self.uiBrowseSecurityDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon26 = QtGui.QIcon()
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon26.addPixmap(QtGui.QPixmap(":/icons/firewall.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseSecurityDevicesAction.setIcon(icon26)
|
||||
self.uiBrowseSecurityDevicesAction.setObjectName("uiBrowseSecurityDevicesAction")
|
||||
self.uiBrowseAllDevicesAction = QtWidgets.QAction(MainWindow)
|
||||
icon27 = QtGui.QIcon()
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons-hover.png"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon27.addPixmap(QtGui.QPixmap(":/icons/browse-all-icons.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiBrowseAllDevicesAction.setIcon(icon27)
|
||||
self.uiBrowseAllDevicesAction.setObjectName("uiBrowseAllDevicesAction")
|
||||
self.uiAddLinkAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiAddLinkAction.setCheckable(True)
|
||||
icon28 = QtGui.QIcon()
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Active, QtGui.QIcon.On)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new-hover.svg"), QtGui.QIcon.Active, QtGui.QIcon.Off)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/cancel-connection.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
|
||||
icon28.addPixmap(QtGui.QPixmap(":/icons/connection-new.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.uiAddLinkAction.setIcon(icon28)
|
||||
self.uiAddLinkAction.setObjectName("uiAddLinkAction")
|
||||
self.uiGettingStartedAction = QtWidgets.QAction(MainWindow)
|
||||
@@ -387,18 +387,6 @@ class Ui_MainWindow(object):
|
||||
self.uiLabInstructionsAction.setObjectName("uiLabInstructionsAction")
|
||||
self.uiFitInViewAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiFitInViewAction.setObjectName("uiFitInViewAction")
|
||||
self.uiExportProjectAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiExportProjectAction.setVisible(False)
|
||||
self.uiExportProjectAction.setObjectName("uiExportProjectAction")
|
||||
self.uiImportProjectAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiImportProjectAction.setVisible(False)
|
||||
self.uiImportProjectAction.setObjectName("uiImportProjectAction")
|
||||
self.uiMoveLocalProjectToCloudAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiMoveLocalProjectToCloudAction.setVisible(False)
|
||||
self.uiMoveLocalProjectToCloudAction.setObjectName("uiMoveLocalProjectToCloudAction")
|
||||
self.uiMoveCloudProjectToLocalAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiMoveCloudProjectToLocalAction.setVisible(False)
|
||||
self.uiMoveCloudProjectToLocalAction.setObjectName("uiMoveCloudProjectToLocalAction")
|
||||
self.uiDarkStyleAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDarkStyleAction.setObjectName("uiDarkStyleAction")
|
||||
self.uiActionFullscreen = QtWidgets.QAction(MainWindow)
|
||||
@@ -427,6 +415,11 @@ class Ui_MainWindow(object):
|
||||
self.uiExportDebugInformationAction.setObjectName("uiExportDebugInformationAction")
|
||||
self.uiDoctorAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiDoctorAction.setObjectName("uiDoctorAction")
|
||||
self.actionExport_project = QtWidgets.QAction(MainWindow)
|
||||
self.actionExport_project.setObjectName("actionExport_project")
|
||||
self.uiExportProjectAction = QtWidgets.QAction(MainWindow)
|
||||
self.uiExportProjectAction.setIcon(icon2)
|
||||
self.uiExportProjectAction.setObjectName("uiExportProjectAction")
|
||||
self.uiEditMenu.addAction(self.uiSelectAllAction)
|
||||
self.uiEditMenu.addAction(self.uiSelectNoneAction)
|
||||
self.uiEditMenu.addSeparator()
|
||||
@@ -435,12 +428,8 @@ class Ui_MainWindow(object):
|
||||
self.uiFileMenu.addAction(self.uiOpenProjectAction)
|
||||
self.uiFileMenu.addAction(self.uiSaveProjectAction)
|
||||
self.uiFileMenu.addAction(self.uiSaveProjectAsAction)
|
||||
self.uiFileMenu.addAction(self.uiExportProjectAction)
|
||||
self.uiFileMenu.addAction(self.uiImportProjectAction)
|
||||
self.uiFileMenu.addSeparator()
|
||||
self.uiFileMenu.addAction(self.uiMoveLocalProjectToCloudAction)
|
||||
self.uiFileMenu.addAction(self.uiMoveCloudProjectToLocalAction)
|
||||
self.uiFileMenu.addAction(self.uiDownloadRemoteProject)
|
||||
self.uiFileMenu.addAction(self.uiExportProjectAction)
|
||||
self.uiFileMenu.addSeparator()
|
||||
self.uiFileMenu.addAction(self.uiOpenApplianceAction)
|
||||
self.uiFileMenu.addAction(self.uiImportExportConfigsAction)
|
||||
@@ -672,10 +661,6 @@ class Ui_MainWindow(object):
|
||||
self.uiGettingStartedAction.setToolTip(_translate("MainWindow", "Show GNS3 news"))
|
||||
self.uiLabInstructionsAction.setText(_translate("MainWindow", "&Lab instructions"))
|
||||
self.uiFitInViewAction.setText(_translate("MainWindow", "Fit in view"))
|
||||
self.uiExportProjectAction.setText(_translate("MainWindow", "Backup project to cloud"))
|
||||
self.uiImportProjectAction.setText(_translate("MainWindow", "Restore backup from cloud"))
|
||||
self.uiMoveLocalProjectToCloudAction.setText(_translate("MainWindow", "Move local project to cloud"))
|
||||
self.uiMoveCloudProjectToLocalAction.setText(_translate("MainWindow", "Move cloud project to local"))
|
||||
self.uiDarkStyleAction.setText(_translate("MainWindow", "Dark Style"))
|
||||
self.uiActionFullscreen.setText(_translate("MainWindow", "Fullscreen"))
|
||||
self.uiActionFullscreen.setShortcut(_translate("MainWindow", "Ctrl+F"))
|
||||
@@ -688,6 +673,8 @@ class Ui_MainWindow(object):
|
||||
self.uiExportDebugInformationAction.setText(_translate("MainWindow", "Export debug information"))
|
||||
self.uiExportDebugInformationAction.setToolTip(_translate("MainWindow", "&Export debug information"))
|
||||
self.uiDoctorAction.setText(_translate("MainWindow", "GNS3 &Doctor"))
|
||||
self.actionExport_project.setText(_translate("MainWindow", "Export project"))
|
||||
self.uiExportProjectAction.setText(_translate("MainWindow", "Export project"))
|
||||
|
||||
from ..console_view import ConsoleView
|
||||
from ..graphics_view import GraphicsView
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import os
|
||||
|
||||
from ..qt import QtCore
|
||||
from ..utils import md5_hash_file
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DownloadProjectWorker(QtCore.QObject):
|
||||
|
||||
"""
|
||||
Downloads project from cloud storage
|
||||
"""
|
||||
|
||||
# signals to update the progress dialog.
|
||||
error = QtCore.pyqtSignal(str, bool)
|
||||
finished = QtCore.pyqtSignal()
|
||||
updated = QtCore.pyqtSignal(int)
|
||||
file_downloaded = QtCore.pyqtSignal()
|
||||
file_list_received = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent, project, servers):
|
||||
self._is_running = False
|
||||
self._project = project
|
||||
self._servers = servers
|
||||
self._files_to_download = []
|
||||
self._get_file_lists = 0 # Counter to know how many file list remain to download
|
||||
self._total_files_to_download = 0
|
||||
super().__init__(parent)
|
||||
|
||||
def run(self):
|
||||
self.updated.emit(0)
|
||||
self._is_running = True
|
||||
|
||||
try:
|
||||
if self._servers.vmServer():
|
||||
self._project.get(self._servers.vmServer(), "/files", self._fileListReceived)
|
||||
self._get_file_lists += 1
|
||||
for server in self._servers.remoteServers().values():
|
||||
self._project.get(server, "/files", self._fileListReceived)
|
||||
self._get_file_lists += 1
|
||||
except Exception as e:
|
||||
self.error.emit("Error importing project: {}".format(e), True)
|
||||
|
||||
if self._get_file_lists == 0:
|
||||
self._is_running = False
|
||||
|
||||
def _fileListReceived(self, result, error=False, server=None, **kwargs):
|
||||
self._get_file_lists -= 1
|
||||
if error:
|
||||
msg = "Error while downloading project {}".format(result["message"])
|
||||
log.error(msg)
|
||||
self.error.emit(msg, True)
|
||||
return
|
||||
|
||||
for file in result:
|
||||
file["server"] = server
|
||||
self._files_to_download.append(file)
|
||||
self._total_files_to_download += 1
|
||||
|
||||
if self._get_file_lists <= 0:
|
||||
self._downloadNextFile()
|
||||
else:
|
||||
self._is_running = False
|
||||
self.finished.emit()
|
||||
|
||||
def _downloadNextFile(self):
|
||||
if not self._is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
file_to_download = self._files_to_download.pop()
|
||||
except IndexError:
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
file_path = os.path.join(self._project.filesDir(), file_to_download["path"])
|
||||
|
||||
self.updated.emit(round(100.0 / self._total_files_to_download * (self._total_files_to_download - (len(self._files_to_download) + 1))))
|
||||
|
||||
try:
|
||||
if os.path.dirname(file_path) is not None:
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
|
||||
if os.path.exists(file_path):
|
||||
if md5_hash_file(file_path) == file_to_download["md5sum"]:
|
||||
self._downloadNextFile()
|
||||
return
|
||||
|
||||
f = open(file_path, "wb+")
|
||||
except OSError as e:
|
||||
self.error.emit("Could not write file {}: {}".format(file_path, e), False)
|
||||
return
|
||||
self._project.get(file_to_download["server"], "/files/{}".format(file_to_download["path"]), self._downloadFileReceived, context={"fd": f, "file_path": file_path}, downloadProgressCallback=self._downloadFileProgress)
|
||||
|
||||
def _downloadFileReceived(self, content, error=False, server=None, context={}):
|
||||
"""
|
||||
Called when download finish
|
||||
"""
|
||||
context["fd"].close()
|
||||
self._downloadNextFile()
|
||||
|
||||
def _downloadFileProgress(self, content, server=None, context={}):
|
||||
"""
|
||||
Called for each part of the file
|
||||
"""
|
||||
try:
|
||||
context["fd"].write(content)
|
||||
except OSError as e:
|
||||
self.error.emit("Could not write file {}: {}".format(context["file_path"], e), False)
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Cancel this worker.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
return
|
||||
self._is_running = False
|
||||
117
gns3/utils/export_project_worker.py
Normal file
117
gns3/utils/export_project_worker.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
import shutil
|
||||
|
||||
from ..qt import QtCore, QtWidgets
|
||||
from ..servers import Servers
|
||||
|
||||
|
||||
class ExportProjectWorker(QtCore.QObject):
|
||||
"""
|
||||
Export the current topology to a portable format
|
||||
"""
|
||||
|
||||
# signals to update the progress dialog.
|
||||
error = QtCore.pyqtSignal(str, bool)
|
||||
finished = QtCore.pyqtSignal()
|
||||
updated = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent, project):
|
||||
super().__init__(parent)
|
||||
self._project = project
|
||||
|
||||
def run(self):
|
||||
if self._project.temporary():
|
||||
self.error.emit("You cannot export a temporary project", True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
for server in self._project.servers():
|
||||
if not server.isLocal() and not server.isGNS3VM():
|
||||
self.error.emit("Project from remote server can not be exported. Only project for local and GNS3 VM are supported.", True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
self._path, _ = QtWidgets.QFileDialog.getSaveFileName(self.parent(), "Export project", None, "GNS3 Topology (*.gns3z)", "GNS3 Topology (*.gns3z)")
|
||||
if self._path is None:
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
try:
|
||||
open(self._path, 'wb+').close()
|
||||
except OSError as e:
|
||||
self.error.emit("Can't write the topology {}: {}".format(self._path, str(e)), True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
vm_server = None
|
||||
for server in self._project.servers():
|
||||
if server.isGNS3VM():
|
||||
vm_server = server
|
||||
|
||||
if vm_server:
|
||||
self._project.get(vm_server, "/export", self._exportVmReceived, downloadProgressCallback=self._downloadFileProgress)
|
||||
else:
|
||||
self._project.get(Servers.instance().localServer(), "/export", self._exportLocalReceived, downloadProgressCallback=self._downloadFileProgress)
|
||||
|
||||
def _exportVmReceived(self, content, error=False, server=None, context={}, **kwargs):
|
||||
if error:
|
||||
self.error.emit("Can't export the project from the VM", True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
vm_path = os.path.join(self._project.filesDir(), "servers", "vm")
|
||||
if os.path.exists(vm_path):
|
||||
shutil.rmtree(vm_path)
|
||||
os.makedirs(vm_path, exist_ok=True)
|
||||
with zipfile.ZipFile(self._path) as myzip:
|
||||
myzip.extractall(vm_path)
|
||||
|
||||
# We reset the content of the file
|
||||
try:
|
||||
open(self._path, 'wb+').close()
|
||||
except OSError as e:
|
||||
self.error.emit("Can't write the topology {}: {}".format(self._path, str(e)), True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
self._project.get(Servers.instance().localServer(), "/export", self._exportLocalReceived, downloadProgressCallback=self._downloadFileProgress)
|
||||
|
||||
def _exportLocalReceived(self, content, error=False, server=None, context={}, **kwargs):
|
||||
if error:
|
||||
self.error.emit("Can't export the project from the local server", True)
|
||||
self.finished.emit()
|
||||
return
|
||||
self.finished.emit()
|
||||
|
||||
def _downloadFileProgress(self, content, server=None, context={}, **kwargs):
|
||||
"""
|
||||
Called for each part of the file
|
||||
"""
|
||||
try:
|
||||
with open(self._path, 'ab') as f:
|
||||
f.write(content)
|
||||
except OSError as e:
|
||||
self.error.emit("Can't write the topology {}: {}".format(self._path, str(e)), True)
|
||||
self.finished.emit()
|
||||
return
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
@@ -1,203 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import pytest
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
from gns3.project import Project
|
||||
from gns3.servers import Servers
|
||||
from gns3.utils.download_project import DownloadProjectWorker
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def servers():
|
||||
|
||||
Servers._instance = None
|
||||
servers = Servers.instance()
|
||||
servers._remote_servers = {} # Erase server from settings
|
||||
return servers
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def project(tmpdir):
|
||||
|
||||
p = MagicMock()
|
||||
p.filesDir.return_value = str(tmpdir)
|
||||
return p
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def download_project(project, servers):
|
||||
|
||||
return DownloadProjectWorker(None, project, servers)
|
||||
|
||||
|
||||
def test_download_project_without_server(download_project):
|
||||
|
||||
download_project.run()
|
||||
|
||||
|
||||
def test_download_project_with_one_server(download_project, servers, tmpdir, project):
|
||||
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
download_project.run()
|
||||
project.get.assert_called_with(one, "/files", download_project._fileListReceived)
|
||||
assert download_project._get_file_lists == 1
|
||||
|
||||
|
||||
def test_fileListReceived(download_project, servers):
|
||||
|
||||
download_project._get_file_lists = 3
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
two = servers.getRemoteServer("http", "127.0.0.1", 8001, None)
|
||||
|
||||
download_project._fileListReceived([{"path": "a", "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249"}], server=one)
|
||||
download_project._fileListReceived([{"path": "b", "md5sum": "126a8a51b9d1bbd07fddc65819a542c3"}], server=two)
|
||||
assert download_project._get_file_lists == 1
|
||||
|
||||
assert download_project._files_to_download == [
|
||||
{"path": "a", "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249", "server": one},
|
||||
{"path": "b", "md5sum": "126a8a51b9d1bbd07fddc65819a542c3", "server": two}
|
||||
]
|
||||
assert download_project._total_files_to_download == 2
|
||||
|
||||
|
||||
def test_downloadNextFile_empty_list(download_project):
|
||||
|
||||
mark_finished = MagicMock()
|
||||
download_project.finished.connect(mark_finished)
|
||||
download_project._is_running = True
|
||||
download_project._downloadNextFile()
|
||||
download_project._is_running = False
|
||||
assert mark_finished.called
|
||||
|
||||
|
||||
def test_downloadNextFile_non_empty_list(download_project, servers, project):
|
||||
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
|
||||
download_project._files_to_download = [
|
||||
{"path": "a", "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249", "server": one}
|
||||
]
|
||||
download_project._total_files_to_download = 1
|
||||
download_project._is_running = True
|
||||
|
||||
mark_finished = MagicMock()
|
||||
download_project.finished.connect(mark_finished)
|
||||
download_project._downloadNextFile()
|
||||
assert not mark_finished.called
|
||||
|
||||
assert os.path.exists(os.path.join(project.filesDir(), "a"))
|
||||
assert project.get.called
|
||||
args, kwargs = project.get.call_args
|
||||
assert args[0] == one
|
||||
assert args[1] == "/files/a"
|
||||
assert args[2] == download_project._downloadFileReceived
|
||||
assert kwargs["context"] is not None
|
||||
assert kwargs["downloadProgressCallback"] == download_project._downloadFileProgress
|
||||
|
||||
|
||||
def test_downloadNextFile_file_exist_but_different(download_project, servers, project):
|
||||
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
|
||||
download_project._files_to_download = [
|
||||
{"path": "a", "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249", "server": one}
|
||||
]
|
||||
download_project._total_files_to_download = 1
|
||||
download_project._is_running = True
|
||||
|
||||
with open(os.path.join(project.filesDir(), "a"), "w+") as f:
|
||||
f.write("hello")
|
||||
|
||||
download_project._downloadNextFile()
|
||||
|
||||
assert project.get.called
|
||||
args, kwargs = project.get.call_args
|
||||
assert args[0] == one
|
||||
assert args[1] == "/files/a"
|
||||
assert args[2] == download_project._downloadFileReceived
|
||||
assert kwargs["context"] is not None
|
||||
assert kwargs["downloadProgressCallback"] == download_project._downloadFileProgress
|
||||
|
||||
|
||||
def test_downloadNextFile_file_exist_and_the_same(download_project, servers, project):
|
||||
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
|
||||
download_project._files_to_download = [
|
||||
{"path": "a", "md5sum": "5d41402abc4b2a76b9719d911017c592", "server": one}
|
||||
]
|
||||
download_project._total_files_to_download = 1
|
||||
|
||||
with open(os.path.join(project.filesDir(), "a"), "w+") as f:
|
||||
f.write("hello")
|
||||
|
||||
download_project._downloadNextFile()
|
||||
|
||||
assert not project.get.called
|
||||
|
||||
|
||||
def test_downloadNextFile_subdirectory(download_project, servers, project):
|
||||
|
||||
one = servers.getRemoteServer("http", "127.0.0.1", 3080, None)
|
||||
|
||||
download_project._files_to_download = [
|
||||
{"path": "a/b/c", "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249", "server": one}
|
||||
]
|
||||
download_project._total_files_to_download = 4
|
||||
download_project._is_running = True
|
||||
|
||||
mark_finished = MagicMock()
|
||||
download_project.finished.connect(mark_finished)
|
||||
|
||||
progress = MagicMock()
|
||||
download_project.updated.connect(progress)
|
||||
|
||||
download_project._downloadNextFile()
|
||||
assert not mark_finished.called
|
||||
progress.assert_called_once_with(75)
|
||||
|
||||
assert os.path.exists(os.path.join(project.filesDir(), "a", "b", "c"))
|
||||
assert project.get.called
|
||||
args, kwargs = project.get.call_args
|
||||
assert args[0] == one
|
||||
assert args[1] == "/files/a/b/c"
|
||||
assert args[2] == download_project._downloadFileReceived
|
||||
assert kwargs["context"] is not None
|
||||
assert kwargs["downloadProgressCallback"] == download_project._downloadFileProgress
|
||||
|
||||
|
||||
def test_downloadFileReceived(download_project, tmpdir):
|
||||
|
||||
f = open(str(tmpdir / "a"), "w+")
|
||||
download_project._downloadFileReceived(None, context={"fd": f})
|
||||
assert f.closed
|
||||
|
||||
|
||||
# TODO: Test progress
|
||||
# TODO: MD5 sum
|
||||
|
||||
def test_downloadFileReceived(download_project, tmpdir):
|
||||
|
||||
f = open(str(tmpdir / "a"), "w+")
|
||||
download_project._downloadFileProgress("hello", context={"fd": f})
|
||||
f.close()
|
||||
with open(str(tmpdir / "a")) as f:
|
||||
assert f.read() == "hello"
|
||||
Reference in New Issue
Block a user