1 | #
|
---|
2 | # sessionproxy.py
|
---|
3 | #
|
---|
4 | # Copyright (C) 2010 Andrew Resch <andrewresch@gmail.com>
|
---|
5 | #
|
---|
6 | # Deluge is free software.
|
---|
7 | #
|
---|
8 | # You may redistribute it and/or modify it under the terms of the
|
---|
9 | # GNU General Public License, as published by the Free Software
|
---|
10 | # Foundation; either version 3 of the License, or (at your option)
|
---|
11 | # any later version.
|
---|
12 | #
|
---|
13 | # deluge is distributed in the hope that it will be useful,
|
---|
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
16 | # See the GNU General Public License for more details.
|
---|
17 | #
|
---|
18 | # You should have received a copy of the GNU General Public License
|
---|
19 | # along with deluge. If not, write to:
|
---|
20 | # The Free Software Foundation, Inc.,
|
---|
21 | # 51 Franklin Street, Fifth Floor
|
---|
22 | # Boston, MA 02110-1301, USA.
|
---|
23 | #
|
---|
24 | # In addition, as a special exception, the copyright holders give
|
---|
25 | # permission to link the code of portions of this program with the OpenSSL
|
---|
26 | # library.
|
---|
27 | # You must obey the GNU General Public License in all respects for all of
|
---|
28 | # the code used other than OpenSSL. If you modify file(s) with this
|
---|
29 | # exception, you may extend this exception to your version of the file(s),
|
---|
30 | # but you are not obligated to do so. If you do not wish to do so, delete
|
---|
31 | # this exception statement from your version. If you delete this exception
|
---|
32 | # statement from all source files in the program, then also delete it here.
|
---|
33 | #
|
---|
34 | #
|
---|
35 |
|
---|
36 | from twisted.internet.defer import maybeDeferred, succeed
|
---|
37 |
|
---|
38 | import deluge.component as component
|
---|
39 | from deluge.ui.client import client
|
---|
40 | from deluge.log import LOG as log
|
---|
41 | import time
|
---|
42 |
|
---|
43 | class SessionProxy(component.Component):
|
---|
44 | """
|
---|
45 | The SessionProxy component is used to cache session information client-side
|
---|
46 | to reduce the number of RPCs needed to provide a rich user interface.
|
---|
47 |
|
---|
48 | On start-up it will query the Core for a full status of all the torrents in
|
---|
49 | the session. After that point, it will query the Core for only changes in
|
---|
50 | the status of the torrents and will try to satisfy client requests from the
|
---|
51 | cache.
|
---|
52 |
|
---|
53 | """
|
---|
54 | def __init__(self):
|
---|
55 | log.debug("SessionProxy init..")
|
---|
56 | component.Component.__init__(self, "SessionProxy", interval=5)
|
---|
57 |
|
---|
58 | # Set the cache time in seconds
|
---|
59 | # This is how long data will be valid before refetching from the core
|
---|
60 | self.cache_time = 1.5
|
---|
61 |
|
---|
62 | # Hold the torrents' status.. {torrent_id: [time, {status_dict}], ...}
|
---|
63 | self.torrents = {}
|
---|
64 |
|
---|
65 | client.register_event_handler("TorrentStateChangedEvent", self.on_torrent_state_changed)
|
---|
66 | client.register_event_handler("TorrentRemovedEvent", self.on_torrent_removed)
|
---|
67 | client.register_event_handler("TorrentAddedEvent", self.on_torrent_added)
|
---|
68 |
|
---|
69 | def start(self):
|
---|
70 | def on_torrent_status(status):
|
---|
71 | # Save the time we got the torrent status
|
---|
72 | t = time.time()
|
---|
73 | for key, value in status.items():
|
---|
74 | self.torrents[key] = [t, value]
|
---|
75 |
|
---|
76 | return client.core.get_torrents_status({}, [], True).addCallback(on_torrent_status)
|
---|
77 |
|
---|
78 | def stop(self):
|
---|
79 | self.torrents = {}
|
---|
80 |
|
---|
81 | def create_status_dict(self, torrent_ids, keys):
|
---|
82 | """
|
---|
83 | Creates a status dict from the cache.
|
---|
84 |
|
---|
85 | :param torrent_ids: the torrent_ids
|
---|
86 | :type torrent_ids: list of strings
|
---|
87 | :param keys: the status keys
|
---|
88 | :type keys: list of strings
|
---|
89 |
|
---|
90 | :returns: a dict with the status information for the *torrent_ids*
|
---|
91 | :rtype: dict
|
---|
92 |
|
---|
93 | """
|
---|
94 | sd = {}
|
---|
95 | for torrent_id in torrent_ids:
|
---|
96 | sd[torrent_id] = dict([(x, y) for x, y in self.torrents[torrent_id][1].iteritems() if x in keys])
|
---|
97 | return sd
|
---|
98 |
|
---|
99 | def get_torrent_status(self, torrent_id, keys):
|
---|
100 | """
|
---|
101 | Get a status dict for one torrent.
|
---|
102 |
|
---|
103 | :param torrent_id: the torrent_id
|
---|
104 | :type torrent_id: string
|
---|
105 | :param keys: the status keys
|
---|
106 | :type keys: list of strings
|
---|
107 |
|
---|
108 | :returns: a dict of status information
|
---|
109 | :rtype: dict
|
---|
110 |
|
---|
111 | """
|
---|
112 | if torrent_id in self.torrents:
|
---|
113 | if time.time() - self.torrents[torrent_id][0] < self.cache_time:
|
---|
114 | return succeed(self.create_status_dict([torrent_id], keys)[torrent_id])
|
---|
115 | else:
|
---|
116 | d = client.core.get_torrent_status(torrent_id, keys, True)
|
---|
117 | def on_status(result, torrent_id):
|
---|
118 | self.torrents[torrent_id][0] = time.time()
|
---|
119 | self.torrents[torrent_id][1].update(result)
|
---|
120 | return self.create_status_dict([torrent_id], keys)[torrent_id]
|
---|
121 | return d.addCallback(on_status, torrent_id)
|
---|
122 | else:
|
---|
123 | d = client.core.get_torrent_status(torrent_id, keys, True)
|
---|
124 | def on_status(result):
|
---|
125 | if result:
|
---|
126 | self.torrents[torrent_id] = (time.time(), result)
|
---|
127 | return result
|
---|
128 | return d.addCallback(on_status)
|
---|
129 |
|
---|
130 | def get_torrents_status(self, filter_dict, keys):
|
---|
131 | """
|
---|
132 | Get a dict of torrent statuses.
|
---|
133 |
|
---|
134 | The filter can take 2 keys, *state* and *id*. The state filter can be
|
---|
135 | one of the torrent states or the special one *Active*. The *id* key is
|
---|
136 | simply a list of torrent_ids.
|
---|
137 |
|
---|
138 | :param filter_dict: the filter used for this query
|
---|
139 | :type filter_dict: dict
|
---|
140 | :param keys: the status keys
|
---|
141 | :type keys: list of strings
|
---|
142 |
|
---|
143 | :returns: a dict of torrent_ids and their status dicts
|
---|
144 | :rtype: dict
|
---|
145 |
|
---|
146 | """
|
---|
147 | # Helper functions and callbacks ---------------------------------------
|
---|
148 | def on_status(result, torrent_ids, keys):
|
---|
149 | # Update the internal torrent status dict with the update values
|
---|
150 | t = time.time()
|
---|
151 | for key, value in result.items():
|
---|
152 | self.torrents[key][0] = t
|
---|
153 | self.torrents[key][1].update(value)
|
---|
154 |
|
---|
155 | # Create the status dict
|
---|
156 | if not torrent_ids:
|
---|
157 | torrent_ids = result.keys()
|
---|
158 |
|
---|
159 | return self.create_status_dict(torrent_ids, keys)
|
---|
160 |
|
---|
161 | def find_torrents_to_fetch(torrent_ids):
|
---|
162 | to_fetch = []
|
---|
163 | t = time.time()
|
---|
164 | for key in torrent_ids:
|
---|
165 | torrent = self.torrents[key]
|
---|
166 | if t - torrent[0] > self.cache_time:
|
---|
167 | to_fetch.append(key)
|
---|
168 |
|
---|
169 | return to_fetch
|
---|
170 | #-----------------------------------------------------------------------
|
---|
171 |
|
---|
172 | if not filter_dict:
|
---|
173 | # This means we want all the torrents status
|
---|
174 | # We get a list of any torrent_ids with expired status dicts
|
---|
175 | to_fetch = find_torrents_to_fetch(self.torrents.keys())
|
---|
176 | if to_fetch:
|
---|
177 | d = client.core.get_torrents_status({"id": to_fetch}, keys, True)
|
---|
178 | return d.addCallback(on_status, self.torrents.keys(), keys)
|
---|
179 |
|
---|
180 | # Don't need to fetch anything
|
---|
181 | return maybeDeferred(self.create_status_dict, self.torrents.keys(), keys)
|
---|
182 |
|
---|
183 |
|
---|
184 | if len(filter_dict) == 1 and "id" in filter_dict:
|
---|
185 | # At this point we should have a filter with just "id" in it
|
---|
186 | to_fetch = find_torrents_to_fetch(filter_dict["id"])
|
---|
187 | if to_fetch:
|
---|
188 | d = client.core.get_torrents_status({"id": to_fetch}, keys, True)
|
---|
189 | return d.addCallback(on_status, filter_dict["id"], keys)
|
---|
190 | else:
|
---|
191 | # Don't need to fetch anything, so just return data from the cache
|
---|
192 | return maybeDeferred(self.create_status_dict, filter_dict["id"], keys)
|
---|
193 | else:
|
---|
194 | # This is a keyworded filter so lets just pass it onto the core
|
---|
195 | # XXX: Add more caching here.
|
---|
196 | d = client.core.get_torrents_status(filter_dict, keys, True)
|
---|
197 | return d.addCallback(on_status, None, keys)
|
---|
198 |
|
---|
199 | def on_torrent_state_changed(self, torrent_id, state):
|
---|
200 | self.torrents[torrent_id][1]["state"] = state
|
---|
201 |
|
---|
202 | def on_torrent_added(self, torrent_id):
|
---|
203 | self.torrents[torrent_id] = [time.time() - self.cache_time - 1, {}]
|
---|
204 | def on_status(status):
|
---|
205 | self.torrents[torrent_id][1].update(status)
|
---|
206 | client.core.get_torrent_status(torrent_id, []).addCallback(on_status)
|
---|
207 |
|
---|
208 | def on_torrent_removed(self, torrent_id):
|
---|
209 | del self.torrents[torrent_id]
|
---|