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