Skip to content

Commit

Permalink
Merge pull request #2312 from goanpeca/restart
Browse files Browse the repository at this point in the history
Add restart functionality to the application
  • Loading branch information
ccordoba12 committed Apr 21, 2015
2 parents 471d0c9 + aa66feb commit 3e619c4
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 2 deletions.
3 changes: 3 additions & 0 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@

options, args = parser.parse_args()

# Store variable to be used in self.restart (restart spyder instance)
os.environ['SPYDER_BOOTSTRAP_ARGS'] = str(sys.argv[1:])

assert options.gui in (None, 'pyqt5', 'pyqt', 'pyside'), \
"Invalid GUI toolkit option '%s'" % options.gui

Expand Down
1 change: 1 addition & 0 deletions spyderlib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def is_ubuntu():
'_/save current layout': "Shift+Alt+S",
'_/toggle default layout': "Shift+Alt+Home",
'_/layout preferences': "Shift+Alt+P",
'_/restart': "Shift+Alt+R",
'_/quit': "Ctrl+Q",
# -- In plugins/editor
'_/debug step over': "Ctrl+F10",
Expand Down
145 changes: 145 additions & 0 deletions spyderlib/restart_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright © 2015 The Spyder Development Team
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)

"""
Restart Spyder
A helper script that allows Spyder to restart from within the application.
"""

import ast
import os
import os.path as osp
import subprocess
import sys
import time


PY2 = sys.version[0] == '2'


def _is_pid_running_on_windows(pid):
"""Check if a process is running on windows systems based on the pid."""
pid = str(pid)

# Hide flashing command prompt
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

process = subprocess.Popen(r'tasklist /fi "PID eq {0}"'.format(pid),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
startupinfo=startupinfo)
stdoutdata, stderrdata = process.communicate()
stdoutdata = to_text_string(stdoutdata)
process.kill()
check = pid in stdoutdata

return check


def _is_pid_running_on_unix(pid):
"""Check if a process is running on unix systems based on the pid."""
try:
# On unix systems os.kill with a 0 as second argument only pokes the
# process (if it exists) and does not kill it
os.kill(pid, 0)
except OSError:
return False
else:
return True


def is_pid_running(pid):
"""Check if a process is running based on the pid."""
# Select the correct function depending on the OS
if os.name == 'nt':
return _is_pid_running_on_windows(pid)
else:
return _is_pid_running_on_unix(pid)


def to_text_string(obj, encoding=None):
"""Convert `obj` to (unicode) text string"""
if PY2:
# Python 2
if encoding is None:
return unicode(obj)
else:
return unicode(obj, encoding)
else:
# Python 3
if encoding is None:
return str(obj)
elif isinstance(obj, str):
# In case this function is not used properly, this could happen
return obj
else:
return str(obj, encoding)


def main():
# Note: Variables defined in spyderlib\spyder.py 'restart()' method
spyder_args = os.environ.pop('SPYDER_ARGS', None)
pid = os.environ.pop('SPYDER_PID', None)
is_bootstrap = os.environ.pop('SPYDER_IS_BOOTSTRAP', None)

# Get the spyder base folder based on this file
spyder_folder = osp.split(osp.dirname(osp.abspath(__file__)))[0]

if any([not spyder_args, not pid, not is_bootstrap]):
error = "This script can only be called from within a Spyder instance"
raise RuntimeError(error)

# Variables were stored as string literals in the environment, so to use
# them we need to parse them in a safe manner.
is_bootstrap = ast.literal_eval(is_bootstrap)
pid = int(pid)
args = ast.literal_eval(spyder_args)

# Enforce the --new-instance flag when running spyder
if '--new-instance' not in args:
if is_bootstrap and not '--' in args:
args = args + ['--', '--new-instance']
else:
args.append('--new-instance')

# Arrange arguments to be passed to the restarter subprocess
args = ' '.join(args)

# Get python excutable running this script
python = sys.executable

# Build the command
if is_bootstrap:
spyder = osp.join(spyder_folder, 'bootstrap.py')
else:
spyderlib = osp.join(spyder_folder, 'spyderlib')
spyder = osp.join(spyderlib, 'start_app.py')

command = '"{0}" "{1}" {2}'.format(python, spyder, args)

