1 # Copyright (C) 2009, 2010, 2011 Roman Zimbelmann <romanz@lavabit.com>
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 from collections
import deque
17 from time
import time
, sleep
18 from subprocess
import Popen
, PIPE
19 from ranger
.core
.shared
import FileManagerAware
20 from ranger
.ext
.signals
import SignalDispatcher
30 class Loadable(object):
32 def __init__(self
, gen
, descr
):
33 self
.load_generator
= gen
34 self
.description
= descr
36 def get_description(self
):
37 return self
.description
52 class CommandLoader(Loadable
, SignalDispatcher
, FileManagerAware
):
54 Run an external command with the loader.
56 Output from stderr will be reported. Ensure that the process doesn't
57 ever ask for input, otherwise the loader will be blocked until this
58 object is removed from the queue (type ^C in ranger)
62 def __init__(self
, args
, descr
, silent
=False, read
=False):
63 SignalDispatcher
.__init
__(self
)
64 Loadable
.__init
__(self
, self
.generate(), descr
)
68 self
.stdout_buffer
= ""
71 null
= open(os
.devnull
, 'r')
72 self
.process
= process
= Popen(self
.args
,
73 stdout
=PIPE
, stderr
=PIPE
, stdin
=null
)
74 self
.signal_emit('before', process
=process
, loader
=self
)
75 if self
.silent
and not self
.read
:
76 while process
.poll() is None:
80 py3
= sys
.version
>= '3'
83 selectlist
.append(process
.stdout
)
85 selectlist
.append(process
.stderr
)
86 while process
.poll() is None:
89 rd
, _
, __
= select
.select(selectlist
, [], [], 0.03)
92 if rd
== process
.stderr
:
95 read
= safeDecode(read
)
97 self
.fm
.notify(read
, bad
=True)
98 elif rd
== process
.stdout
:
101 read
= safeDecode(read
)
103 self
.stdout_buffer
+= read
107 for l
in process
.stderr
.readlines():
110 self
.fm
.notify(l
, bad
=True)
112 read
= process
.stdout
.read()
114 read
= safeDecode(read
)
115 self
.stdout_buffer
+= read
118 self
.signal_emit('after', process
=process
, loader
=self
)
121 if not self
.finished
and not self
.paused
:
123 self
.process
.send_signal(20)
127 self
.signal_emit('pause', process
=self
.process
, loader
=self
)
130 if not self
.finished
and self
.paused
:
132 self
.process
.send_signal(18)
135 Loadable
.unpause(self
)
136 self
.signal_emit('unpause', process
=self
.process
, loader
=self
)
139 self
.signal_emit('destroy', process
=self
.process
, loader
=self
)
144 def safeDecode(string
):
146 return string
.decode("utf-8")
147 except (UnicodeDecodeError):
149 return string
.decode(chardet
.detect(string
)["encoding"])
154 class Loader(FileManagerAware
):
155 seconds_of_work_time
= 0.03
156 throbber_chars
= r
'/-\|'
161 self
.load_generator
= None
162 self
.throbber_status
= 0
167 """Rotate the throbber"""
168 # TODO: move all throbber logic to UI
169 self
.throbber_status
= \
170 (self
.throbber_status
+ 1) % len(self
.throbber_chars
)
171 self
.status
= self
.throbber_chars
[self
.throbber_status
]
175 Add an object to the queue.
176 It should have a load_generator method.
178 while obj
in self
.queue
:
179 self
.queue
.remove(obj
)
180 self
.queue
.appendleft(obj
)
182 def move(self
, _from
, to
):
184 item
= self
.queue
[_from
]
188 del self
.queue
[_from
]
191 self
.queue
.appendleft(item
)
193 self
.queue
[1].pause()
195 self
.queue
.append(item
)
197 raise NotImplementedError
199 def remove(self
, item
=None, index
=None):
200 if item
is not None and index
is None:
201 for i
, test
in enumerate(self
.queue
):
208 if index
is not None:
210 item
= self
.queue
[index
]
211 if hasattr(item
, 'unload'):
214 del self
.queue
[index
]
218 Load items from the queue if there are any.
219 Stop after approximately self.seconds_of_work_time.
222 # get the first item with a proper load_generator
225 if item
.load_generator
is None:
233 if item
!= self
.old_item
:
235 self
.old_item
.pause()
239 end_time
= time() + self
.seconds_of_work_time
242 while time() < end_time
:
243 next(item
.load_generator
)
244 except StopIteration:
245 item
.load_generator
= None
246 self
.queue
.remove(item
)
247 except Exception as err
:
251 """Is there anything to load?"""
252 return bool(self
.queue
)
256 self
.queue
.pop().destroy()