1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
34
from miro import eventloop
36
# Amount of time to wait for daemonic threads to quit. Right now, the
37
# only thing we use Daemonic threads for is to send HTTP requests to
38
# BitTorrent trackers.
39
DAEMONIC_THREAD_TIMEOUT = 2
41
class Command(object):
43
def __init__(self, daemon, *args, **kws):
44
self.command_id = "cmd%08d" % random.randint(0, 99999999)
50
def set_daemon(self, daemon):
53
def send(self, callback=None):
54
if self.daemon.shutdown:
56
eventloop.add_idle(lambda : self.daemon.send(self, callback),
57
"sending command %r" % self)
61
logging.warning("no action defined for command %s", self.command_id)
63
def __getstate__(self):
64
out = {"id": self.command_id,
70
except AttributeError:
74
def __setstate__(self, data):
75
self.command_id = data["id"]
76
self.kws = data["kws"]
77
self.args = data["args"]
78
self.orig = data["orig"]
80
self.ret = data["ret"]
84
#############################################################################
85
# Downloader to App commands #
86
#############################################################################
87
class FindHTTPAuthCommand(Command):
89
from miro import httpauth
90
id_, args = self.args[0], self.args[1:]
91
def callback(auth_header):
92
c = GotHTTPAuthCommand(self.daemon, id_, auth_header)
94
httpauth.find_http_auth(callback, *args)
96
class AskForHTTPAuthCommand(Command):
98
from miro import httpauth
99
id_, args = self.args[0], self.args[1:]
100
def callback(auth_header):
101
c = GotHTTPAuthCommand(self.daemon, id_, auth_header)
103
httpauth.ask_for_http_auth(callback, *args)
105
class RemoveHTTPAuthCommand(Command):
107
from miro import httpauth
108
httpauth.remove_by_url_and_realm(*self.args)
110
class UpdateDownloadStatus(Command):
112
from miro.downloader import RemoteDownloader
113
return RemoteDownloader.update_status(*self.args, **self.kws)
115
class BatchUpdateDownloadStatus(Command):
118
from miro.downloader import RemoteDownloader
119
for status in self.args[0]:
120
RemoteDownloader.update_status(status)
122
class DownloaderErrorCommand(Command):
124
from miro import signals
125
signals.system.failed("In Downloader process", details=self.args[0])
127
class DuplicateTorrent(Command):
128
"""The downloader daemon detected that one download was for the same
129
torrent as another one.
132
original_id, duplicate_id = self.args[0], self.args[1]
133
from miro import downloader
134
duplicate = downloader.get_downloader_by_dlid(duplicate_id)
135
original = downloader.get_downloader_by_dlid(original_id)
136
if duplicate is None:
137
logging.warn("duplicate torrent doesn't exist anymore, "
138
"ignoring (dlid %s)", duplicate_id)
141
logging.warn("original torrent doesn't exist anymore, "
142
"restarting (dlid %s)", original_id)
145
for item in duplicate.item_list:
146
item.set_downloader(original)
148
class ShutDownResponseCommand(Command):
150
self.daemon.shutdown_response()
152
#############################################################################
153
# App to Downloader commands #
154
#############################################################################
155
class InitialConfigCommand(Command):
157
from miro import config
158
from miro.dl_daemon import download
159
config.set_dictionary(*self.args, **self.kws)
160
download.config_received()
162
class UpdateConfigCommand(Command):
164
from miro import config
165
config.update_dictionary(*self.args, **self.kws)
167
class UpdateHTTPPasswordsCommand(Command):
169
from miro.dl_daemon.private import httpauth
170
httpauth.update_passwords(*self.args)
172
class StartNewDownloadCommand(Command):
174
from miro.dl_daemon import download
175
return download.start_new_download(*self.args, **self.kws)
177
class StartDownloadCommand(Command):
179
from miro.dl_daemon import download
180
return download.start_download(*self.args, **self.kws)
182
class PauseDownloadCommand(Command):
184
from miro.dl_daemon import download
185
return download.pause_download(*self.args, **self.kws)
187
class StopDownloadCommand(Command):
189
from miro.dl_daemon import download
190
return download.stop_download(*self.args, **self.kws)
192
class StopUploadCommand(Command):
194
from miro.dl_daemon import download
195
return download.stop_upload(*self.args, **self.kws)
197
class PauseUploadCommand(Command):
199
from miro.dl_daemon import download
200
return download.pause_upload(*self.args, **self.kws)
202
class GetDownloadStatusCommand(Command):
204
from miro.dl_daemon import download
205
return download.get_download_status(*self.args, **self.kws)
207
class RestoreDownloaderCommand(Command):
209
from miro.dl_daemon import download
210
return download.restore_downloader(*self.args, **self.kws)
212
class MigrateDownloadCommand(Command):
214
from miro.dl_daemon import download
215
return download.migrate_download(*self.args, **self.kws)
217
class GotHTTPAuthCommand(Command):
219
id_, auth_header = self.args
220
from miro.dl_daemon.private import httpauth
221
httpauth.handle_http_auth_response(id_, auth_header)
223
class ShutDownCommand(Command):
224
def response_sent(self):
226
logging.info("Shutdown complete")
229
starttime = time.time()
230
from miro import httpclient
231
from miro.dl_daemon import download
233
httpclient.stop_thread()
234
eventloop.thread_pool_quit()
235
for thread in threading.enumerate():
236
if (thread != threading.currentThread()
237
and thread.getName() != "MainThread"
238
and not thread.isDaemon()):
240
endtime = starttime + DAEMONIC_THREAD_TIMEOUT
241
for thread in threading.enumerate():
242
if (thread != threading.currentThread()
243
and thread.getName() != "MainThread"):
244
timeout = endtime - time.time()
248
c = ShutDownResponseCommand(self.daemon)
249
c.send(callback=self.response_sent)
250
self.daemon.shutdown = True