# Adjust the command and/or arguments to subprocess depending on the OS
shell = os.name != 'nt'

# Wait for original process to end before launching the new instance
while True:
if not is_pid_running(pid):
break
time.sleep(0.2) # Throttling control

env = os.environ.copy()
try:
subprocess.Popen(command, shell=shell, env=env)
except Exception as error:
print(command)
print(error)
time.sleep(15)


if __name__ == '__main__':
main()
65 changes: 63 additions & 2 deletions spyderlib/spyder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2009-2013 Pierre Raybaut
# Copyright © 2013-2015 The Spyder Development Team
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)

Expand Down Expand Up @@ -30,6 +31,7 @@
import re
import socket
import shutil
import subprocess
import sys
import threading

Expand Down Expand Up @@ -118,7 +120,7 @@
from spyderlib.baseconfig import (get_conf_path, get_module_data_path,
get_module_source_path, STDERR, DEBUG, DEV,
debug_print, TEST, SUBFOLDER, MAC_APP_NAME,
running_in_mac_app)
running_in_mac_app, get_module_path)
from spyderlib.config import CONF, EDIT_EXT, IMPORT_EXT, OPEN_FILES_PORT
from spyderlib.cli_options import get_options
from spyderlib import dependencies
Expand Down Expand Up @@ -853,10 +855,16 @@ def create_edit_action(text, tr_text, icon_name):
icon='exit.png', tip=_("Quit"),
triggered=self.console.quit)
self.register_shortcut(quit_action, "_", "Quit")
restart_action = create_action(self, _("&Restart"),
icon='restart.png',
tip=_("Restart"),
triggered=self.restart)
self.register_shortcut(restart_action, "_", "Restart")

self.file_menu_actions += [self.load_temp_session_action,
self.load_session_action,
self.save_session_action,
None, quit_action]
None, restart_action, quit_action]
self.set_splash("")

self.debug_print(" ..widgets")
Expand Down Expand Up @@ -2658,6 +2666,59 @@ def start_open_files_server(self):
self.sig_open_external_file.emit(fname)
req.sendall(b' ')

# ---- Quit and restart
def restart(self):
"""Quit and Restart Spyder application"""
# Get start path to use in restart script
spyder_start_directory = get_module_path('spyderlib')
restart_script = osp.join(spyder_start_directory, 'restart_app.py')

# Get any initial argument passed when spyder was started
# Note: Variables defined in bootstrap.py and spyderlib\start_app.py
env = os.environ.copy()
bootstrap_args = env.pop('SPYDER_BOOTSTRAP_ARGS', None)
spyder_args = env.pop('SPYDER_ARGS')

# Get current process and python running spyder
pid = os.getpid()
python = sys.executable

# Check if started with bootstrap.py
if bootstrap_args is not None:
spyder_args = bootstrap_args
is_bootstrap = True
else:
is_bootstrap = False

# Pass variables as environment variables (str) to restarter subprocess
env['SPYDER_ARGS'] = spyder_args
env['SPYDER_PID'] = str(pid)
env['SPYDER_IS_BOOTSTRAP'] = str(is_bootstrap)

# Build the command and popen arguments depending on the OS
if os.name == 'nt':
# Hide flashing command prompt
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
shell = False
else:
startupinfo = None
shell = True

command = '"{0}" "{1}"'
command = command.format(python, restart_script)

try:
if self.closing(True):
subprocess.Popen(command, shell=shell, env=env,
startupinfo=startupinfo)
self.console.quit()
except Exception as error:
# If there is an error with subprocess, Spyder should not quit and
# the error can be inspected in the internal console
print(error)
print(command)

# ---- Interactive Tours
def show_tour(self, index):
""" """
Expand Down
4 changes: 4 additions & 0 deletions spyderlib/start_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os.path as osp
import random
import socket
import sys
import time

# Local imports
Expand Down Expand Up @@ -63,6 +64,9 @@ def main():
# Parse command line options
options, args = get_options()

# Store variable to be used in self.restart (restart spyder instance)
os.environ['SPYDER_ARGS'] = str(sys.argv[1:])

if CONF.get('main', 'single_instance') and not options.new_instance \
and not running_in_mac_app():
# Minimal delay (0.1-0.2 secs) to avoid that several
Expand Down

0 comments on commit 3e619c4

Please sign in to comment.