Example #1
0
    def _startListenNotifications(self):
        if not Controller.instance().connected():
            return

        # Qt websocket before Qt 5.6 doesn't support auth
        if parse_version(QtCore.QT_VERSION_STR) < parse_version(
                "5.6.0") or parse_version(
                    QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
            path = "/projects/{project_id}/notifications".format(
                project_id=self._id)
            self._notification_stream = Controller.instance().createHTTPQuery(
                "GET",
                path,
                self._endListenNotificationCallback,
                downloadProgressCallback=self._event_received,
                networkManager=self._notification_network_manager,
                timeout=None,
                showProgress=False,
                ignoreErrors=True)

        else:
            path = "/projects/{project_id}/notifications/ws".format(
                project_id=self._id)
            self._notification_stream = Controller.instance().connectWebSocket(
                path)
            self._notification_stream.textMessageReceived.connect(
                self._websocket_event_received)
            self._notification_stream.error.connect(self._websocket_error)
Example #2
0
    def _pypiReplySlot(self):
        network_reply = self.sender()
        if network_reply.error() != QtNetwork.QNetworkReply.NoError:
            if not self._silent:
                QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
            return
        try:
            body = bytes(network_reply.readAll()).decode("utf-8")
            body = json.loads(body)
        except (UnicodeEncodeError, ValueError) as e:
            log.warning("Invalid answer from the PyPi server")

        last_version = self._getLastMinorVersionFromPyPiReply(body)
        if parse_version(last_version) > parse_version(version.__version__):
            reply = QtWidgets.QMessageBox.question(self._parent,
                                                   "Check For Update",
                                                   "Newer GNS3 version {} is available, do you want to to download it in background and install it at next application launch?".format(last_version),
                                                   QtWidgets.QMessageBox.Yes,
                                                   QtWidgets.QMessageBox.No)
            if reply == QtWidgets.QMessageBox.Yes:
                try:
                    self.downloadUpdates(last_version)
                except OSError as e:
                    QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Cannot download update: {}".format(e))
        else:
            self._get('http://update.gns3.net', self._gns3UpdateReplySlot)
Example #3
0
 def _gns3UpdateReplySlot(self):
     network_reply = self.sender()
     if network_reply.error() != QtNetwork.QNetworkReply.NoError:
         if not self._silent:
             QtWidgets.QMessageBox.critical(
                 self._parent, "Check For Update",
                 "Cannot check for update: {}".format(
                     network_reply.errorString()))
         return
     try:
         latest_release = bytes(
             network_reply.readAll()).decode("utf-8").rstrip()
     except UnicodeDecodeError:
         log.warning("Invalid answer from the update server")
         return
     if re.match(r"^[a-z0-9\.]+$", latest_release) is None:
         log.warning("Invalid answer from the update server")
         return
     if parse_version(version.__version__) < parse_version(latest_release):
         reply = QtWidgets.QMessageBox.question(
             self._parent, "Check For Update",
             "Newer GNS3 version {} is available, do you want to visit our website to download it?"
             .format(latest_release), QtWidgets.QMessageBox.Yes,
             QtWidgets.QMessageBox.No)
         if reply == QtWidgets.QMessageBox.Yes:
             QtGui.QDesktopServices.openUrl(
                 QtCore.QUrl("http://www.gns3.net/download/"))
     elif not self._silent:
         QtWidgets.QMessageBox.information(self._parent, "Check For Update",
                                           "GNS3 is up-to-date!")
Example #4
0
    def __init__(self, argv, hdpi=True):

        self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
        # both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
        if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
            # only available starting Qt version 5.6
            if hdpi:
                if sys.platform.startswith("linux"):
                    log.warning("HDPI mode is enabled. HDPI support on Linux is not fully stable and GNS3 may crash depending of your version of Linux. To disabled HDPI mode please edit ~/.config/GNS3/gns3_gui.conf and set 'hdpi' to 'false'")
                self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
                self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
            else:
                log.info("HDPI mode is disabled")
                self.setAttribute(QtCore.Qt.AA_DisableHighDpiScaling)

        super().__init__(argv)

        # this info is necessary for QSettings
        self.setOrganizationName("GNS3")
        self.setOrganizationDomain("gns3.net")
        self.setApplicationName("GNS3")
        self.setApplicationVersion(__version__)

        # File path if we have received the path to
        # a file on system via an OSX event
        self.open_file_at_startup = None
Example #5
0
    def _startListenNotifications(self):
        if not self.connected():
            return

        # Due to bug in Qt on some version we need a dedicated network manager
        self._notification_network_manager = QtNetwork.QNetworkAccessManager()
        self._notification_stream = None

        # Qt websocket before Qt 5.6 doesn't support auth
        if parse_version(QtCore.QT_VERSION_STR) < parse_version(
                "5.6.0") or parse_version(
                    QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
            self._notification_stream = Controller.instance().createHTTPQuery(
                "GET",
                "/notifications",
                self._endListenNotificationCallback,
                downloadProgressCallback=self._event_received,
                networkManager=self._notification_network_manager,
                timeout=None,
                showProgress=False,
                ignoreErrors=True)

        else:
            self._notification_stream = self._http_client.connectWebSocket(
                self._websocket, "/notifications/ws")
            self._notification_stream.textMessageReceived.connect(
                self._websocket_event_received)
            self._notification_stream.error.connect(self._websocket_error)
Example #6
0
 def _gns3UpdateReplySlot(self):
     network_reply = self.sender()
     if network_reply is None:
         return
     if network_reply.error() != QtNetwork.QNetworkReply.NoError:
         if not self._silent:
             QtWidgets.QMessageBox.critical(self._parent, "Check For Update", "Cannot check for update: {}".format(network_reply.errorString()))
         return
     try:
         latest_release = bytes(network_reply.readAll()).decode("utf-8").rstrip()
     except UnicodeDecodeError:
         log.debug("Invalid answer from the update server")
         return
     if re.match(r"^[a-z0-9\.]+$", latest_release) is None:
         log.debug("Invalid answer from the update server")
         return
     if parse_version(version.__version__) < parse_version(latest_release):
         reply = QtWidgets.QMessageBox.question(self._parent,
                                                "Check For Update",
                                                "Newer GNS3 version {} is available, do you want to visit our website to download it?".format(latest_release),
                                                QtWidgets.QMessageBox.Yes,
                                                QtWidgets.QMessageBox.No)
         if reply == QtWidgets.QMessageBox.Yes:
             QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://www.gns3.com/software"))
     elif not self._silent:
         QtWidgets.QMessageBox.information(self._parent, "Check For Update", "GNS3 is up-to-date!")
Example #7
0
    def _pypiReplySlot(self):
        network_reply = self.sender()
        if network_reply.error() != QtNetwork.QNetworkReply.NoError:
            if not self._silent:
                QtWidgets.QMessageBox.critical(
                    self._parent, "Check For Update",
                    "Cannot check for update: {}".format(
                        network_reply.errorString()))
            return
        try:
            body = bytes(network_reply.readAll()).decode("utf-8")
            body = json.loads(body)
        except (UnicodeEncodeError, ValueError) as e:
            log.warning("Invalid answer from the PyPi server")

        last_version = self._getLastMinorVersionFromPyPiReply(body)
        if parse_version(last_version) > parse_version(version.__version__):
            reply = QtWidgets.QMessageBox.question(
                self._parent, "Check For Update",
                "Newer GNS3 version {} is available, do you want to to download it in background and install it at next application launch?"
                .format(last_version), QtWidgets.QMessageBox.Yes,
                QtWidgets.QMessageBox.No)
            if reply == QtWidgets.QMessageBox.Yes:
                try:
                    self.downloadUpdates(last_version)
                except OSError as e:
                    QtWidgets.QMessageBox.critical(
                        self._parent, "Check For Update",
                        "Cannot download update: {}".format(e))
        else:
            self._get('http://update.gns3.net', self._gns3UpdateReplySlot)
Example #8
0
    def __init__(self, argv, hdpi=True):

        self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
        # both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
        if parse_version(QtCore.QT_VERSION_STR) >= parse_version(
                "5.6") and parse_version(
                    QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
            # only available starting Qt version 5.6
            if hdpi:
                if sys.platform.startswith("linux"):
                    log.warning(
                        "HDPI mode is enabled. HDPI support on Linux is not fully stable and GNS3 may crash depending of your version of Linux. To disabled HDPI mode please edit ~/.config/GNS3/gns3_gui.conf and set 'hdpi' to 'false'"
                    )
                self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
                self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
            else:
                log.info("HDPI mode is disabled")
                self.setAttribute(QtCore.Qt.AA_DisableHighDpiScaling)

        super().__init__(argv)

        # this info is necessary for QSettings
        self.setOrganizationName("GNS3")
        self.setOrganizationDomain("gns3.net")
        self.setApplicationName("GNS3")
        self.setApplicationVersion(__version__)

        # File path if we have received the path to
        # a file on system via an OSX event
        self.open_file_at_startup = None
Example #9
0
    def _check_vpcs_version(self, working_dir):
        """
        Checks if the VPCS executable version is >= 0.5b1.
        """

        try:
            output = subprocess.check_output([self._settings["vpcs_path"], "-v"], cwd=working_dir)
            match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output.decode("utf-8"))
            if match:
                version = match.group(1)
                if parse_version(version) < parse_version("0.5b1"):
                    raise ModuleError("VPCS executable version must be >= 0.5b1")
            else:
                raise ModuleError("Could not determine the VPCS version for {}".format(self._settings["vpcs_path"]))
        except (OSError, subprocess.SubprocessError) as e:
            raise ModuleError("Error while looking for the VPCS version: {}".format(e))
Example #10
0
    def _check_vpcs_version(self, working_dir):
        """
        Checks if the VPCS executable version is >= 0.5b1.
        """

        try:
            output = subprocess.check_output([self._settings["vpcs_path"], "-v"], cwd=working_dir)
            match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output.decode("utf-8"))
            if match:
                version = match.group(1)
                if parse_version(version) < parse_version("0.5b1"):
                    raise ModuleError("VPCS executable version must be >= 0.5b1")
            else:
                raise ModuleError("Could not determine the VPCS version for {}".format(self._settings["vpcs_path"]))
        except (OSError, subprocess.SubprocessError) as e:
            raise ModuleError("Error while looking for the VPCS version: {}".format(e))
Example #11
0
    def _getLastMinorVersionFromPyPiReply(self, body):
        """
        Return the most recent minor version for this release
        from a PyPi answer.

        If no valid version is found it's return the current.
        """
        current_version = parse_version(version.__version__)
        for release in sorted(body['releases'].keys(), reverse=True):
            release_version = parse_version(release)
            if release_version[-1:][0] == "final":
                if self.isDevVersion():
                    continue
            else:
                if not self.isDevVersion():
                    continue
            if release_version > current_version and release_version[:2] == current_version[:2]:
                return release
        return version.__version__
Example #12
0
    def _startListenNotifications(self):
        if not Controller.instance().connected():
            return

        # Qt websocket before Qt 5.6 doesn't support auth
        if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
            path = "/projects/{project_id}/notifications".format(project_id=self._id)
            self._notification_stream = Controller.instance().createHTTPQuery("GET", path, self._endListenNotificationCallback,
                                                                              downloadProgressCallback=self._event_received,
                                                                              networkManager=self._notification_network_manager,
                                                                              timeout=None,
                                                                              showProgress=False,
                                                                              ignoreErrors=True)

        else:
            path = "/projects/{project_id}/notifications/ws".format(project_id=self._id)
            self._notification_stream = Controller.instance().connectWebSocket(path)
            self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
            self._notification_stream.error.connect(self._websocket_error)
Example #13
0
    def _get(self, url, finished_slot, user_attribute=None):
        """
        HTTP get

        :param url: Url to download
        :param user_attribute: Param to pass to the finished slot
        :returns: QNetworkReply
        """
        if self._network_manager is None:
            self._network_manager = QtNetwork.QNetworkAccessManager()
        request = QtNetwork.QNetworkRequest(QtCore.QUrl(url))
        request.setRawHeader(b'User-Agent', b'GNS3 Check For Update')
        request.setAttribute(QtNetwork.QNetworkRequest.User, user_attribute)
        if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6.0") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6.0"):
            # follow redirects only supported starting with Qt 5.6.0
            request.setAttribute(QtNetwork.QNetworkRequest.FollowRedirectsAttribute, True)
        reply = self._network_manager.get(request)
        reply.finished.connect(finished_slot)
        log.debug('Download %s', url)
        return reply
