From 098a23520aeba150088491ee41bfdb83c93d8a51 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 26 May 2014 12:32:58 -0600 Subject: [PATCH] Relative paths support for IOS/IOU images in project files. --- gns3/graphics_view.py | 16 +++++++++- gns3/main_window.py | 8 ++++- gns3/modules/builtin/__init__.py | 11 ++++++- gns3/modules/dynamips/__init__.py | 19 +++++++++++ gns3/modules/dynamips/nodes/router.py | 12 +++++-- .../pages/ios_router_preferences_page.py | 17 ++++------ gns3/modules/iou/__init__.py | 19 +++++++++++ gns3/modules/iou/iou_device.py | 13 ++++++-- .../iou/pages/iou_device_preferences_page.py | 32 +++++++++++++++++-- gns3/modules/module.py | 18 +++++++++++ gns3/modules/vpcs/__init__.py | 9 ++++++ 11 files changed, 152 insertions(+), 22 deletions(-) diff --git a/gns3/graphics_view.py b/gns3/graphics_view.py index b5c432af1..721e4c1de 100644 --- a/gns3/graphics_view.py +++ b/gns3/graphics_view.py @@ -119,7 +119,21 @@ def updateProjectFilesDir(self, path): instance = module.instance() instance.setProjectFilesDir(path) except ModuleError as e: - QtGui.QMessageBox.critical(self, "Local working directory", "{}".format(e)) + QtGui.QMessageBox.critical(self, "Local projects directory", "{}".format(e)) + + def updateImageFilesDir(self, path): + """ + Updates the image files directory path for all modules. + + :param path: path to the local images files directory. + """ + + try: + for module in MODULES: + instance = module.instance() + instance.setImageFilesDir(path) + except ModuleError as e: + QtGui.QMessageBox.critical(self, "Local images directory", "{}".format(e)) def _loadSettings(self): """ diff --git a/gns3/main_window.py b/gns3/main_window.py index 0dfd3abf7..f2c0c997a 100644 --- a/gns3/main_window.py +++ b/gns3/main_window.py @@ -86,6 +86,9 @@ def __init__(self, parent=None): self.uiDocksMenu.addAction(self.uiConsoleDockWidget.toggleViewAction()) self.uiDocksMenu.addAction(self.uiNodesDockWidget.toggleViewAction()) + # set the images directory + self.uiGraphicsView.updateImageFilesDir(self.imagesDirPath()) + # load initial stuff once the event loop isn't busy QtCore.QTimer.singleShot(0, self.startupLoading) @@ -136,6 +139,10 @@ def setSettings(self, new_settings): :param new_settings: settings dictionary """ + # set a new images directory + if new_settings["images_path"] != self.imagesDirPath(): + self.uiGraphicsView.updateImageFilesDir(self.imagesDirPath()) + # save the settings self._settings.update(new_settings) settings = QtCore.QSettings() @@ -1060,7 +1067,6 @@ def projectsDirPath(self): return self._settings["projects_path"] - def imagesDirPath(self): """ Returns the images directory path. diff --git a/gns3/modules/builtin/__init__.py b/gns3/modules/builtin/__init__.py index 25cd4fb6f..bdd53310b 100644 --- a/gns3/modules/builtin/__init__.py +++ b/gns3/modules/builtin/__init__.py @@ -49,7 +49,16 @@ def setProjectFilesDir(self, path): :param path: path to the local project files directory """ - pass + pass # not used by this module + + def setImageFilesDir(self, path): + """ + Sets the image files directory path this module. + + :param path: path to the local image files directory + """ + + pass # not used by this module def addServer(self, server): """ diff --git a/gns3/modules/dynamips/__init__.py b/gns3/modules/dynamips/__init__.py index 84e37502f..b1d5155b7 100644 --- a/gns3/modules/dynamips/__init__.py +++ b/gns3/modules/dynamips/__init__.py @@ -56,6 +56,7 @@ def __init__(self): self._servers = [] self._nodes = [] self._working_dir = "" + self._images_dir = "" # load the settings and IOS images. self._loadSettings() @@ -177,6 +178,24 @@ def setProjectFilesDir(self, path): if server.connected(): self._sendSettings(server) + def setImageFilesDir(self, path): + """ + Sets the image files directory path this module. + + :param path: path to the local image files directory + """ + + self._images_dir = os.path.join(path, "IOS") + + def imageFilesDir(self): + """ + Returns the files directory path this module. + + :returns: path to the local image files directory + """ + + return self._images_dir + def addServer(self, server): """ Adds a server to be used by this module. diff --git a/gns3/modules/dynamips/nodes/router.py b/gns3/modules/dynamips/nodes/router.py index 0459caaf4..4f606edb0 100644 --- a/gns3/modules/dynamips/nodes/router.py +++ b/gns3/modules/dynamips/nodes/router.py @@ -46,6 +46,7 @@ def __init__(self, module, server, platform="c7200"): Node.__init__(self, server) log.info("router {} is being created".format(platform)) + self._defaults = {} self._ports = [] self._router_id = None @@ -826,8 +827,10 @@ def dump(self): for port in self._ports: ports.append(port.dump()) - #TODO: handle the image path - # router["properties"]["image"] + image_path = router["properties"]["image"] + if os.path.commonprefix([image_path, self._module.imageFilesDir()]) == self._module.imageFilesDir(): + # save only the image name if it is stored the images directory + router["properties"]["image"] = os.path.basename(image_path) return router @@ -843,6 +846,11 @@ def load(self, node_info): settings = node_info["properties"] name = settings.pop("name") image = settings.pop("image") + if not os.path.isfile(image): + # add the default images directory path to the image name to see if it exists + updated_path = os.path.join(self._module.imageFilesDir(), image) + if os.path.isfile(updated_path): + image = updated_path ram = settings.get("ram", PLATFORMS_DEFAULT_RAM[self._settings["platform"]]) self.updated_signal.connect(self._updatePortSettings) # block the created signal, it will be triggered when loading is completely done diff --git a/gns3/modules/dynamips/pages/ios_router_preferences_page.py b/gns3/modules/dynamips/pages/ios_router_preferences_page.py index ca4eb01df..41a9acc2e 100644 --- a/gns3/modules/dynamips/pages/ios_router_preferences_page.py +++ b/gns3/modules/dynamips/pages/ios_router_preferences_page.py @@ -23,11 +23,10 @@ import sys import re import pkg_resources +import shutil from gns3.qt import QtGui from gns3.servers import Servers from gns3.main_window import MainWindow -from gns3.utils.progress_dialog import ProgressDialog -from gns3.utils.process_files_thread import ProcessFilesThread from ..settings import PLATFORMS_DEFAULT_RAM, CHASSIS from .. import Dynamips from ..ui.ios_router_preferences_page_ui import Ui_IOSRouterPreferencesPageWidget @@ -243,7 +242,7 @@ def _iosImageBrowserSlot(self): except FileExistsError: pass except OSError as e: - QtGui.QMessageBox.critical(self, "Images directory", "Could not create the images directory {}: {}".format(destination_directory, str(e))) + QtGui.QMessageBox.critical(self, "IOS images directory", "Could not create the IOS images directory {}: {}".format(destination_directory, str(e))) return if os.path.dirname(path) != destination_directory: @@ -256,15 +255,11 @@ def _iosImageBrowserSlot(self): path = symlink_path except (OSError, NotImplementedError): # if unsuccessful, then copy the IOS image itself - self._thread = ProcessFilesThread(path, new_destination_path) - progress_dialog = ProgressDialog(self._thread, "IOS image", "Copying the IOS image...", "Cancel", parent=self) - progress_dialog.show() - progress_dialog.exec_() - errors = progress_dialog.errors() - if errors: - QtGui.QMessageBox.critical(self, "IOS image", "Could not copy the IOS image {}".format("".join(errors))) - else: + try: + shutil.copyfile(path, new_destination_path) path = new_destination_path + except OSError: + pass self.uiIOSPathLineEdit.clear() self.uiIOSPathLineEdit.setText(path) diff --git a/gns3/modules/iou/__init__.py b/gns3/modules/iou/__init__.py index bdcb861e4..ec3078c58 100644 --- a/gns3/modules/iou/__init__.py +++ b/gns3/modules/iou/__init__.py @@ -45,6 +45,7 @@ def __init__(self): self._iou_images = {} self._servers = [] self._working_dir = "" + self._images_dir = "" # load the settings self._loadSettings() @@ -142,6 +143,24 @@ def setProjectFilesDir(self, path): if server.connected(): self._sendSettings(server) + def setImageFilesDir(self, path): + """ + Sets the image files directory path this module. + + :param path: path to the local image files directory + """ + + self._images_dir = path + + def imageFilesDir(self): + """ + Returns the files directory path this module. + + :returns: path to the local image files directory + """ + + return self._images_dir + def addServer(self, server): """ Adds a server to be used by this module. diff --git a/gns3/modules/iou/iou_device.py b/gns3/modules/iou/iou_device.py index f7656efc5..67bca925f 100644 --- a/gns3/modules/iou/iou_device.py +++ b/gns3/modules/iou/iou_device.py @@ -247,7 +247,7 @@ def _updateCallback(self, result, error=False): if name in self._settings and self._settings[name] != value: log.info("{}: updating {} from '{}' to '{}'".format(self.name(), name, self._settings[name], value)) updated = True - if (name == "ethernet_adapters" or name == "serial_adapters"): + if name == "ethernet_adapters" or name == "serial_adapters": nb_adapters_changed = True self._settings[name] = value @@ -505,8 +505,10 @@ def dump(self): for port in self._ports: ports.append(port.dump()) - #TODO: handle the image path - # router["properties"]["image"] + image_path = router["properties"]["path"] + if os.path.commonprefix([image_path, self._module.imageFilesDir()]) == self._module.imageFilesDir(): + # save only the image name if it is stored the images directory + router["properties"]["path"] = os.path.basename(image_path) return router @@ -522,6 +524,11 @@ def load(self, node_info): settings = node_info["properties"] name = settings.pop("name") path = settings.pop("path") + if not os.path.isfile(path): + # add the default images directory path to the image name to see if it exists + updated_path = os.path.join(self._module.imageFilesDir(), path) + if os.path.isfile(updated_path): + path = updated_path console = settings.pop("console") self.updated_signal.connect(self._updatePortSettings) # block the created signal, it will be triggered when loading is completely done diff --git a/gns3/modules/iou/pages/iou_device_preferences_page.py b/gns3/modules/iou/pages/iou_device_preferences_page.py index 740a6a272..663aced34 100644 --- a/gns3/modules/iou/pages/iou_device_preferences_page.py +++ b/gns3/modules/iou/pages/iou_device_preferences_page.py @@ -22,8 +22,10 @@ import os import sys import pkg_resources +import shutil from gns3.qt import QtGui from gns3.servers import Servers +from gns3.main_window import MainWindow from .. import IOU from ..ui.iou_device_preferences_page_ui import Ui_IOUDevicePreferencesPageWidget @@ -37,8 +39,8 @@ def __init__(self): QtGui.QWidget.__init__(self) self.setupUi(self) + self._main_window = MainWindow.instance() self._iou_images = {} - self.uiSaveIOUImagePushButton.clicked.connect(self._iouImageSaveSlot) self.uiDeleteIOUImagePushButton.clicked.connect(self._iouImageDeleteSlot) self.uiIOUImagesTreeWidget.itemClicked.connect(self._iouImageClickedSlot) @@ -165,10 +167,10 @@ def _iouImageBrowserSlot(self): Slot to open a file browser and select an IOU image. """ - #TODO: current directory for IOU image + filter? + destination_directory = os.path.join(self._main_window.settings()["images_path"], "IOU") path, _ = QtGui.QFileDialog.getOpenFileNameAndFilter(self, "Select an IOU image", - ".", + destination_directory, "All files (*.*);;IOU image (*.bin *.image)", "IOU image (*.bin *.image)") @@ -197,6 +199,30 @@ def _iouImageBrowserSlot(self): QtGui.QMessageBox.critical(self, "IOU image", "{} is not executable".format(path)) return + try: + os.makedirs(destination_directory) + except FileExistsError: + pass + except OSError as e: + QtGui.QMessageBox.critical(self, "IOU images directory", "Could not create the IOU images directory {}: {}".format(destination_directory, str(e))) + return + + if os.path.dirname(path) != destination_directory: + # the IOU image is not in the default images directory + new_destination_path = os.path.join(destination_directory, os.path.basename(path)) + try: + # try to create a symbolic link to it + symlink_path = new_destination_path + os.symlink(path, symlink_path) + path = symlink_path + except (OSError, NotImplementedError): + # if unsuccessful, then copy the IOU image itself + try: + shutil.copyfile(path, new_destination_path) + path = new_destination_path + except OSError: + pass + self.uiIOUPathLineEdit.clear() self.uiIOUPathLineEdit.setText(path) diff --git a/gns3/modules/module.py b/gns3/modules/module.py index 66a300c9b..90def7954 100644 --- a/gns3/modules/module.py +++ b/gns3/modules/module.py @@ -36,6 +36,24 @@ def __init__(self): super(Module, self).__init__() + def setProjectFilesDir(self, path): + """ + Sets the project files directory path this module. + + :param path: path to the local project files directory + """ + + raise NotImplementedError() + + def setImageFilesDir(self, path): + """ + Sets the image files directory path this module. + + :param path: path to the local image files directory + """ + + raise NotImplementedError() + @staticmethod def nodes(self): """ diff --git a/gns3/modules/vpcs/__init__.py b/gns3/modules/vpcs/__init__.py index 6d9fc4c48..064b4893f 100644 --- a/gns3/modules/vpcs/__init__.py +++ b/gns3/modules/vpcs/__init__.py @@ -88,6 +88,15 @@ def setProjectFilesDir(self, path): if server.connected(): self._sendSettings(server) + def setImageFilesDir(self, path): + """ + Sets the image files directory path this module. + + :param path: path to the local image files directory + """ + + pass # not used by this module + def addServer(self, server): """ Adds a server to be used by this module.