Package entropy :: Package client :: Package interfaces :: Module client

Source Code for Module entropy.client.interfaces.client

  1  # -*- coding: utf-8 -*- 
  2  ''' 
  3      # DESCRIPTION: 
  4      # Entropy Object Oriented Interface 
  5   
  6      Copyright (C) 2007-2009 Fabio Erculiani 
  7   
  8      This program is free software; you can redistribute it and/or modify 
  9      it under the terms of the GNU General Public License as published by 
 10      the Free Software Foundation; either version 2 of the License, or 
 11      (at your option) any later version. 
 12   
 13      This program 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.  See the 
 16      GNU General Public License for more details. 
 17   
 18      You should have received a copy of the GNU General Public License 
 19      along with this program; if not, write to the Free Software 
 20      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 21  ''' 
 22   
 23  from __future__ import with_statement 
 24  import os 
 25  import sys 
 26  from entropy.core import Singleton 
 27  from entropy.output import TextInterface 
 28  from entropy.db import dbapi2 
 29  from entropy.client.interfaces.loaders import LoadersMixin 
 30  from entropy.client.interfaces.cache import CacheMixin 
 31  from entropy.client.interfaces.dep import CalculatorsMixin 
 32  from entropy.client.interfaces.methods import RepositoryMixin, MiscMixin, \ 
 33      MatchMixin 
 34  from entropy.client.interfaces.fetch import FetchersMixin 
 35  from entropy.const import etpConst, etpCache, etpUi, const_debug_write 
 36  from entropy.core import SystemSettings, SystemSettingsPlugin 
 37  from entropy.misc import LogFile 
 38  from entropy.exceptions import SystemDatabaseError, RepositoryError 
 39   
