##// END OF EJS Templates
Improve robustness, as suggested by Min.
Thomas Kluyver -
Show More
@@ -1,144 +1,144 b''
1 """Windows-specific implementation of process utilities.
1 """Windows-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010 The IPython Development Team
7 # Copyright (C) 2010 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # stdlib
18 # stdlib
19 import os
19 import os
20 import sys
20 import sys
21
21
22 from subprocess import STDOUT
22 from subprocess import STDOUT
23
23
24 # our own imports
24 # our own imports
25 from ._process_common import read_no_interrupt, process_handler
25 from ._process_common import read_no_interrupt, process_handler
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Function definitions
28 # Function definitions
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 class AvoidUNCPath(object):
31 class AvoidUNCPath(object):
32 """A context manager to protect command execution from UNC paths.
32 """A context manager to protect command execution from UNC paths.
33
33
34 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
34 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
35 This context manager temporarily changes directory to the 'C:' drive on
35 This context manager temporarily changes directory to the 'C:' drive on
36 entering, and restores the original working directory on exit.
36 entering, and restores the original working directory on exit.
37
37
38 The context manager returns the starting working directory *if* it made a
38 The context manager returns the starting working directory *if* it made a
39 change and None otherwise, so that users can apply the necessary adjustment
39 change and None otherwise, so that users can apply the necessary adjustment
40 to their system calls in the event of a change.
40 to their system calls in the event of a change.
41
41
42 Example
42 Example
43 -------
43 -------
44 ::
44 ::
45 cmd = 'dir'
45 cmd = 'dir'
46 with AvoidUNCPath() as path:
46 with AvoidUNCPath() as path:
47 if path is not None:
47 if path is not None:
48 cmd = '"pushd %s &&"%s' % (path, cmd)
48 cmd = '"pushd %s &&"%s' % (path, cmd)
49 os.system(cmd)
49 os.system(cmd)
50 """
50 """
51 def __enter__(self):
51 def __enter__(self):
52 self.path = os.getcwd()
52 self.path = os.getcwd()
53 self.is_unc_path = self.path.startswith(r"\\")
53 self.is_unc_path = self.path.startswith(r"\\")
54 if self.is_unc_path:
54 if self.is_unc_path:
55 # change to c drive (as cmd.exe cannot handle UNC addresses)
55 # change to c drive (as cmd.exe cannot handle UNC addresses)
56 os.chdir("C:")
56 os.chdir("C:")
57 return self.path
57 return self.path
58 else:
58 else:
59 # We return None to signal that there was no change in the working
59 # We return None to signal that there was no change in the working
60 # directory
60 # directory
61 return None
61 return None
62
62
63 def __exit__(self, exc_type, exc_value, traceback):
63 def __exit__(self, exc_type, exc_value, traceback):
64 if self.is_unc_path:
64 if self.is_unc_path:
65 os.chdir(self.path)
65 os.chdir(self.path)
66
66
67
67
68 def _find_cmd(cmd):
68 def _find_cmd(cmd):
69 """Find the full path to a .bat or .exe using the win32api module."""
69 """Find the full path to a .bat or .exe using the win32api module."""
70 try:
70 try:
71 from win32api import SearchPath
71 from win32api import SearchPath
72 except ImportError:
72 except ImportError:
73 raise ImportError('you need to have pywin32 installed for this to work')
73 raise ImportError('you need to have pywin32 installed for this to work')
74 else:
74 else:
75 PATH = os.environ['PATH']
75 PATH = os.environ['PATH']
76 extensions = ['.exe', '.com', '.bat', '.py']
76 extensions = ['.exe', '.com', '.bat', '.py']
77 path = None
77 path = None
78 for ext in extensions:
78 for ext in extensions:
79 try:
79 try:
80 path = SearchPath(PATH, cmd + ext)[0]
80 path = SearchPath(PATH, cmd + ext)[0]
81 except:
81 except:
82 pass
82 pass
83 if path is None:
83 if path is None:
84 raise OSError("command %r not found" % cmd)
84 raise OSError("command %r not found" % cmd)
85 else:
85 else:
86 return path
86 return path
87
87
88
88
89 def _system_body(p):
89 def _system_body(p):
90 """Callback for _system."""
90 """Callback for _system."""
91 enc = sys.stdin.encoding
91 enc = sys.stdin.encoding or sys.getdefaultencoding()
92 for line in read_no_interrupt(p.stdout).splitlines():
92 for line in read_no_interrupt(p.stdout).splitlines():
93 line = line.decode(enc)
93 line = line.decode(enc, 'replace')
94 print(line, file=sys.stdout)
94 print(line, file=sys.stdout)
95 for line in read_no_interrupt(p.stderr).splitlines():
95 for line in read_no_interrupt(p.stderr).splitlines():
96 line = line.decode(enc)
96 line = line.decode(enc, 'replace')
97 print(line, file=sys.stderr)
97 print(line, file=sys.stderr)
98
98
99
99
100 def system(cmd):
100 def system(cmd):
101 """Win32 version of os.system() that works with network shares.
101 """Win32 version of os.system() that works with network shares.
102
102
103 Note that this implementation returns None, as meant for use in IPython.
103 Note that this implementation returns None, as meant for use in IPython.
104
104
105 Parameters
105 Parameters
106 ----------
106 ----------
107 cmd : str
107 cmd : str
108 A command to be executed in the system shell.
108 A command to be executed in the system shell.
109
109
110 Returns
110 Returns
111 -------
111 -------
112 None : we explicitly do NOT return the subprocess status code, as this
112 None : we explicitly do NOT return the subprocess status code, as this
113 utility is meant to be used extensively in IPython, where any return value
113 utility is meant to be used extensively in IPython, where any return value
114 would trigger :func:`sys.displayhook` calls.
114 would trigger :func:`sys.displayhook` calls.
115 """
115 """
116 with AvoidUNCPath() as path:
116 with AvoidUNCPath() as path:
117 if path is not None:
117 if path is not None:
118 cmd = '"pushd %s &&"%s' % (path, cmd)
118 cmd = '"pushd %s &&"%s' % (path, cmd)
119 process_handler(cmd, _system_body)
119 process_handler(cmd, _system_body)
120
120
121
121
122 def getoutput(cmd):
122 def getoutput(cmd):
123 """Return standard output of executing cmd in a shell.
123 """Return standard output of executing cmd in a shell.
124
124
125 Accepts the same arguments as os.system().
125 Accepts the same arguments as os.system().
126
126
127 Parameters
127 Parameters
128 ----------
128 ----------
129 cmd : str
129 cmd : str
130 A command to be executed in the system shell.
130 A command to be executed in the system shell.
131
131
132 Returns
132 Returns
133 -------
133 -------
134 stdout : str
134 stdout : str
135 """
135 """
136
136
137 with AvoidUNCPath() as path:
137 with AvoidUNCPath() as path:
138 if path is not None:
138 if path is not None:
139 cmd = '"pushd %s &&"%s' % (path, cmd)
139 cmd = '"pushd %s &&"%s' % (path, cmd)
140 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
140 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
141
141
142 if out is None:
142 if out is None:
143 out = ''
143 out = ''
144 return out
144 return out
@@ -1,92 +1,96 b''
1 import logging
1 import logging
2 import sys
2 import sys
3 import time
3 import time
4 from cStringIO import StringIO
4 from cStringIO import StringIO
5
5
6 from session import extract_header, Message
6 from session import extract_header, Message
7
7
8 from IPython.utils import io
8 from IPython.utils import io
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Globals
11 # Globals
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Module-level logger
14 # Module-level logger
15 logger = logging.getLogger(__name__)
15 logger = logging.getLogger(__name__)
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Stream classes
18 # Stream classes
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 class OutStream(object):
21 class OutStream(object):
22 """A file like object that publishes the stream to a 0MQ PUB socket."""
22 """A file like object that publishes the stream to a 0MQ PUB socket."""
23
23
24 # The time interval between automatic flushes, in seconds.
24 # The time interval between automatic flushes, in seconds.
25 flush_interval = 0.05
25 flush_interval = 0.05
26
26
27 def __init__(self, session, pub_socket, name):
27 def __init__(self, session, pub_socket, name):
28 self.session = session
28 self.session = session
29 self.pub_socket = pub_socket
29 self.pub_socket = pub_socket
30 self.name = name
30 self.name = name
31 self.parent_header = {}
31 self.parent_header = {}
32 self._new_buffer()
32 self._new_buffer()
33
33
34 def set_parent(self, parent):
34 def set_parent(self, parent):
35 self.parent_header = extract_header(parent)
35 self.parent_header = extract_header(parent)
36
36
37 def close(self):
37 def close(self):
38 self.pub_socket = None
38 self.pub_socket = None
39
39
40 def flush(self):
40 def flush(self):
41 #io.rprint('>>>flushing output buffer: %s<<<' % self.name) # dbg
41 #io.rprint('>>>flushing output buffer: %s<<<' % self.name) # dbg
42 if self.pub_socket is None:
42 if self.pub_socket is None:
43 raise ValueError(u'I/O operation on closed file')
43 raise ValueError(u'I/O operation on closed file')
44 else:
44 else:
45 data = self._buffer.getvalue()
45 data = self._buffer.getvalue()
46 if data:
46 if data:
47 # Make sure that we're handling unicode
48 if not isinstance(data, unicode):
49 enc = sys.stdin.encoding or sys.getdefaultencoding()
50 data = data.decode(enc, 'replace')
47 content = {u'name':self.name, u'data':data}
51 content = {u'name':self.name, u'data':data}
48 msg = self.session.send(self.pub_socket, u'stream',
52 msg = self.session.send(self.pub_socket, u'stream',
49 content=content,
53 content=content,
50 parent=self.parent_header)
54 parent=self.parent_header)
51 logger.debug(msg)
55 logger.debug(msg)
52 self._buffer.close()
56 self._buffer.close()
53 self._new_buffer()
57 self._new_buffer()
54
58
55 def isatty(self):
59 def isatty(self):
56 return False
60 return False
57
61
58 def next(self):
62 def next(self):
59 raise IOError('Read not supported on a write only stream.')
63 raise IOError('Read not supported on a write only stream.')
60
64
61 def read(self, size=-1):
65 def read(self, size=-1):
62 raise IOError('Read not supported on a write only stream.')
66 raise IOError('Read not supported on a write only stream.')
63
67
64 def readline(self, size=-1):
68 def readline(self, size=-1):
65 raise IOError('Read not supported on a write only stream.')
69 raise IOError('Read not supported on a write only stream.')
66
70
67 def write(self, string):
71 def write(self, string):
68 if self.pub_socket is None:
72 if self.pub_socket is None:
69 raise ValueError('I/O operation on closed file')
73 raise ValueError('I/O operation on closed file')
70 else:
74 else:
71 # We can only send raw bytes, not unicode objects, so we encode
75 # We can only send raw bytes, not unicode objects, so we encode
72 # into utf-8 for all frontends if we get unicode inputs.
76 # into utf-8 for all frontends if we get unicode inputs.
73 if type(string) == unicode:
77 if type(string) == unicode:
74 string = string.encode('utf-8')
78 string = string.encode('utf-8')
75
79
76 self._buffer.write(string)
80 self._buffer.write(string)
77 current_time = time.time()
81 current_time = time.time()
78 if self._start <= 0:
82 if self._start <= 0:
79 self._start = current_time
83 self._start = current_time
80 elif current_time - self._start > self.flush_interval:
84 elif current_time - self._start > self.flush_interval:
81 self.flush()
85 self.flush()
82
86
83 def writelines(self, sequence):
87 def writelines(self, sequence):
84 if self.pub_socket is None:
88 if self.pub_socket is None:
85 raise ValueError('I/O operation on closed file')
89 raise ValueError('I/O operation on closed file')
86 else:
90 else:
87 for string in sequence:
91 for string in sequence:
88 self.write(string)
92 self.write(string)
89
93
90 def _new_buffer(self):
94 def _new_buffer(self):
91 self._buffer = StringIO()
95 self._buffer = StringIO()
92 self._start = -1
96 self._start = -1
General Comments 0
You need to be logged in to leave comments. Login now