harmonized maemo/harmattan src files
[feedingit] / psa_harmattan / feedingit / pysrc / 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 #CONFIGDIR="/home/user/.feedingit/"
48 CONFIGDIR = os.environ.get("HOME", "/home/user") + "/.feedingit/"
49 #DESKTOP_FILE = "/usr/share/applications/hildon-status-menu/feedingit_status.desktop"
50
51 from socket import setdefaulttimeout
52 timeout = 5
53 setdefaulttimeout(timeout)
54 del timeout
55
56 class FeedUpdate(UpdateServerObject):
57     def __init__(self, bus_name):
58         UpdateServerObject.__init__(self, bus_name)
59
60         self.config = Config(self, CONFIGDIR+"config.ini")
61         self.listing = Listing(self.config, CONFIGDIR)
62
63         jm = JobManager(True)
64         jm.stats_hook_register (self.job_manager_update,
65                                 run_in_main_thread=True)
66
67         # Whether or no an update is in progress.
68         self.am_updating = False
69
70         # After an update an finished, we start the inactivity timer.
71         # If this fires before a new job arrives, we quit.
72         self.inactivity_timer = 0
73
74         # Whether we started in daemon mode, or not.
75         self.daemon = '--daemon' in sys.argv
76
77         if self.daemon:
78             logger.debug("Running in daemon mode: waiting for commands.")
79             self.inactivity_timer = gobject.timeout_add(
80                 5 * 60 * 1000, self.inactivity_cb)
81         else:
82             # Update all feeds.
83             logger.debug("Not running in daemon mode: updating all feeds.")
84             gobject.idle_add(self.UpdateAll)
85
86 #        # If the system becomes idle
87 #        bus = dbus.SystemBus()
88 #
89 #        mce_request_proxy = bus.get_object(
90 #            'com.nokia.mce', '/com/nokia/mce/request')
91 #        mce_request_iface = dbus.Interface(
92 #            mce_request_proxy, 'com.nokia.mce.request')
93 #        system_idle = mce_request_iface.get_inactivity_status()
94 #        # Force self.system_inactivity_ind to run: ensure that a state
95 #        # change occurs.
96 #        self.system_idle = not system_idle
97 #        self.system_inactivity_ind(system_idle)
98 #
99 #        mce_signal_proxy = bus.get_object(
100 #            'com.nokia.mce', '/com/nokia/mce/signal')
101 #        mce_signal_iface = dbus.Interface(
102 #            mce_signal_proxy, 'com.nokia.mce.signal')
103 #        mce_signal_iface.connect_to_signal(
104 #            'system_inactivity_ind', self.system_inactivity_ind)
105
106     def increase_download_parallelism(self):
107         # The system has been idle for a while.  Enable parallel
108         # downloads.
109         logger.debug("Increasing parallelism to 4 workers.")
110         JobManager().num_threads = 4
111         gobject.source_remove (self.increase_download_parallelism_id)
112         del self.increase_download_parallelism_id
113         return False
114
115     def system_inactivity_ind(self, idle):
116         # The system's idle state changed.
117         if (self.system_idle and idle) or (not self.system_idle and not idle):
118             # No change.
119             return
120
121         if not idle:
122             if hasattr (self, 'increase_download_parallelism_id'):
123                 gobject.source_remove (self.increase_download_parallelism_id)
124                 del self.increase_download_parallelism_id
125         else:
126             self.increase_download_parallelism_id = \
127                 gobject.timeout_add_seconds(
128                     60, self.increase_download_parallelism)
129
130         if not idle:
131             logger.debug("Reducing parallelism to 1 worker.")
132             JobManager().num_threads = 1
133
134         self.system_idle = idle
135
136     def job_manager_update(self, jm, old_stats, new_stats, updated_feed):
137         queued = new_stats['jobs-queued']
138         in_progress = new_stats['jobs-in-progress']
139
140         if (queued or in_progress) and not self.am_updating:
141             logger.debug("new update started")
142             self.am_updating = True
143             self.UpdateStarted()
144             self.UpdateProgress(0, 0, in_progress, queued, 0, 0, 0, "")
145
146         if not queued and not in_progress:
147             logger.debug("update finished!")
148             self.am_updating = False
149             self.UpdateFinished()
150             self.ArticleCountUpdated()
151
152             if self.daemon:
153                 self.inactivity_timer = gobject.timeout_add(
154                     60 * 1000, self.inactivity_cb)
155             else:
156                 logger.debug("update finished, not running in daemon mode: "
157                              "quitting")
158                 mainloop.quit()
159
160         if (queued or in_progress) and self.inactivity_timer:
161             gobject.source_remove(self.inactivity_timer)
162             self.inactivity_timer = 0
163
164     def inactivity_cb(self):
165         """
166         The updater has been inactive for a while.  Quit.
167         """
168         assert self.inactivity_timer
169         self.inactivity_timer = 0
170
171         if not self.am_updating:
172             logger.info("Nothing to do for a while.  Quitting.")
173             mainloop.quit()
174
175     def StopUpdate(self):
176         """
177         Stop updating.
178         """
179         super(FeedUpdate, self).stopUpdate()
180
181         JobManager().quit()
182
183     def UpdateAll(self):
184         """
185         Update all feeds.
186         """
187         logger.info("starting update.")
188         super(FeedUpdate, self).UpdateAll()
189
190         feeds = self.listing.getListOfFeeds()
191         for k in feeds:
192             self.listing.updateFeed(k)
193         logger.debug("Queued all feeds (%d) for update." % len(feeds))
194
195     def Update(self, feed):
196         """
197         Update a particular feed.
198         """
199         super(FeedUpdate, self).Update(feed)
200
201         # We got a request via dbus.  If we weren't in daemon mode
202         # before, enter it now.
203         self.daemon = True
204
205         self.listing.updateFeed(feed)
206
207
208 import dbus.mainloop.glib
209 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
210
211 mainloop = gobject.MainLoop()
212 mainthread.init()
213
214 # Acquire our name on the session bus.  If this doesn't work, most
215 # likely another update_feeds instance is already running.  In this
216 # case, just quit.
217 try:
218     bus_name = dbus.service.BusName('org.marcoz.feedingit',
219                                     bus=dbus.SessionBus(),
220                                     do_not_queue=True)
221 except Exception:
222     # We failed to acquire our bus name.  Die.
223     try:
224         dbus_proxy = dbus.SessionBus().get_object(
225             'org.freedesktop.DBus', '/org/freedesktop/DBus')
226         dbus_iface = dbus.Interface(dbus_proxy, 'org.freedesktop.DBus')
227         pid = dbus_iface.GetConnectionUnixProcessID('org.marcoz.feedingit')
228         logger.error("update_feeds already running: pid %d." % pid)
229     except Exception, e:
230         logger.error("Getting pid associated with org.marcoz.feedingit: %s"
231                      % str(e))
232         logger.error("update_feeds already running.")
233
234     sys.exit(1)
235
236 # Run the updater.  Note: we run this until feed.am_updating is false.
237 # Only is this case have all worker threads exited.  If the main
238 # thread exits before all threads have exited and the process gets a
239 # signal, the Python interpreter is unable to handle the signal and it
240 # runs really slow (rescheduling after ever single instruction instead
241 # of every few thousand).
242 feed = FeedUpdate(bus_name)
243 while True:
244     try:
245         mainloop.run()
246     except KeyboardInterrupt:
247         logger.error("Interrupted.  Quitting.")
248         JobManager().quit()
249
250     if not feed.am_updating:
251         break
252