40 -class ClientSystemSettingsPlugin(SystemSettingsPlugin):
41 42 import entropy.tools as entropyTools 43
44 - def __init__(self, plugin_id, helper_interface):
45 SystemSettingsPlugin.__init__(self, plugin_id, helper_interface) 46 self.__repos_files = {} 47 self.__repos_mtime = {} 48 # this is a workaround to avoid running 49 # some post_setup hooks at class init 50 self.__hooks_on_init = True
51
52 - def __setup_repos_files(self, system_settings):
53 """ 54 This function collects available repositories configuration files 55 by filling internal dict() __repos_files and __repos_mtime. 56 57 @param system_settings: SystemSettings instance 58 @type system_settings: instance of SystemSettings 59 @return: None 60 @rtype: None 61 """ 62 63 self.__repos_mtime = { 64 'repos_license_whitelist': {}, 65 'repos_mask': {}, 66 'repos_system_mask': {}, 67 'repos_critical_updates': {}, 68 } 69 self.__repos_files = { 70 'repos_license_whitelist': {}, 71 'repos_mask': {}, 72 'repos_system_mask': {}, 73 'conflicting_tagged_packages': {}, 74 'repos_critical_updates': {}, 75 } 76 77 dmp_dir = etpConst['dumpstoragedir'] 78 for repoid in system_settings['repositories']['order']: 79 80 repos_mask_setting = {} 81 repos_mask_mtime = {} 82 repos_lic_wl_setting = {} 83 repos_lic_wl_mtime = {} 84 repo_data = system_settings['repositories']['available'][repoid] 85 repos_sm_mask_setting = {} 86 repos_sm_mask_mtime = {} 87 confl_tagged = {} 88 repos_critical_updates_setting = {} 89 repos_critical_updates_mtime = {} 90 91 maskpath = os.path.join(repo_data['dbpath'], 92 etpConst['etpdatabasemaskfile']) 93 wlpath = os.path.join(repo_data['dbpath'], 94 etpConst['etpdatabaselicwhitelistfile']) 95 sm_path = os.path.join(repo_data['dbpath'], 96 etpConst['etpdatabasesytemmaskfile']) 97 ct_path = os.path.join(repo_data['dbpath'], 98 etpConst['etpdatabaseconflictingtaggedfile']) 99 critical_path = os.path.join(repo_data['dbpath'], 100 etpConst['etpdatabasecriticalfile']) 101 102 if os.access(maskpath, os.R_OK | os.F_OK): 103 repos_mask_setting[repoid] = maskpath 104 repos_mask_mtime[repoid] = dmp_dir + "/repo_" + \ 105 repoid + "_" + etpConst['etpdatabasemaskfile'] + ".mtime" 106 107 if os.access(wlpath, os.R_OK | os.F_OK): 108 repos_lic_wl_setting[repoid] = wlpath 109 repos_lic_wl_mtime[repoid] = dmp_dir + "/repo_" + \ 110 repoid + "_" + etpConst['etpdatabaselicwhitelistfile'] + \ 111 ".mtime" 112 113 if os.access(sm_path, os.R_OK | os.F_OK): 114 repos_sm_mask_setting[repoid] = sm_path 115 repos_sm_mask_mtime[repoid] = dmp_dir + "/repo_" + \ 116 repoid + "_" + etpConst['etpdatabasesytemmaskfile'] + \ 117 ".mtime" 118 if os.access(ct_path, os.R_OK | os.F_OK): 119 confl_tagged[repoid] = ct_path 120 121 if os.access(critical_path, os.R_OK | os.F_OK): 122 repos_critical_updates_setting[repoid] = critical_path 123 repos_critical_updates_mtime[repoid] = dmp_dir + "/repo_" + \ 124 repoid + "_" + etpConst['etpdatabasecriticalfile'] + \ 125 ".mtime" 126 127 self.__repos_files['repos_mask'].update(repos_mask_setting) 128 self.__repos_mtime['repos_mask'].update(repos_mask_mtime) 129 130 self.__repos_files['repos_license_whitelist'].update( 131 repos_lic_wl_setting) 132 self.__repos_mtime['repos_license_whitelist'].update( 133 repos_lic_wl_mtime) 134 135 self.__repos_files['repos_system_mask'].update( 136 repos_sm_mask_setting) 137 self.__repos_mtime['repos_system_mask'].update( 138 repos_sm_mask_mtime) 139 140 self.__repos_files['conflicting_tagged_packages'].update( 141 confl_tagged) 142 143 self.__repos_files['repos_critical_updates'].update( 144 repos_critical_updates_setting) 145 self.__repos_mtime['repos_critical_updates'].update( 146 repos_critical_updates_mtime)
147
148 - def __run_post_branch_migration_hooks(self, sys_settings_instance):
149 150 # only root can do this 151 if os.getuid() != 0: 152 return 153 154 old_branch_path = etpConst['etp_previous_branch_file'] 155 current_branch = sys_settings_instance['repositories']['branch'] 156 157 def write_current_branch(branch): 158 old_brf = open(old_branch_path, "w") 159 old_brf.write(current_branch+"\n") 160 old_brf.flush() 161 old_brf.close()
162 163 if not os.access(old_branch_path, os.F_OK | os.R_OK): 164 write_current_branch(current_branch) 165 return 166 167 old_f = open(old_branch_path, "r") 168 old_branch = old_f.readline().strip() 169 old_f.close() 170 171 if old_branch == current_branch: # all fine, no need to run 172 return 173 174 repos, err = self._helper.run_repositories_post_branch_switch_hooks( 175 old_branch, current_branch) 176 if not err: 177 write_current_branch(current_branch)
178
179 - def __run_post_branch_upgrade_hooks(self, sys_settings_instance):
180 181 # only root can do this 182 if os.getuid() != 0: 183 return 184 185 # look for updates 186 # critical_updates = False is needed to avoid 187 # issues with metadata not being available 188 try: 189 update, remove, fine, spm_fine = self._helper.calculate_world_updates( 190 critical_updates = False) 191 except (ValueError,): 192 update = 1 # foo! 193 194 # actually execute this only if 195 # there are no updates left 196 if not update: 197 self._helper.run_repository_post_branch_upgrade_hooks()
198
199 - def system_mask_parser(self, system_settings_instance):
200 201 parser_data = {} 202 # match installed packages of system_mask 203 mask_installed = [] 204 mask_installed_keys = {} 205 while (self._helper.clientDbconn != None): 206 try: 207 self._helper.clientDbconn.validateDatabase() 208 except SystemDatabaseError: 209 break 210 mc_cache = set() 211 repos_mask_list = self.__repositories_system_mask( 212 system_settings_instance) 213 m_list = repos_mask_list + system_settings_instance['system_mask'] 214 for atom in m_list: 215 m_ids, m_r = self._helper.clientDbconn.atomMatch(atom, 216 multiMatch = True) 217 if m_r != 0: 218 continue 219 mykey = self.entropyTools.dep_getkey(atom) 220 if mykey not in mask_installed_keys: 221 mask_installed_keys[mykey] = set() 222 for m_id in m_ids: 223 if m_id in mc_cache: 224 continue 225 mc_cache.add(m_id) 226 mask_installed.append(m_id) 227 mask_installed_keys[mykey].add(m_id) 228 break 229 230 parser_data.update({ 231 'repos_installed': mask_installed, 232 'repos_installed_keys': mask_installed_keys, 233 }) 234 return parser_data
235
236 - def masking_validation_parser(self, system_settings_instance):
237 data = { 238 'cache': {}, # package masking validation cache 239 } 240 return data
241
242 - def __repositories_system_mask(self, sys_settings_instance):
243 """ 244 Parser returning system packages mask metadata read from 245 packages.db.system_mask file inside the repository directory. 246 This file contains packages that should be always kept 247 installed, extending the already defined (in repository database) 248 set of atoms. 249 """ 250 system_mask = [] 251 for repoid in self.__repos_files['repos_system_mask']: 252 sys_settings_instance.validate_entropy_cache( 253 self.__repos_files['repos_system_mask'][repoid], 254 self.__repos_mtime['repos_system_mask'][repoid], 255 repoid = repoid) 256 system_mask += [x for x in \ 257 self.entropyTools.generic_file_content_parser( 258 self.__repos_files['repos_system_mask'][repoid]) if x \ 259 not in system_mask] 260 return system_mask
261
262 - def repositories_parser(self, sys_settings_instance):
263 """ 264 Parser that generates repository settings metadata. 265 266 @param sys_settings_instance: SystemSettings instance 267 @type sys_settings_instance: instance of SystemSettings 268 @return: parsed metadata 269 @rtype: dict 270 """ 271 272 # fill repositories metadata dictionaries 273 self.__setup_repos_files(sys_settings_instance) 274 275 data = { 276 'license_whitelist': {}, 277 'mask': {}, 278 'system_mask': [], 279 'critical_updates': {}, 280 'conflicting_tagged_packages': {}, 281 } 282 283 # parse license whitelist 284 """ 285 Parser returning licenses considered accepted by default 286 (= GPL compatibles) read from package.lic_whitelist. 287 """ 288 for repoid in self.__repos_files['repos_license_whitelist']: 289 sys_settings_instance.validate_entropy_cache( 290 self.__repos_files['repos_license_whitelist'][repoid], 291 self.__repos_mtime['repos_license_whitelist'][repoid], 292 repoid = repoid) 293 294 data['license_whitelist'][repoid] = \ 295 self.entropyTools.generic_file_content_parser( 296 self.__repos_files['repos_license_whitelist'][repoid]) 297 298 # package masking 299 """ 300 Parser returning packages masked at repository level read from 301 packages.db.mask inside the repository database directory. 302 """ 303 for repoid in self.__repos_files['repos_mask']: 304 sys_settings_instance.validate_entropy_cache( 305 self.__repos_files['repos_mask'][repoid], 306 self.__repos_mtime['repos_mask'][repoid], repoid = repoid) 307 308 data['mask'][repoid] = \ 309 self.entropyTools.generic_file_content_parser( 310 self.__repos_files['repos_mask'][repoid]) 311 312 # system masking 313 data['system_mask'] = self.__repositories_system_mask( 314 sys_settings_instance) 315 316 # critical updates 317 """ 318 Parser returning critical packages list metadata read from 319 packages.db.critical file inside the repository directory. 320 This file contains packages that should be always updated 321 before anything else. 322 """ 323 for repoid in self.__repos_files['repos_critical_updates']: 324 sys_settings_instance.validate_entropy_cache( 325 self.__repos_files['repos_critical_updates'][repoid], 326 self.__repos_mtime['repos_critical_updates'][repoid], 327 repoid = repoid) 328 data['critical_updates'][repoid] = \ 329 self.entropyTools.generic_file_content_parser( 330 self.__repos_files['repos_critical_updates'][repoid]) 331 332 333 # conflicts map 334 """ 335 Parser returning packages that could have been installed because 336 they aren't in the same scope, but ending up creating critical 337 issues. You can see it as a configurable conflict map. 338 """ 339 # keep priority order 340 repoids = [x for x in sys_settings_instance['repositories']['order'] \ 341 if x in self.__repos_files['conflicting_tagged_packages']] 342 for repoid in repoids: 343 filepath = self.__repos_files['conflicting_tagged_packages'].get( 344 repoid) 345 if os.access(filepath, os.R_OK | os.F_OK): 346 confl_f = open(filepath,"r") 347 content = confl_f.readlines() 348 confl_f.close() 349 content = [x.strip().rsplit("#", 1)[0].strip().split() for x \ 350 in content if not x.startswith("#") and x.strip()] 351 for mydata in content: 352 if len(mydata) < 2: 353 continue 354 data['conflicting_tagged_packages'][mydata[0]] = mydata[1:] 355 356 return data
357 358
359 - def misc_parser(self, sys_settings_instance):
360 361 """ 362 Parses Entropy client system configuration file. 363 364 @return dict data 365 """ 366 367 data = { 368 'filesbackup': etpConst['filesbackup'], 369 'forcedupdates': etpConst['forcedupdates'], 370 'ignore_spm_downgrades': etpConst['spm']['ignore-spm-downgrades'], 371 'collisionprotect': etpConst['collisionprotect'], 372 'configprotect': etpConst['configprotect'][:], 373 'configprotectmask': etpConst['configprotectmask'][:], 374 'configprotectskip': etpConst['configprotectskip'][:], 375 } 376 377 cli_conf = etpConst['clientconf'] 378 if not (os.path.isfile(cli_conf) and os.access(cli_conf, os.R_OK)): 379 return data 380 381 client_f = open(cli_conf,"r") 382 clientconf = [x.strip() for x in client_f.readlines() if \ 383 x.strip() and not x.strip().startswith("#")] 384 client_f.close() 385 for line in clientconf: 386 387 split_line = line.split("|") 388 split_line_len = len(split_line) 389 390 if line.startswith("filesbackup|") and (split_line_len == 2): 391 392 compatopt = split_line[1].strip().lower() 393 if compatopt in ("disable", "disabled","false", "0", "no",): 394 data['filesbackup'] = False 395 396 if line.startswith("forcedupdates|") and (split_line_len == 2): 397 398 compatopt = split_line[1].strip().lower() 399 if compatopt in ("disable", "disabled","false", "0", "no",): 400 data['forcedupdates'] = False 401 else: 402 data['forcedupdates'] = True 403 404 elif line.startswith("ignore-spm-downgrades|") and \ 405 (split_line_len == 2): 406 407 compatopt = split_line[1].strip().lower() 408 if compatopt in ("enable", "enabled", "true", "1", "yes"): 409 data['ignore_spm_downgrades'] = True 410 411 elif line.startswith("collisionprotect|") and (split_line_len == 2): 412 413 collopt = split_line[1].strip() 414 if collopt.lower() in ("0", "1", "2",): 415 data['collisionprotect'] = int(collopt) 416 417 elif line.startswith("configprotect|") and (split_line_len == 2): 418 419 configprotect = split_line[1].strip() 420 for myprot in configprotect.split(): 421 data['configprotect'].append( 422 unicode(myprot,'raw_unicode_escape')) 423 424 elif line.startswith("configprotectmask|") and \ 425 (split_line_len == 2): 426 427 configprotect = split_line[1].strip() 428 for myprot in configprotect.split(): 429 data['configprotectmask'].append( 430 unicode(myprot,'raw_unicode_escape')) 431 432 elif line.startswith("configprotectskip|") and \ 433 (split_line_len == 2): 434 435 configprotect = split_line[1].strip() 436 for myprot in configprotect.split(): 437 data['configprotectskip'].append( 438 etpConst['systemroot']+unicode(myprot, 439 'raw_unicode_escape')) 440 441 return data
442
443 - def post_setup(self, system_settings_instance):
444 """ 445 Reimplemented from SystemSettingsPlugin. 446 """ 447 if not self.__hooks_on_init: 448 # run post-branch migration scripts if branch setting got changed 449 self.__run_post_branch_migration_hooks(system_settings_instance) 450 # run post-branch upgrade migration scripts if the function 451 # above created migration files to handle 452 self.__run_post_branch_upgrade_hooks(system_settings_instance) 453 self.__hooks_on_init = False
454 455
456 -class Client(Singleton, TextInterface, LoadersMixin, CacheMixin, CalculatorsMixin, \ 457 RepositoryMixin, MiscMixin, MatchMixin, FetchersMixin):
458
459 - def init_singleton(self, indexing = True, noclientdb = 0, 460 xcache = True, user_xcache = False, repo_validation = True, 461 load_ugc = True, url_fetcher = None, 462 multiple_url_fetcher = None):
463 464 const_debug_write(__name__, "debug enabled") 465 self.sys_settings_client_plugin_id = \ 466 etpConst['system_settings_plugins_ids']['client_plugin'] 467 self.__instance_destroyed = False 468 self.atomMatchCacheKey = etpCache['atomMatch'] 469 self.dbapi2 = dbapi2 # export for third parties 470 self.FileUpdates = None 471 self.validRepositories = [] 472 self.UGC = None 473 # supporting external updateProgress stuff, you can point self.progress 474 # to your progress bar and reimplement updateProgress 475 self.progress = None 476 self.clientDbconn = None 477 self.safe_mode = 0 478 self.indexing = indexing 479 self.repo_validation = repo_validation 480 self.noclientdb = False 481 self.openclientdb = True 482 483 # setup package settings (masking and other stuff) 484 self.SystemSettings = SystemSettings() 485 const_debug_write(__name__, "SystemSettings loaded") 486 487 # modules import 488 import entropy.dump as dumpTools 489 import entropy.tools as entropyTools 490 self.dumpTools = dumpTools 491 self.entropyTools = entropyTools 492 self.clientLog = LogFile(level = self.SystemSettings['system']['log_level'], 493 filename = etpConst['equologfile'], header = "[client]") 494 495 self.MultipleUrlFetcher = multiple_url_fetcher 496 self.urlFetcher = url_fetcher 497 if self.urlFetcher == None: 498 from entropy.transceivers import UrlFetcher 499 self.urlFetcher = UrlFetcher 500 if self.MultipleUrlFetcher == None: 501 from entropy.transceivers import MultipleUrlFetcher 502 self.MultipleUrlFetcher = MultipleUrlFetcher 503 504 from entropy.cache import EntropyCacher 505 self.Cacher = EntropyCacher() 506 507 from entropy.client.misc import FileUpdates 508 self.FileUpdates = FileUpdates(self) 509 510 from entropy.client.mirrors import StatusInterface 511 # mirror status interface 512 self.MirrorStatus = StatusInterface() 513 514 if noclientdb in (False,0): 515 self.noclientdb = False 516 elif noclientdb in (True,1): 517 self.noclientdb = True 518 elif noclientdb == 2: 519 self.noclientdb = True 520 self.openclientdb = False 521 522 # load User Generated Content Interface 523 if load_ugc: 524 from entropy.client.services.ugc.interfaces import Client as ugc_cl 525 self.UGC = ugc_cl(self) 526 527 # class init 528 LoadersMixin.__init__(self) 529 530 self.xcache = xcache 531 shell_xcache = os.getenv("ETP_NOCACHE") 532 if shell_xcache: 533 self.xcache = False 534 535 do_validate_repo_cache = False 536 # now if we are on live, we should disable it 537 # are we running on a livecd? (/proc/cmdline has "cdroot") 538 if self.entropyTools.islive(): 539 self.xcache = False 540 elif (not self.entropyTools.is_user_in_entropy_group()) and not user_xcache: 541 self.xcache = False 542 elif not user_xcache: 543 do_validate_repo_cache = True 544 545 if not self.xcache and (self.entropyTools.is_user_in_entropy_group()): 546 try: 547 self.purge_cache(False) 548 except: 549 pass 550 551 if self.openclientdb: 552 self.open_client_repository() 553 554 # create our SystemSettings plugin 555 self.sys_settings_client_plugin = ClientSystemSettingsPlugin( 556 self.sys_settings_client_plugin_id, self) 557 558 # needs to be started here otherwise repository cache will be 559 # always dropped 560 if self.xcache: 561 self.Cacher.start() 562 563 if do_validate_repo_cache: 564 self.validate_repositories_cache() 565 566 if self.repo_validation: 567 self.validate_repositories() 568 else: 569 self.validRepositories.extend( 570 self.SystemSettings['repositories']['order']) 571 572 # add our SystemSettings plugin 573 # Make sure we connect Entropy Client plugin AFTER client db init 574 self.SystemSettings.add_plugin(self.sys_settings_client_plugin) 575 576 const_debug_write(__name__, "singleton loaded")
577 578
579 - def destroy(self):
580 self.__instance_destroyed = True 581 if hasattr(self,'clientDbconn'): 582 if self.clientDbconn != None: 583 self.clientDbconn.closeDB() 584 del self.clientDbconn 585 if hasattr(self,'FileUpdates'): 586 del self.FileUpdates 587 if hasattr(self,'clientLog'): 588 self.clientLog.close() 589 if hasattr(self,'Cacher'): 590 self.Cacher.stop() 591 if hasattr(self,'SystemSettings') and \ 592 hasattr(self,'sys_settings_client_plugin_id'): 593 594 if hasattr(self.SystemSettings,'remove_plugin'): 595 try: 596 self.SystemSettings.remove_plugin( 597 self.sys_settings_client_plugin_id) 598 except KeyError: 599 pass 600 601 self.close_all_repositories(mask_clear = False) 602 self.closeAllSecurity() 603 self.closeAllQA()
604
605 - def is_destroyed(self):
606 return self.__instance_destroyed
607
608 - def __del__(self):
609 self.destroy()
610