Example #14
0
    def _getLastMinorVersionFromPyPiReply(self, body):
        """
        Return the most recent minor version for this release
        from a PyPi answer.

        If no valid version is found it's return the current.
        """

        current_version = parse_version(version.__version__)
        for release in sorted(body['releases'].keys(), reverse=True):
            release_version = parse_version(release)
            if release_version[-1:][0] == "final":
                if self.isDevVersion():
                    continue
            else:
                if not self.isDevVersion():
                    continue
            if release_version > current_version and release_version[:2] == current_version[:2]:
                return release
        return version.__version__
Example #15
0
    def __init__(self, argv):

        self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
        # both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
        if parse_version(QtCore.QT_VERSION_STR) >= parse_version("5.6") and parse_version(QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
            # only available starting Qt version 5.6
            self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
            self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)

        super().__init__(argv)

        # this info is necessary for QSettings
        self.setOrganizationName("GNS3")
        self.setOrganizationDomain("gns3.net")
        self.setApplicationName("GNS3")
        self.setApplicationVersion(__version__)

        # File path if we have received the path to
        # a file on system via an OSX event
        self.open_file_at_startup = None
Example #16
0
    def _startListenNotifications(self):
        if not self.connected():
            return

        # Due to bug in Qt on some version we need a dedicated network manager
        self._notification_network_manager = QtNetwork.QNetworkAccessManager()
        self._notification_stream = None

        # Qt websocket before Qt 5.6 doesn't support auth
        if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.6.0") or parse_version(QtCore.PYQT_VERSION_STR) < parse_version("5.6.0"):
            self._notification_stream = Controller.instance().createHTTPQuery("GET", "/notifications", self._endListenNotificationCallback,
                                                                              downloadProgressCallback=self._event_received,
                                                                              networkManager=self._notification_network_manager,
                                                                              timeout=None,
                                                                              showProgress=False,
                                                                              ignoreErrors=True)

        else:
            self._notification_stream = self._http_client.connectWebSocket(self._controller_websocket, "/notifications/ws")
            self._notification_stream.textMessageReceived.connect(self._websocket_event_received)
            self._notification_stream.error.connect(self._websocket_error)
