harmonized maemo/harmattan src files
[feedingit] / src / update_feeds.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # Copyright (c) 2011 Neal H. Walfield
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Lesser General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU Lesser General Public License for more details.
15 #
16 #  You should have received a copy of the GNU Lesser General Public License
17 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 # ============================================================================
21 # Name        : update_feeds.py
22 # Author      : Yves Marcoz
23 # Version     : 0.6.1
24 # Description : Simple RSS Reader
25 # ============================================================================
26
27 from rss_sqlite import Listing
28 from config import Config
29 from updatedbus import UpdateServerObject
30
31 import os
32 import traceback
33 import sys
34 import dbus
35
36 from jobmanager import JobManager
37 import mainthread
38
39 import gobject
40 gobject.threads_init()
41
42 import logging
43 logger = logging.getLogger(__name__)
44 import debugging
45 debugging.init(dot_directory=".feedingit", program_name="update_feeds")
46
47 from updatedbus import update_server_object
48
49 CONFIGDIR = os.environ.get("HOME", "/home/user") + "/.feedingit/"
50 #DESKTOP_FILE = "/usr/share/applications/hildon-status-menu/feedingit_status.desktop"
51
52 from socket import setdefaulttimeout
53 timeout = 5
54 setdefaulttimeout(timeout)
55 del timeout
56
57 class FeedUpdate(UpdateServerObject):
58     def __init__(self, bus_name):
59         UpdateServerObject.__init__(self, bus_name)
60
61         self.config = Config(self, CONFIGDIR+"config.ini")
62         self.listing = Listing(self.config, CONFIGDIR)
63
64         jm = JobManager(True)
65         jm.stats_hook_register (self.job_manager_update,
66                                 run_in_main_thread=True)
67
68         # Whether or no an update is in progress.
69         self.am_updating = False
70
71         # After an update an finished, we start the inactivity timer.
72         # If this fires before a new job arrives, we quit.
73         self.inactivity_timer = 0
74
75         # Whether we started in daemon mode, or not.
76         self.daemon = '--daemon' in sys.argv
77
78         if self.daemon:
79             logger.debug("Running in daemon mode: waiting for commands.")
80             self.inactivity_timer = gobject.timeout_add(
81                 5 * 60 * 1000, self.inactivity_cb)
82         else:
83             # Update all feeds.
84             logger.debug("Not running in daemon mode: updating all feeds.")
85             gobject.idle_add(self.UpdateAll)
86
87         # If the system becomes idle
88         bus = dbus.SystemBus()
89
90         mce_request_proxy = bus.get_object(
91             'com.nokia.mce', '/com/nokia/mce/request')
92         mce_request_iface = dbus.Interface(
93             mce_request_proxy, 'com.nokia.mce.request')
94         system_idle = mce_request_iface.get_inactivity_status()
95         # Force self.system_inactivity_ind to run: ensure that a state
96         # change occurs.
97         self.system_idle = not system_idle
98         self.system_inactivity_ind(system_idle)
99
100         mce_signal_proxy = bus.get_object(
101             'com.nokia.mce', '/com/nokia/mce/signal')
102         mce_signal_iface = dbus.Interface(
103             mce_signal_proxy, 'com.nokia.mce.signal')
104         mce_signal_iface.connect_to_signal(
105             'system_inactivity_ind', self.system_inactivity_ind)
106
107     def increase_download_parallelism(self):
108         # The system has been idle for a while.  Enable parallel
109         # downloads.
110         logger.debug("Increasing parallelism to 4 workers.")
111         JobManager().num_threads = 4
112         gobject.source_remove (self.increase_download_parallelism_id)
113         del self.increase_download_parallelism_id
114         return False
115
116     def system_inactivity_ind(self, idle):
117         # The system's idle state changed.
118         if (self.system_idle and idle) or (not self.system_idle and not idle):
119             # No change.
120             return
121
122         if not idle:
123             if hasattr (self, 'increase_download_parallelism_id'):
124                 gobject.source_remove (self.increase_download_parallelism_id)
125                 del self.increase_download_parallelism_id
126         else:
127             self.increase_download_parallelism_id = \
128                 gobject.timeout_add_seconds(
129                     60, self.increase_download_parallelism)
130
131         if not idle:
132             logger.debug("Reducing parallelism to 1 worker.")
133             JobManager().num_threads = 1
134
135         self.system_idle = idle
136
137     def job_manager_update(self, jm, old_stats, new_stats, updated_feed):
138         queued = new_stats['jobs-queued']
139         in_progress = new_stats['jobs-in-progress']
140
141         if (queued or in_progress) and not self.am_updating:
142             logger.debug("new update started")
143             self.am_updating = True
144             self.UpdateStarted()
145             self.UpdateProgress(0, 0, in_progress, queued, 0, 0, 0, "")
146
147         if not queued and not in_progress:
148             logger.debug("update finished!")
149             self.am_updating = False
150             self.UpdateFinished()
151             self.ArticleCountUpdated()
152
153             if self.daemon:
154                 self.inactivity_timer = gobject.timeout_add(
155                     60 * 1000, self.inactivity_cb)
156             else:
157                 logger.debug("update finished, not running in daemon mode: "
158                              "quitting")
159                 mainloop.quit()
160
161         if (queued or in_progress) and self.inactivity_timer:
162             gobject.source_remove(self.inactivity_timer)
163             self.inactivity_timer = 0
164
165     def inactivity_cb(self):
166         """
167         The updater has been inactive for a while.  Quit.
168         """
169         assert self.inactivity_timer
170         self.inactivity_timer = 0
171
172         if not self.am_updating:
173             logger.info("Nothing to do for a while.  Quitting.")
174
175             # Make any progress bar go away.
176             try:
177                 update_server_object().UpdateProgress(
178                     100, 0, 0, 0, 0, 0, 0, "")
179             except Exception:
180                 logger.exception("Sending final progress update")
181
182             mainloop.quit()
183
184     def StopUpdate(self):
185         """
186         Stop updating.
187         """
188         super(FeedUpdate, self).stopUpdate()
189
190         JobManager().quit()
191
192     def UpdateAll(self):
193         """
194         Update all feeds.
195         """
196         super(FeedUpdate, self).UpdateAll()
197
198         feeds = self.listing.getListOfFeeds()
199         for k in feeds:
200             self.listing.updateFeed(k)
201         logger.debug("Queued all feeds (%d) for update." % len(feeds))
202
203     def Update(self, feed):
204         """
205         Update a particular feed.
206         """
207         super(FeedUpdate, self).Update(feed)
208
209         # We got a request via dbus.  If we weren't in daemon mode
210         # before, enter it now.
211         self.daemon = True
212
213         self.listing.updateFeed(feed)
214
215
216 import dbus.mainloop.glib
217 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
218
219 mainloop = gobject.MainLoop()
220 mainthread.init()
221
222 # Acquire our name on the session bus.  If this doesn't work, most
223 # likely another update_feeds instance is already running.  In this
224 # case, just quit.
225 try:
226     bus_name = dbus.service.BusName('org.marcoz.feedingit',
227                                     bus=dbus.SessionBus(),
228                                     do_not_queue=True)
229 except Exception:
230     # We failed to acquire our bus name.  Die.
231     try:
232         dbus_proxy = dbus.SessionBus().get_object(
233             'org.freedesktop.DBus', '/org/freedesktop/DBus')
234         dbus_iface = dbus.Interface(dbus_proxy, 'org.freedesktop.DBus')
235         pid = dbus_iface.GetConnectionUnixProcessID('org.marcoz.feedingit')
236         logger.error("update_feeds already running: pid %d." % pid)
237     except Exception, e:
238         logger.error("Getting pid associated with org.marcoz.feedingit: %s"
239                      % str(e))
240         logger.error("update_feeds already running.")
241
242     sys.exit(1)
243
244 # Run the updater.  Note: we run this until feed.am_updating is false.
245 # Only is this case have all worker threads exited.  If the main
246 # thread exits before all threads have exited and the process gets a
247 # signal, the Python interpreter is unable to handle the signal and it
248 # runs really slow (rescheduling after ever single instruction instead
249 # of every few thousand).
250 feed = FeedUpdate(bus_name)
251 while True:
252     try:
253         mainloop.run()
254     except KeyboardInterrupt:
255         logger.error("Interrupted.  Quitting.")
256         JobManager().quit()
257
258     if not feed.am_updating:
259         break
260