#!/usr/bin/python2 -O # -*- coding: utf-8 -*- """ # DESCRIPTION: # Entropy Object Oriented Interface Copyright (C) 2007-2009 Fabio Erculiani This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from __future__ import with_statement import os import sys import time import gobject import dbus import dbus.service import dbus.mainloop.glib import signal from threading import Lock # Entropy imports sys.path.insert(0,'/usr/lib/entropy/libraries') sys.path.insert(0,'/usr/lib/entropy/server') sys.path.insert(0,'/usr/lib/entropy/client') sys.path.insert(0,'../libraries') sys.path.insert(0,'../server') sys.path.insert(0,'../client') from entropy.misc import TimeScheduled, ParallelTask from entropy.i18n import _ from entropy.exceptions import * import entropy.tools as entropyTools from entropy.client.interfaces import Client from entropy.client.interfaces import Repository as RepoInterface from entropy.transceivers import urlFetcher from entropy.const import etpConst, ETP_LOGPRI_INFO, ETP_LOGLEVEL_NORMAL from entropy.misc import LogFile, TimeScheduled from entropy.core import SystemSettings as SysSet from entropy.output import TextInterface SYS_SETTINGS = SysSet() TEXT = TextInterface() DAEMON_LOGFILE = os.path.join(etpConst['syslogdir'],"client-updater.log") DAEMON_LOG = LogFile(SYS_SETTINGS['system']['log_level']+1, DAEMON_LOGFILE, header = "[client-updater]") DAEMON_DEBUG = False CHECK_DELAY_SECS = 60 if "--debug" in sys.argv: DAEMON_DEBUG = True for xopt in sys.argv: if xopt.startswith("--secs=") and (len(xopt.split("=")) > 1): try: delay_secs = int(xopt.split("=")[1]) CHECK_DELAY_SECS = delay_secs except ValueError: continue class DaemonUrlFetcher(urlFetcher): daemon_last_avg = 100 __average = 0 __downloadedsize = 0 __remotesize = 0 __datatransfer = 0 def handle_statistics(self, th_id, downloaded_size, total_size, average, old_average, update_step, show_speed, data_transfer, time_remaining, time_remaining_secs): self.__average = average self.__downloadedsize = downloaded_size self.__remotesize = total_size self.__datatransfer = data_transfer def updateProgress(self): myavg = abs(int(round(float(self.__average),1))) if abs((myavg - self.daemon_last_avg)) < 1: return if int(myavg) % 10 == 0: DAEMON_LOG.write("fetch @ %s%s" % (myavg,"%",)) self.daemon_last_avg = myavg class Entropy(Client): __previous_progress = '' def init_singleton(self): Client.init_singleton(self, noclientdb = False, load_ugc = False, url_fetcher = DaemonUrlFetcher, repo_validation = False) self.nocolor() self.updateProgress( "Loading Entropy Updates daemon: check every %s secs, logfile: %s" % ( CHECK_DELAY_SECS, DAEMON_LOGFILE,) ) def updateProgress(self, *args, **kwargs): message = args[0] if self.__previous_progress == message: return self.__previous_progress = message DAEMON_LOG.write(message) DAEMON_LOG.flush() if DAEMON_DEBUG: TEXT.updateProgress(*args,**kwargs) class UpdatesDaemon(dbus.service.Object): def __init__(self): if not entropyTools.is_user_in_entropy_group(): raise PermissionDenied('insufficient permissions') self.Entropy = Entropy() self.__alive = False self.__updater = None self.__oncall_updater = None self.__trigger_oncall_updater = False self.__fetch_mutex = Lock() gobject.threads_init() # start dbus service object_path = "/org/entropy/Client" dbus.mainloop.glib.DBusGMainLoop(set_as_default = True) system_bus = dbus.SystemBus() name = dbus.service.BusName("org.entropy.Client", system_bus) dbus.service.Object.__init__ (self, system_bus, object_path) def start(self): self.stop() self.__alive = True self.__updater = gobject.timeout_add( CHECK_DELAY_SECS*1000, self.run_fetcher) self.__oncall_updater = gobject.timeout_add( 1000, self.run_oncall_fetcher) def stop(self): if self.__alive: self.__alive = False if self.__updater != None: gobject.source_remove(self.__updater) if self.__oncall_updater != None: gobject.source_remove(self.__oncall_updater) def do_alert(self, string, msg, urgency = "critical"): TEXT.updateProgress('alert: %s, %s, urgency: %s' % ( string, msg, urgency,)) def run_oncall_fetcher(self): if not self.__trigger_oncall_updater: return self.__alive with self.__fetch_mutex: self.__trigger_oncall_updater = False self.__run_fetcher() return self.__alive def run_fetcher(self): with self.__fetch_mutex: self.__run_fetcher() return self.__alive def __run_fetcher(self): if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: called %s" % (time.time(),)) if self.__updater == None: return 0 # this makes sure we are always using the very best # client db self.Entropy.reopen_client_repository() rc = 0 repos_to_up = self.compare_repositories_status() if repos_to_up: repos = repos_to_up.keys() try: repoConn = self.Entropy.Repositories( repos, fetchSecurity = False, noEquoCheck = True) if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: repository interface loaded") except MissingParameter, e: if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: MissingParameter exception, error: %s" % (e,)) except Exception, e: if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: Unhandled exception, error: %s" % (e,)) else: # -128: sync error, something bad happened # -2: repositories not available (all) # -1: not able to update all the repositories if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: preparing to run sync") rc = repoConn.sync() rc = rc*-1 del repoConn if DAEMON_DEBUG: self.Entropy.updateProgress("__run_fetcher: sync done") if DAEMON_DEBUG: self.Entropy.updateProgress( "__run_fetcher: sync closed, rc: %s" % (rc,)) if rc == 1: err = _("No repositories specified. Cannot check for package updates.") self.do_alert( _("Updates: attention"), err ) return rc elif rc == 2: err = _("Cannot connect to the Updates Service, you're probably not connected to the world.") self.do_alert( _("Updates: connection issues"), err ) return rc elif rc == -1: err = _("Not all the repositories have been fetched for checking") self.do_alert( _("Updates: repository issues"), err ) return rc elif rc == -2: err = _("No repositories found online") self.do_alert( _("Updates: repository issues"), err ) return rc elif rc == -128: err = _("Synchronization errors. Cannot update repositories. Check logs.") self.do_alert( _("Updates: sync issues"), err ) return rc elif isinstance(rc,basestring): self.do_alert( _("Updates: unhandled error"), rc ) return rc try: update, remove, fine = self.Entropy.calculate_world_updates() del fine, remove except Exception, e: entropyTools.print_traceback(f = DAEMON_LOG) msg = "%s: %s" % (_("Updates: error"),e,) self.do_alert(_("Updates: error"), msg) return 1 if update: self.do_alert( _("Updates available"), "%s %d %s" % ( _("There are"),len(update),_("updates available."),), urgency = 'critical' ) self.signal_updates() return 0 # compare repos status for updates @dbus.service.method ( "org.entropy.Client", in_signature = '', out_signature = 'a{si}') def compare_repositories_status(self): repos = {} try: repoConn = self.Entropy.Repositories( noEquoCheck = True, fetchSecurity = False) except MissingParameter: return repos except Exception, e: return repos # now get remote for repoid in self.Entropy.SystemSettings['repositories']['available']: self.Entropy.update_repository_revision(repoid) if repoConn.is_repository_updatable(repoid): if DAEMON_DEBUG: self.Entropy.updateProgress( "compare_repositories_status: repo needs to be updated: %s" % (repoid,)) self.Entropy.repository_move_clear_cache(repoid) repo_rev = self.Entropy.get_repository_revision(repoid) online_rev = repoConn.get_online_repository_revision(repoid) repos[repoid] = { 'local': repo_rev, 'remote': online_rev, } del repoConn return repos # signal sent when updates are available for retrive @dbus.service.signal(dbus_interface = 'org.entropy.Client', signature = '') def signal_updates(self): pass if __name__ == "__main__": signal.signal(signal.SIGINT, signal.SIG_DFL) ud = UpdatesDaemon() ud.start() mainloop = gobject.MainLoop() mainloop.run() ud.stop() raise SystemExit(0)