Example #17
0
    def __init__(self, argv):

        self.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
        # both Qt and PyQt must be version >= 5.6 in order to enable high DPI scaling
        if parse_version(QtCore.QT_VERSION_STR) >= parse_version(
                "5.6") and parse_version(
                    QtCore.PYQT_VERSION_STR) >= parse_version("5.6"):
            # only available starting Qt version 5.6
            self.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
            self.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)

        super().__init__(argv)

        # this info is necessary for QSettings
        self.setOrganizationName("GNS3")
        self.setOrganizationDomain("gns3.net")
        self.setApplicationName("GNS3")
        self.setApplicationVersion(__version__)

        # File path if we have received the path to
        # a file on system via an OSX event
        self.open_file_at_startup = None
Example #18
0
def main():
    """
    Entry point for GNS3 GUI.
    """

    # Sometimes (for example at first launch) the OSX app service launcher add
    # an extra argument starting with -psn_. We filter it
    if sys.platform.startswith("darwin"):
        sys.argv = [a for a in sys.argv if not a.startswith("-psn_")]

    parser = argparse.ArgumentParser()
    parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
    parser.add_argument("--version", help="show the version", action="version", version=__version__)
    parser.add_argument("--debug", help="print out debug messages", action="store_true", default=False)
    parser.add_argument("--config", help="Configuration file")
    options = parser.parse_args()
    exception_file_path = "exceptions.log"

    if options.config:
        LocalConfig.instance(config_file=options.config)
    else:
        LocalConfig.instance()

    if options.project:
        options.project = os.path.abspath(options.project)

    if hasattr(sys, "frozen"):
        # We add to the path where the OS search executable our binary location starting by GNS3
        # packaged binary
        frozen_dir = os.path.dirname(os.path.abspath(sys.executable))
        if sys.platform.startswith("darwin"):
            frozen_dirs = [
                frozen_dir,
                os.path.normpath(os.path.join(frozen_dir, '..', 'Resources'))
            ]
        elif sys.platform.startswith("win"):
            frozen_dirs = [
                frozen_dir,
                os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
                os.path.normpath(os.path.join(frozen_dir, 'vpcs'))
            ]

        os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")

        if options.project:
            os.chdir(frozen_dir)

    def exceptionHook(exception, value, tb):

        if exception == KeyboardInterrupt:
            sys.exit(0)

        lines = traceback.format_exception(exception, value, tb)
        print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
        print("\nPLEASE REPORT ON https://www.gns3.com\n")
        print("".join(lines))
        try:
            curdate = time.strftime("%d %b %Y %H:%M:%S")
            logfile = open(exception_file_path, "a", encoding="utf-8")
            logfile.write("=== GNS3 {} traceback on {} ===\n".format(__version__, curdate))
            logfile.write("".join(lines))
            logfile.close()
        except OSError as e:
            print("Could not save traceback to {}: {}".format(os.path.normpath(exception_file_path), e))

        if not sys.stdout.isatty():
            # if stdout is not a tty (redirected to the console view),
            # then print the exception on stderr too.
            print("".join(lines), file=sys.stderr)

        if exception is MemoryError:
            print("YOUR SYSTEM IS OUT OF MEMORY!")
        else:
            CrashReport.instance().captureException(exception, value, tb)

    # catch exceptions to write them in a file
    sys.excepthook = exceptionHook

    current_year = datetime.date.today().year
    print("GNS3 GUI version {}".format(__version__))
    print("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))

    # we only support Python 3 version >= 3.4
    if sys.version_info < (3, 4):
        raise SystemExit("Python 3.4 or higher is required")

    if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.0.0"):
        raise SystemExit("Requirement is PyQt5 version 5.0.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))

    if parse_version(psutil.__version__) < parse_version("2.2.1"):
        raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))

    # check for the correct locale
    # (UNIX/Linux only)
    locale_check()

    try:
        os.getcwd()
    except FileNotFoundError:
        log.critical("the current working directory doesn't exist")
        return

    # always use the INI format on Windows and OSX (because we don't like the registry and plist files)
    if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
        QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)

    if sys.platform.startswith('win') and hasattr(sys, "frozen"):
        try:
            import win32console
            import win32con
            import win32gui
        except ImportError:
            raise SystemExit("Python for Windows extensions must be installed.")

        if not options.debug:
            try:
                # hide the console
                console_window = win32console.GetConsoleWindow()
                win32gui.ShowWindow(console_window, win32con.SW_HIDE)
            except win32console.error as e:
                print("warning: could not allocate console: {}".format(e))

    global app
    app = Application(sys.argv)

    # save client logging info to a file
    logfile = os.path.join(LocalConfig.configDirectory(), "gns3_gui.log")

    # on debug enable logging to stdout
    if options.debug:
        root_logger = init_logger(logging.DEBUG, logfile)
    else:
        root_logger = init_logger(logging.INFO, logfile)

    # update the exception file path to have it in the same directory as the settings file.
    exception_file_path = os.path.join(LocalConfig.configDirectory(), exception_file_path)

    global mainwindow
    mainwindow = MainWindow()

    # On OSX we can receive the file to open from a system event
    # loadPath is smart and will load only if a path is present
    mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(app.open_file_at_startup))
    mainwindow.ready_signal.connect(lambda: mainwindow.loadPath(options.project))
    app.file_open_signal.connect(lambda path: mainwindow.loadPath(path))

    # Manage Ctrl + C or kill command
    def sigint_handler(*args):
        log.info("Signal received exiting the application")
        mainwindow.setSoftExit(False)
        app.closeAllWindows()
    orig_sigint = signal.signal(signal.SIGINT, sigint_handler)
    orig_sigterm = signal.signal(signal.SIGTERM, sigint_handler)

    mainwindow.show()

    exit_code = app.exec_()

    signal.signal(signal.SIGINT, orig_sigint)
    signal.signal(signal.SIGTERM, orig_sigterm)

    delattr(MainWindow, "_instance")

    # We force deleting the app object otherwise it's segfault on Fedora
    del app
    # We force a full garbage collect before exit
    # for unknow reason otherwise Qt Segfault on OSX in some
    # conditions
    import gc
    gc.collect()

    sys.exit(exit_code)
Example #19
0
def test_parse_version():
    assert parse_version('1') == ('000001', '00000', '000000', 'final')
    assert parse_version('1.3') == ('000001', '000003', '000000', 'final')
    assert parse_version('1.3.dev3') == ('000001', '000003', '000000', 'dev',
                                         '000003')
    assert parse_version('1.3a1') == ('000001', '000003', '000000', 'a',
                                      '000001')
    assert parse_version('1.3rc1') == ('000001', '000003', '000000', 'c',
                                       '000001')

    assert parse_version('1.2.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.3alpha1')
    assert parse_version('1.3') > parse_version('1.3rc1')
    assert parse_version('1.3rc1') > parse_version('1.3alpha3')
    assert parse_version('1.3dev1') > parse_version('1.3rc1')
    assert parse_version('1.2.3') > parse_version('1.2')
Example #20
0
def main():
    """
    Entry point for GNS3 GUI.
    """

    # Get Python tracebacks explicitly, on a fault like segfault
    faulthandler.enable()

    # Sometimes (for example at first launch) the OSX app service launcher add
    # an extra argument starting with -psn_. We filter it
    if sys.platform.startswith("darwin"):
        sys.argv = [a for a in sys.argv if not a.startswith("-psn_")]
        if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.15.2"):
            # Fixes issue on macOS Big Sur: https://github.com/GNS3/gns3-gui/issues/3037
            os.environ["QT_MAC_WANTS_LAYER"] = "1"

    parser = argparse.ArgumentParser()
    parser.add_argument("project", help="load a GNS3 project (.gns3)", metavar="path", nargs="?")
    parser.add_argument("--version", help="show the version", action="version", version=__version__)
    parser.add_argument("--debug", help="print out debug messages", action="store_true", default=False)
    parser.add_argument("-q", "--quiet", action="store_true", help="do not show logs on stdout")
    parser.add_argument("--config", help="Configuration file")
    parser.add_argument("--profile", help="Settings profile (blank will use default settings files)")
    options = parser.parse_args()
    exception_file_path = "exceptions.log"

    if options.project:
        options.project = os.path.abspath(options.project)

    if hasattr(sys, "frozen"):
        # We add to the path where the OS search executable our binary location starting by GNS3
        # packaged binary
        frozen_dir = os.path.dirname(os.path.abspath(sys.executable))
        if sys.platform.startswith("darwin"):
            frozen_dirs = [frozen_dir]
        elif sys.platform.startswith("win"):
            frozen_dirs = [
                frozen_dir,
                os.path.normpath(os.path.join(frozen_dir, 'dynamips')),
                os.path.normpath(os.path.join(frozen_dir, 'vpcs')),
                os.path.normpath(os.path.join(frozen_dir, 'traceng'))
            ]

        os.environ["PATH"] = os.pathsep.join(frozen_dirs) + os.pathsep + os.environ.get("PATH", "")

        if options.project:
            os.chdir(frozen_dir)

    def exceptionHook(exception, value, tb):

        if exception == KeyboardInterrupt:
            sys.exit(0)

        lines = traceback.format_exception(exception, value, tb)
        print("****** Exception detected, traceback information saved in {} ******".format(exception_file_path))
        print("\nPLEASE REPORT ON https://www.gns3.com\n")
        print("".join(lines))
        try:
            curdate = time.strftime("%d %b %Y %H:%M:%S")
            logfile = open(exception_file_path, "a", encoding="utf-8")
            logfile.write("=== GNS3 {} traceback on {} ===\n".format(__version__, curdate))
            logfile.write("".join(lines))
            logfile.close()
        except OSError as e:
            print("Could not save traceback to {}: {}".format(os.path.normpath(exception_file_path), e))

        if not sys.stdout.isatty():
            # if stdout is not a tty (redirected to the console view),
            # then print the exception on stderr too.
            print("".join(lines), file=sys.stderr)

        if exception is MemoryError:
            print("YOUR SYSTEM IS OUT OF MEMORY!")
        else:
            CrashReport.instance().captureException(exception, value, tb)

    # catch exceptions to write them in a file
    sys.excepthook = exceptionHook

    # we only support Python 3 version >= 3.4
    if sys.version_info < (3, 4):
        raise SystemExit("Python 3.4 or higher is required")

    if parse_version(QtCore.QT_VERSION_STR) < parse_version("5.5.0"):
        raise SystemExit("Requirement is PyQt5 version 5.5.0 or higher, got version {}".format(QtCore.QT_VERSION_STR))

    if parse_version(psutil.__version__) < parse_version("2.2.1"):
        raise SystemExit("Requirement is psutil version 2.2.1 or higher, got version {}".format(psutil.__version__))

    # check for the correct locale
    # (UNIX/Linux only)
    locale_check()

    try:
        os.getcwd()
    except FileNotFoundError:
        log.critical("the current working directory doesn't exist")
        return

    # always use the INI format on Windows and OSX (because we don't like the registry and plist files)
    if sys.platform.startswith('win') or sys.platform.startswith('darwin'):
        QtCore.QSettings.setDefaultFormat(QtCore.QSettings.IniFormat)

    if sys.platform.startswith('win') and hasattr(sys, "frozen"):
        try:
            import win32console
            import win32con
            import win32gui
        except ImportError:
            raise SystemExit("Python for Windows extensions must be installed.")

        if not options.debug:
            try:
                # hide the console
                console_window = win32console.GetConsoleWindow()
                win32gui.ShowWindow(console_window, win32con.SW_HIDE)
            except win32console.error as e:
                print("warning: could not allocate console: {}".format(e))

    local_config = LocalConfig.instance()

    global app
    app = Application(sys.argv, hdpi=local_config.hdpi())

    if local_config.multiProfiles() and not options.profile:
        profile_select = ProfileSelectDialog()
        profile_select.show()
        if profile_select.exec_():
            options.profile = profile_select.profile()
        else:
            sys.exit(0)

    # Init the config
    if options.config:
        local_config.setConfigFilePath(options.config)
    elif options.profile:
        local_config.setProfile(options.profile)

    # save client logging info to a file
    logfile = os.path.join(LocalConfig.instance().configDirectory(), "gns3_gui.log")

    # on debug enable logging to stdout
    if options.debug:
        init_logger(logging.DEBUG, logfile)
    elif options.quiet:
        init_logger(logging.ERROR, logfile)
    else:
        init_logger(logging.INFO, logfile)

    current_year = datetime.date.today().year
    log.info("GNS3 GUI version {}".format(__version__))
    log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year))
    log.info("Application started with {}".format(" ".join(sys.argv)))

    # update the exception file path to have it in the same directory as the settings file.
    exception_file_path = os.path.join(LocalConfig.instance().configDirectory(), exception_file_path)

    # We disallow to run GNS3 from outside the /Applications folder to avoid
    # issue when people run GNS3 from the .dmg
    if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
        if not os.path.realpath(sys.executable).startswith("/Applications"):
            error_message = "GNS3.app must be moved to the '/Applications' folder before it can be used"
            QtWidgets.QMessageBox.critical(False, "Loading error", error_message)
            QtCore.QTimer.singleShot(0, app.quit)
            app.exec_()
            sys.exit(1)

    global mainwindow
    startup_file = app.open_file_at_startup
    if not startup_file:
        startup_file = options.project

    mainwindow = MainWindow(open_file=startup_file)

    # On OSX we can receive the file to open from a system event
    # loadPath is smart and will load only if a path is present
    app.file_open_signal.connect(lambda path: mainwindow.loadPath(path))

    # Manage Ctrl + C or kill command
    def sigint_handler(*args):
        log.info("Signal received exiting the application")
        app.closeAllWindows()
    orig_sigint = signal.signal(signal.SIGINT, sigint_handler)
    orig_sigterm = signal.signal(signal.SIGTERM, sigint_handler)

    mainwindow.show()

    exit_code = app.exec_()
    signal.signal(signal.SIGINT, orig_sigint)
    signal.signal(signal.SIGTERM, orig_sigterm)

    delattr(MainWindow, "_instance")

    # We force deleting the app object otherwise it's segfault on Fedora
    del app
    # We force a full garbage collect before exit
    # for unknown reason otherwise Qt Segfault on OSX in some
    # conditions
    import gc
    gc.collect()

    sys.exit(exit_code)
Example #21
0
def test_parse_version():
    assert parse_version('1') == ('000001', '00000', '000000', 'final')
    assert parse_version('1.3') == ('000001', '000003', '000000', 'final')
    assert parse_version('1.3.dev3') == ('000001', '000003', '000000', 'dev', '000003')
    assert parse_version('1.3a1') == ('000001', '000003', '000000', 'a', '000001')
    assert parse_version('1.3rc1') == ('000001', '000003', '000000', 'c', '000001')

    assert parse_version('1.2.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.3alpha1')
    assert parse_version('1.3') > parse_version('1.3rc1')
    assert parse_version('1.3rc1') > parse_version('1.3alpha3')
    assert parse_version('1.3dev1') > parse_version('1.3rc1')
    assert parse_version('1.2.3') > parse_version('1.2')
Example #22
0
def test_parse_version():
    assert parse_version('1') == (1, 'final')
    assert parse_version('1.3') == (1, 3, 'final')
    assert parse_version('1.3.dev3') == (1, 3, 'dev', 3)
    assert parse_version('1.3a1') == (1, 3, 'a', 1)
    assert parse_version('1.3rc1') == (1, 3, 'c', 1)

    assert parse_version('1.2.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.3alpha1')
    assert parse_version('1.3') > parse_version('1.3rc1')
    assert parse_version('1.3rc1') > parse_version('1.3alpha3')
    assert parse_version('1.3dev1') > parse_version('1.3rc1')
Example #23
0
def test_parse_version():
    assert parse_version('1') == (1, 'final')
    assert parse_version('1.3') == (1, 3, 'final')
    assert parse_version('1.3.dev3') == (1, 3, 'dev', 3)
    assert parse_version('1.3a1') == (1, 3, 'a', 1)
    assert parse_version('1.3rc1') == (1, 3, 'c', 1)

    assert parse_version('1.2.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.2.2')
    assert parse_version('1.3') > parse_version('1.3alpha1')
    assert parse_version('1.3') > parse_version('1.3rc1')
    assert parse_version('1.3rc1') > parse_version('1.3alpha3')
    assert parse_version('1.3dev1') > parse_version('1.3rc1')