Merged from trunk 6539:6550
authorFabian Groffen <grobian@gentoo.org>
Wed, 23 May 2007 16:27:03 +0000 (16:27 -0000)
committerFabian Groffen <grobian@gentoo.org>
Wed, 23 May 2007 16:27:03 +0000 (16:27 -0000)
   +------------------------------------------------------------------------+
   | 6540    | Don't protect USE_EXPAND variables from -* anymore because   |
   | zmedico | the really critical ones should all be in use.force now      |
   |         | (typically kernel_linux, elibc_glibc, and userland_GNU).     |
   |---------+--------------------------------------------------------------|
   | 6541    | For bug #162404, use a pseudo-terminal device pair (instead  |
   | zmedico | of a normal pipe) for moving ouput out of the sesandbox      |
   |         | domain.                                                      |
   |---------+--------------------------------------------------------------|
   | 6542    | wrap lines                                                   |
   | zmedico |                                                              |
   |---------+--------------------------------------------------------------|
   | 6543    | Check to see if I have svn.g.o fixed, no actual file         |
   | robbat2 | changes.                                                     |
   |---------+--------------------------------------------------------------|
   | 6544    | Check to see if I have svn.g.o fixed, no actual file         |
   | robbat2 | changes, pass #2                                             |
   |---------+--------------------------------------------------------------|
   | 6545    | Add registration support for preserved library objects, move |
   | genone  | the library->consumer map code out of vardbapi and fix a bug |
   |         | in the setup code for preserve-libs and collision-protect    |
   |         | caused by using relative paths with os.path.islink()         |
   |---------+--------------------------------------------------------------|
   | 6546    | - store newsid in unread file, not the path to the news item |
   | genone  | - use octal numbers to specify file permissions - use        |
   |         | descriptive exceptions when (not) creating NewsItem objects  |
   |         | - code style cleanups                                        |
   |---------+--------------------------------------------------------------|
   | 6547    | Display notification about unread news in all situations     |
   | genone  | specified by glep 42                                         |
   |---------+--------------------------------------------------------------|
   | 6548    | Enable elog functionality for the python side of portage     |
   | genone  |                                                              |
   |---------+--------------------------------------------------------------|
   | 6549    | Update for glep42, preserve-libs and elog                    |
   | genone  |                                                              |
   |---------+--------------------------------------------------------------|
   | 6550    | Catch a potential OSError if the child process has already   |
   | zmedico | died.                                                        |
   +------------------------------------------------------------------------+

svn path=/main/branches/prefix/; revision=6593

15 files changed:
NEWS
pym/emerge/__init__.py
pym/portage/__init__.py
pym/portage/dbapi/vartree.py
pym/portage/elog/__init__.py
pym/portage/elog/filtering.py [new file with mode: 0644]
pym/portage/elog/messages.py [new file with mode: 0644]
pym/portage/elog/mod_custom.py
pym/portage/elog/mod_echo.py
pym/portage/elog/mod_mail.py
pym/portage/elog/mod_mail_summary.py
pym/portage/elog/mod_save.py
pym/portage/elog/mod_save_summary.py
pym/portage/elog/mod_syslog.py
pym/portage/news.py

diff --git a/NEWS b/NEWS
index 7b253bd5c81753dd071571d0796f8ce0af7f12fb..e0f8d66c0ca62c2ef310e6bb74d3b4f51cafff43 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,10 @@ portage-2.1.3
 * Enable "echo" and "save_summary" elog modules by default
 * Fix -* handling in package.keywords to work as intended (reset the accepted 
   keywords list), also see RELEASE-NOTES.
+* Experimental support for preserving old library files on package upgrades
+  based on FEATURES=preserve-libs, USE AT YOUR OWN RISK!!!
+* Make elog functionality available to python code
+* Add support for news items (GLEP 42)
 
 portage-2.1.2
 -------------
index cf2b6e786aa2144c26ec09fc80c04ad7a55526be..0109f848818e06cce952cea2532a1e15b528c9db 100644 (file)
@@ -31,7 +31,7 @@ except ImportError:
        import portage
 del os.environ["PORTAGE_LEGACY_GLOBALS"]
 from portage import digraph, portdbapi
-from portage.const import NEWS_LIB_PATH
+from portage.const import NEWS_LIB_PATH, CACHE_PATH
 
 import emerge.help
 import portage.xpak, commands, errno, re, socket, time, types
@@ -3725,6 +3725,26 @@ def chk_updated_info_files(root, infodirs, prev_mtimes, retval):
                                print " "+green("*")+" Processed",icount,"info files."
 
 
+def display_news_notification(settings):
+       target_root = settings["ROOT"]
+       NEWS_PATH = os.path.join("metadata", "news")
+       UNREAD_PATH = os.path.join(target_root, NEWS_LIB_PATH, "news")
+       porttree = portdbapi(porttree_root=settings["PORTDIR"], mysettings=settings)
+       newsReaderDisplay = False
+       
+       print
+       for repo in porttree.getRepositories():
+               unreadItems = checkUpdatedNewsItems(target_root, NEWS_PATH, UNREAD_PATH, repo)
+               if unreadItems:
+                       print colorize("WARN", " * IMPORTANT:"),
+                       print "%s news items need reading for repository '%s'." % (unreadItems, repo)
+                       newsReaderDisplay = True
+       
+       if newsReaderDisplay:
+               print colorize("WARN", " *"),
+               print "Use " + colorize("GOOD", "eselect news") + " to read news items."
+
+       print
 def post_emerge(settings, mtimedb, retval):
        """
        Misc. things to run at the end of a merge session.
@@ -3733,6 +3753,7 @@ def post_emerge(settings, mtimedb, retval):
        Update Config Files
        Update News Items
        Commit mtimeDB
+       Display preserved libs warnings
        Exit Emerge
        
        @param settings: Configuration settings (typically portage.settings)
@@ -3767,21 +3788,19 @@ def post_emerge(settings, mtimedb, retval):
                chk_updated_info_files(normalize_path(target_root + EPREFIX), infodirs, info_mtimes, retval)
        chk_updated_cfg_files(normalize_path(target_root + EPREFIX), config_protect)
        
-       NEWS_PATH = os.path.join( "metadata", "news" )
-       UNREAD_PATH = os.path.join( target_root, NEWS_LIB_PATH, 'news')
-       porttree = portdbapi( porttree_root = settings["PORTDIR"], mysettings = settings )
-       newsReaderDisplay = False
+       display_news_notification(settings)
        
-       for repo in porttree.getRepositories():
-               unreadItems = checkUpdatedNewsItems(target_root, NEWS_PATH, UNREAD_PATH, repo)
-               if unreadItems:
-                       print colorize("WARN", " * IMPORTANT:"),
-                       print "%s news items need reading for repository '%s'." % (unreadItems, repo)
-                       newsReaderDisplay = True
-       
-       if newsReaderDisplay:
-               print colorize("WARN", " *"),
-               print "Use " + colorize("GOOD", "eselect news") + " to read news items."
+       from portage.dbapi.vartree import PreservedLibsRegistry
+       plib_registry = PreservedLibsRegistry(os.path.join(target_root, CACHE_PATH, "preserved_libs_registry"))
+       if plib_registry.hasEntries():
+               print colorize("WARN", "!!!") + " existing preserved libs:"
+               plibdata = plib_registry.getPreservedLibs()
+               for cpv in plibdata.keys():
+                       print colorize("WARN", ">>>") + " package: %s" % cpv
+                       for f in plibdata[cpv]:
+                               print colorize("WARN", " * ") + " - %s" % f
+               print "Use " + colorize("GOOD", "revdep-rebuild") + " to rebuild packages using these libraries"
+               print "and then remerge the packages listed above."
        
        mtimedb.commit()
        sys.exit(retval)
@@ -4368,6 +4387,8 @@ def action_sync(settings, trees, mtimedb, myopts, myaction):
                print red(" * ")+"configuration files."
                print red(" * ")+"To update portage, run 'emerge portage'."
                print
+       
+       display_news_notification(settings)
 
 def action_metadata(settings, portdb, myopts):
        portage.writemsg_stdout("\n>>> Updating Portage cache:      ")
@@ -5658,10 +5679,14 @@ def emerge_main():
        # "update", "system", or just process files:
        else:
                validate_ebuild_environment(trees)
+               if "--pretend" not in myopts:
+                       display_news_notification(settings)
                action_build(settings, trees, mtimedb,
                        myopts, myaction, myfiles, spinner)
                if "--pretend" not in myopts:
                        post_emerge(settings, mtimedb, 0)
+               else:
+                       display_news_notification(settings)
 
 if __name__ == "__main__":
        retval = emerge_main()
index f40d40102ba2f4a4e57f35eec072424c874f5f51..fd423dfc84fb818e920ad7a7205a9d478d764d17 100644 (file)
@@ -1970,7 +1970,7 @@ class config:
                        mysplit = curdb["USE"].split()
                        for x in mysplit:
                                if x == "-*":
-                                       myflags = use_expand_protected[:]
+                                       myflags = []
                                        continue
 
                                if x[0] == "+":
@@ -2267,10 +2267,12 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
        # from shells and from binaries that belong to portage (the number of entry
        # points is minimized).  The "tee" binary is not among the allowed entry
        # points, so it is spawned outside of the sesandbox domain and reads from a
-       # pipe between two domains.
+       # pseudo-terminal that connects two domains.
        logfile = keywords.get("logfile")
        mypids = []
-       pw = None
+       slave_fd = None
+       output_pid = None
+       input_pid = None
        if logfile:
                del keywords["logfile"]
                fd_pipes = keywords.get("fd_pipes")
@@ -2278,26 +2280,42 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
                        fd_pipes = {0:0, 1:1, 2:2}
                elif 1 not in fd_pipes or 2 not in fd_pipes:
                        raise ValueError(fd_pipes)
-               pr, pw = os.pipe()
-               mypids.extend(portage.process.spawn(('tee', '-i', '-a', logfile),
-                        returnpid=True, fd_pipes={0:pr, 1:fd_pipes[1], 2:fd_pipes[2]}))
-               os.close(pr)
-               fd_pipes[1] = pw
-               fd_pipes[2] = pw
+               from pty import openpty
+               master_fd, slave_fd = openpty()
+               # Disable the ECHO attribute so the terminal behaves properly
+               # if the subprocess needs to read input from stdin.
+               import termios
+               term_attr = termios.tcgetattr(master_fd)
+               term_attr[3] &= ~termios.ECHO
+               termios.tcsetattr(master_fd, termios.TCSAFLUSH, term_attr)
+               # tee will always exit with an IO error, so ignore it's stderr.
+               null_file = open('/dev/null', 'w')
+               mypids.extend(portage.process.spawn(['tee', '-i', '-a', logfile],
+                       returnpid=True, fd_pipes={0:master_fd, 1:fd_pipes[1],
+                       2:null_file.fileno()}))
+               output_pid = mypids[-1]
+               mypids.extend(portage.process.spawn(['cat'],
+                       returnpid=True, fd_pipes={0:fd_pipes[0], 1:master_fd,
+                       2:null_file.fileno()}))
+               input_pid = mypids[-1]
+               os.close(master_fd)
+               null_file.close()
+               fd_pipes[0] = slave_fd
+               fd_pipes[1] = slave_fd
+               fd_pipes[2] = slave_fd
                keywords["fd_pipes"] = fd_pipes
 
        features = mysettings.features
-       # XXX: Negative RESTRICT word
-       droppriv=(droppriv and ("userpriv" in features) and not \
-               (("nouserpriv" in mysettings["RESTRICT"].split()) or \
-                ("userpriv" in mysettings["RESTRICT"].split())))
-
+       restrict = mysettings.get("RESTRICT", "").split()
+       droppriv=(droppriv and "userpriv" in features and not \
+               ("nouserpriv" in restrict or "userpriv" in restrict))
        if droppriv and not uid and portage_gid and portage_uid:
-               keywords.update({"uid":portage_uid,"gid":portage_gid,"groups":userpriv_groups,"umask":002})
-
+               keywords.update({"uid":portage_uid,"gid":portage_gid,
+                       "groups":userpriv_groups,"umask":002})
        if not free:
                free=((droppriv and "usersandbox" not in features) or \
-                       (not droppriv and "sandbox" not in features and "usersandbox" not in features))
+                       (not droppriv and "sandbox" not in features and \
+                       "usersandbox" not in features))
 
        if free:
                keywords["opt_name"] += " bash"
@@ -2308,7 +2326,8 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
 
        if sesandbox:
                con = selinux.getcontext()
-               con = con.replace(mysettings["PORTAGE_T"], mysettings["PORTAGE_SANDBOX_T"])
+               con = con.replace(mysettings["PORTAGE_T"],
+                       mysettings["PORTAGE_SANDBOX_T"])
                selinux.setexec(con)
 
        returnpid = keywords.get("returnpid")
@@ -2316,29 +2335,35 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, **keyw
        try:
                mypids.extend(spawn_func(mystring, env=env, **keywords))
        finally:
-               if pw:
-                       os.close(pw)
+               if slave_fd:
+                       os.close(slave_fd)
                if sesandbox:
                        selinux.setexec(None)
 
        if returnpid:
                return mypids
 
-       while mypids:
-               pid = mypids.pop(0)
-               retval = os.waitpid(pid, 0)[1]
-               portage.process.spawned_pids.remove(pid)
-               if retval != os.EX_OK:
-                       for pid in mypids:
-                               if os.waitpid(pid, os.WNOHANG) == (0,0):
-                                       import signal
-                                       os.kill(pid, signal.SIGTERM)
-                                       os.waitpid(pid, 0)
-                               portage.process.spawned_pids.remove(pid)
-                       if retval & 0xff:
-                               return (retval & 0xff) << 8
-                       return retval >> 8
-       return os.EX_OK
+       if output_pid:
+               # tee will exit when the other end of the pseudo-terminal is closed.
+               os.waitpid(output_pid, 0)
+               portage.process.spawned_pids.remove(output_pid)
+       if input_pid:
+               # cat is blocking on stdin, so it must be killed.
+               import signal
+               try:
+                       os.kill(input_pid, signal.SIGTERM)
+               except OSError:
+                       pass # it died by itself
+               os.waitpid(input_pid, 0)
+               portage.process.spawned_pids.remove(input_pid)
+       pid = mypids[-1]
+       retval = os.waitpid(pid, 0)[1]
+       portage.process.spawned_pids.remove(pid)
+       if retval != os.EX_OK:
+               if retval & 0xff:
+                       return (retval & 0xff) << 8
+               return retval >> 8
+       return retval
 
 def fetch(myuris, mysettings, listonly=0, fetchonly=0, locks_in_subdir=".locks",use_locks=1, try_mirrors=1):
        "fetch files.  Will use digest file if available."
index 39a2845619951449e0c57bbc438eb0abc3801885..9aa9b19093a15690d99f427d22710798621d918b 100644 (file)
@@ -23,6 +23,133 @@ from portage import listdir, dep_expand, config, flatten, key_expand, \
 import os, sys, stat, cPickle, errno, commands, copy, time
 from itertools import izip
 
+
+class PreservedLibsRegistry(object):
+       """ This class handles the tracking of preserved library objects """
+       def __init__(self, filename, autocommit=True):
+               """ @param filename: absolute path for saving the preserved libs records
+                   @type filename: String
+                       @param autocommit: determines if the file is written after every update
+                       @type autocommit: Boolean
+               """
+               self._filename = filename
+               self._autocommit = autocommit
+               self.load()
+       
+       def load(self):
+               """ Reload the registry data from file """
+               try:
+                       self._data = cPickle.load(open(self._filename, "r"))
+               except IOError, e:
+                       if e.errno == errno.ENOENT:
+                               self._data = {}
+                       else:
+                               raise e
+               
+       def store(self):
+               """ Store the registry data to file. No need to call this if autocommit
+                   was enabled.
+               """
+               cPickle.dump(self._data, open(self._filename, "w"))
+       
+       def register(self, cpv, slot, counter, paths):
+               """ Register new objects in the registry. If there is a record with the
+                       same packagename (internally derived from cpv) and slot it is 
+                       overwritten with the new data.
+                       @param cpv: package instance that owns the objects
+                       @type cpv: CPV (as String)
+                       @param slot: the value of SLOT of the given package instance
+                       @type slot: String
+                       @param counter: vdb counter value for the package instace
+                       @type counter: Integer
+                       @param paths: absolute paths of objects that got preserved during an update
+                       @type paths: List
+               """
+               cp = "/".join(catpkgsplit(cpv)[:2])
+               cps = cp+":"+slot
+               if len(paths) == 0 and self._data.has_key(cps) \
+                               and self._data[cps][0] == cpv and int(self._data[cps][1]) == int(counter):
+                       del self._data[cps]
+               elif len(paths) > 0:
+                       self._data[cps] = (cpv, counter, paths)
+               if self._autocommit:
+                       self.store()
+       
+       def unregister(self, cpv, slot, counter):
+               """ Remove a previous registration of preserved objects for the given package.
+                       @param cpv: package instance whose records should be removed
+                       @type cpv: CPV (as String)
+                       @param slot: the value of SLOT of the given package instance
+                       @type slot: String
+               """
+               self.register(cpv, slot, counter, [])
+       
+       def pruneNonExisting(self):
+               """ Remove all records for objects that no longer exist on the filesystem. """
+               for cps in self._data.keys():
+                       cpv, counter, paths = self._data[cps]
+                       paths = [f for f in paths if os.path.exists(f)]
+                       if len(paths) > 0:
+                               self._data[cps] = (cpv, counter, paths)
+                       else:
+                               del self._data[cps]
+               if self._autocommit:
+                       self.store()
+       
+       def hasEntries(self):
+               """ Check if this registry contains any records. """
+               return (len(self._data.keys()) > 0)
+       
+       def getPreservedLibs(self):
+               """ Return a mapping of packages->preserved objects.
+                       @returns mapping of package instances to preserved objects
+                       @rtype Dict cpv->list-of-paths
+               """
+               rValue = {}
+               for cps in self._data.keys():
+                       rValue[self._data[cps][0]] = self._data[cps][2]
+               return rValue
+
+class LibraryPackageMap(object):
+       """ This class provides a library->consumer mapping generated from VDB data """
+       def __init__(self, filename, vardbapi):
+               self._filename = filename
+               self._dbapi = vardbapi
+
+       def get(self):
+               """ Read the global library->consumer map for the given vdb instance.
+                   @returns mapping of library objects (just basenames) to consumers (absolute paths)
+                       @rtype filename->list-of-paths
+               """
+               if not os.path.exists(self._filename):
+                       self.update()
+               rValue = {}
+               for l in open(self._filename, "r").read().split("\n"):
+                       mysplit = l.split()
+                       if len(mysplit) > 1:
+                               rValue[mysplit[0]] = mysplit[1].split(",")
+               return rValue
+
+       def update(self):
+               """ Update the global library->consumer map for the given vdb instance. """
+               obj_dict = {}
+               for cpv in self._dbapi.cpv_all():
+                       needed_list = self._dbapi.aux_get(cpv, ["NEEDED"])[0]
+                       for l in needed_list.split("\n"):
+                               mysplit = l.split()
+                               if len(mysplit) < 2:
+                                       continue
+                               libs = mysplit[1].split(",")
+                               for lib in libs:
+                                       if not obj_dict.has_key(lib):
+                                               obj_dict[lib] = [mysplit[0]]
+                                       else:
+                                               obj_dict[lib].append(mysplit[0])
+               mapfile = open(self._filename, "w")
+               for lib in obj_dict.keys():
+                       mapfile.write(lib+" "+",".join(obj_dict[lib])+"\n")
+               mapfile.close()
+
 class vardbapi(dbapi):
        def __init__(self, root, categories=None, settings=None, vartree=None):
                self.root = root[:]
@@ -54,6 +181,9 @@ class vardbapi(dbapi):
                self._aux_cache_filename = os.path.join(self.root,
                        CACHE_PATH.lstrip(os.path.sep), "vdb_metadata.pickle")
 
+               self.libmap = LibraryPackageMap(os.path.join(self.root, CACHE_PATH, "library_consumers"), self)
+               self.plib_registry = PreservedLibsRegistry(os.path.join(self.root, CACHE_PATH, "preserved_libs_registry"))
+
        def getpath(self, mykey, filename=None):
                rValue = os.path.join(self.root, VDB_PATH, mykey)
                if filename != None:
@@ -527,39 +657,6 @@ class vardbapi(dbapi):
                        write_atomic(cpath, str(counter))
                return counter
 
-       def get_library_map(self):
-               """ Read the global library->consumer map for this vdb instance """
-               mapfilename = os.path.join(self.root, CACHE_PATH, "library_consumers")
-               if not os.path.exists(mapfilename):
-                       self.update_library_map()
-               rValue = {}
-               for l in open(mapfilename, "r").read().split("\n"):
-                       mysplit = l.split()
-                       if len(mysplit) > 1:
-                               rValue[mysplit[0]] = mysplit[1].split(",")
-               return rValue
-
-       def update_library_map(self):
-               """ Update the global library->consumer map for this vdb instance. """
-               mapfilename = os.path.join(self.root, CACHE_PATH, "library_consumers")
-               obj_dict = {}
-               for cpv in self.cpv_all():
-                       needed_list = self.aux_get(cpv, ["NEEDED"])[0]
-                       for l in needed_list.split("\n"):
-                               mysplit = l.split()
-                               if len(mysplit) < 2:
-                                       continue
-                               libs = mysplit[1].split(",")
-                               for lib in libs:
-                                       if not obj_dict.has_key(lib):
-                                               obj_dict[lib] = [mysplit[0]]
-                                       else:
-                                               obj_dict[lib].append(mysplit[0])
-               mapfile = open(mapfilename, "w")
-               for lib in obj_dict.keys():
-                       mapfile.write(lib+" "+",".join(obj_dict[lib])+"\n")
-               mapfile.close()
-
 class vartree(object):
        "this tree will scan a var/db/pkg database located at root (passed to init)"
        def __init__(self, root="/", virtual=None, clone=None, categories=None,
@@ -973,6 +1070,9 @@ class dblink(object):
                                        return retval
 
                        self._unmerge_pkgfiles(pkgfiles)
+                       
+                       # Remove the registration of preserved libs for this pkg instance
+                       self.vartree.dbapi.plib_registry.unregister(self.mycpv, self.settings["SLOT"], self.settings["COUNTER"])
 
                        if myebuildpath:
                                retval = doebuild(myebuildpath, "postrm", self.myroot,
@@ -991,7 +1091,7 @@ class dblink(object):
                                        vartree=self.vartree)
                        
                        # regenerate reverse NEEDED map
-                       self.vartree.dbapi.update_library_map()
+                       self.vartree.dbapi.libmap.update()
 
                finally:
                        if builddir_lock:
@@ -1196,9 +1296,9 @@ class dblink(object):
                return True
 
 
-       def _preserve_libs(self, srcroot, destroot, mycontents):
+       def _preserve_libs(self, srcroot, destroot, mycontents, counter):
                # read global reverse NEEDED map
-               libmap = self.vartree.dbapi.get_library_map()
+               libmap = self.vartree.dbapi.libmap.get()
 
                # get list of libraries from old package instance
                old_contents = self._installed_instance.getcontents().keys()
@@ -1257,6 +1357,10 @@ class dblink(object):
                                preserve_paths.append(linktarget)
                        else:
                                shutil.copy2(os.path.join(destroot, x), os.path.join(srcroot, x.lstrip(os.sep)))
+
+               # keep track of the libs we preserved
+               self.vartree.dbapi.plib_registry.register(self.mycpv, self.settings["SLOT"], counter, preserve_paths)
+
                del preserve_paths
        
        def _collision_protect(self, srcroot, destroot, otherversions, mycontents, mysymlinks):
@@ -1421,21 +1525,31 @@ class dblink(object):
                                catsplit(slot_matches[0])[1], destroot, self.settings,
                                vartree=self.vartree)
 
+               # get current counter value (counter_tick also takes care of incrementing it)
+               # XXX Need to make this destroot, but it needs to be initialized first. XXX
+               # XXX bis: leads to some invalidentry() call through cp_all().
+               # Note: The counter is generated here but written later because preserve_libs
+               #       needs the counter value but has to be before dbtmpdir is made (which
+               #       has to be before the counter is written) - genone
+               counter = self.vartree.dbapi.counter_tick(self.myroot, mycpv=self.mycpv)
+
                myfilelist = None
                mylinklist = None
 
                # Preserve old libs if they are still in use
                if slot_matches and "preserve-libs" in self.settings.features:
                        myfilelist = listdir(srcroot, recursive=1, filesonly=1, followSymlinks=False)
-                       mylinklist = filter(os.path.islink, listdir(srcroot, recursive=1, filesonly=0, followSymlinks=False))
-                       self._preserve_libs(srcroot, destroot, myfilelist+mylinklist)
+                       mylinklist = filter(os.path.islink, [os.path.join(srcroot, x) for x in listdir(srcroot, recursive=1, filesonly=0, followSymlinks=False)])
+                       mylinklist = [x[len(srcroot):] for x in mylinklist]
+                       self._preserve_libs(srcroot, destroot, myfilelist+mylinklist, counter)
 
                # check for package collisions
                if "collision-protect" in self.settings.features:
                        if myfilelist == None:
                                myfilelist = listdir(srcroot, recursive=1, filesonly=1, followSymlinks=False)
                        if mylinklist == None:
-                               mylinklist = filter(os.path.islink, listdir(srcroot, recursive=1, filesonly=0, followSymlinks=False))
+                               mylinklist = filter(os.path.islink, [os.path.join(srcroot, x) for x in listdir(srcroot, recursive=1, filesonly=0, followSymlinks=False)])
+                               mylinklist = [x[len(srcroot):] for x in mylinklist]
                        self._collision_protect(srcroot, destroot, otherversions, myfilelist+mylinklist, mylinklist)
 
                if os.stat(srcroot).st_dev == os.stat(destroot).st_dev:
@@ -1475,10 +1589,6 @@ class dblink(object):
                for x in listdir(inforoot):
                        self.copyfile(inforoot+"/"+x)
 
-               # get current counter value (counter_tick also takes care of incrementing it)
-               # XXX Need to make this destroot, but it needs to be initialized first. XXX
-               # XXX bis: leads to some invalidentry() call through cp_all().
-               counter = self.vartree.dbapi.counter_tick(self.myroot, mycpv=self.mycpv)
                # write local package counter for recording
                lcfile = open(os.path.join(self.dbtmpdir, "COUNTER"),"w")
                lcfile.write(str(counter))
@@ -1569,7 +1679,7 @@ class dblink(object):
                del conf_mem_file
 
                # regenerate reverse NEEDED map
-               self.vartree.dbapi.update_library_map()
+               self.vartree.dbapi.libmap.update()
 
                #do postinst script
                a = doebuild(myebuild, "postinst", destroot, self.settings, use_cache=0,
index be7eb5b66a121b27d53aebdd10c881a6e8b5890d..77bdd1b944e85794edcf678db463ea4ccf41709a 100644 (file)
@@ -8,111 +8,99 @@ from portage.exception import PortageException
 from portage.process import atexit_register
 from portage.util import writemsg
 
-from portage import listdir
+from portage.elog.messages import collect_ebuild_messages, collect_messages
+from portage.elog.filtering import filter_loglevels
 
 import os
 
+def _merge_logentries(a, b):
+       rValue = {}
+       phases = set(a.keys()+b.keys())
+       for p in phases:
+               rValue[p] = []
+               if a.has_key(p):
+                       for x in a[p]:
+                               rValue[p].append(x)
+               if b.has_key(p):
+                       for x in b[p]:
+                               rValue[p].append(x)
+       return rValue
+
+def _combine_logentries(logentries):
+       # generate a single string with all log messages
+       rValue = ""
+       for phase in EBUILD_PHASES:
+               if not phase in logentries:
+                       continue
+               for msgtype, msgcontent in logentries[phase]:
+                       rValue += "%s: %s\n" % (msgtype, phase)
+                       for line in msgcontent:
+                               rValue += line
+                       rValue += "\n"
+       return rValue
+
 _elog_atexit_handlers = []
 def elog_process(cpv, mysettings):
-       mylogfiles = listdir(mysettings["T"]+"/logging/")
-       # shortcut for packages without any messages
-       if len(mylogfiles) == 0:
-               return
-       # exploit listdir() file order so we process log entries in chronological order
-       mylogfiles.reverse()
-       all_logentries = {}
-       for f in mylogfiles:
-               msgfunction, msgtype = f.split(".")
-               if msgfunction not in EBUILD_PHASES:
-                       writemsg("!!! can't process invalid log file: %s\n" % f,
-                               noiselevel=-1)
-                       continue
-               if not msgfunction in all_logentries:
-                       all_logentries[msgfunction] = []
-               msgcontent = open(mysettings["T"]+"/logging/"+f, "r").readlines()
-               all_logentries[msgfunction].append((msgtype, msgcontent))
+       ebuild_logentries = collect_ebuild_messages(os.path.join(mysettings["T"], "logging"))
+       all_logentries = collect_messages()
+       if all_logentries.has_key(cpv):
+               all_logentries[cpv] = _merge_logentries(ebuild_logentries, all_logentries[cpv])
+       else:
+               all_logentries[cpv] = ebuild_logentries
 
-       def filter_loglevels(logentries, loglevels):
-               # remove unwanted entries from all logentries
-               rValue = {}
-               loglevels = map(str.upper, loglevels)
-               for phase in logentries.keys():
-                       for msgtype, msgcontent in logentries[phase]:
-                               if msgtype.upper() in loglevels or "*" in loglevels:
-                                       if not rValue.has_key(phase):
-                                               rValue[phase] = []
-                                       rValue[phase].append((msgtype, msgcontent))
-               return rValue
-       
        my_elog_classes = set(mysettings.get("PORTAGE_ELOG_CLASSES", "").split())
-       default_logentries = filter_loglevels(all_logentries, my_elog_classes)
 
-       # in case the filters matched all messages and no module overrides exist
-       if len(default_logentries) == 0 and (not ":" in mysettings.get("PORTAGE_ELOG_SYSTEM", "")):
-               return
 
-       def combine_logentries(logentries):
-               # generate a single string with all log messages
-               rValue = ""
-               for phase in EBUILD_PHASES:
-                       if not phase in logentries:
-                               continue
-                       for msgtype, msgcontent in logentries[phase]:
-                               rValue += "%s: %s\n" % (msgtype, phase)
-                               for line in msgcontent:
-                                       rValue += line
-                               rValue += "\n"
-               return rValue
-       
-       default_fulllog = combine_logentries(default_logentries)
+       for key in all_logentries.keys():
+               default_logentries = filter_loglevels(all_logentries[key], my_elog_classes)
 
-       # pass the processing to the individual modules
-       logsystems = mysettings["PORTAGE_ELOG_SYSTEM"].split()
-       for s in logsystems:
-               # allow per module overrides of PORTAGE_ELOG_CLASSES
-               if ":" in s:
-                       s, levels = s.split(":", 1)
-                       levels = levels.split(",")
-                       mod_logentries = filter_loglevels(all_logentries, levels)
-                       mod_fulllog = combine_logentries(mod_logentries)
-               else:
-                       mod_logentries = default_logentries
-                       mod_fulllog = default_fulllog
-               if len(mod_logentries) == 0:
-                       continue
-               # - is nicer than _ for module names, so allow people to use it.
-               s = s.replace("-", "_")
-               try:
-                       # FIXME: ugly ad.hoc import code
-                       # TODO:  implement a common portage module loader
-                       name = "portage.elog.mod_" + s
-                       m = __import__(name)
-                       for comp in name.split(".")[1:]:
-                               m = getattr(m, comp)
-                       def timeout_handler(signum, frame):
-                               raise PortageException("Timeout in elog_process for system '%s'" % s)
-                       import signal
-                       signal.signal(signal.SIGALRM, timeout_handler)
-                       # Timeout after one minute (in case something like the mail
-                       # module gets hung).
-                       signal.alarm(60)
+               # in case the filters matched all messages and no module overrides exist
+               if len(default_logentries) == 0 and (not ":" in mysettings.get("PORTAGE_ELOG_SYSTEM", "")):
+                       return
+
+               default_fulllog = _combine_logentries(default_logentries)
+
+               # pass the processing to the individual modules
+               logsystems = mysettings["PORTAGE_ELOG_SYSTEM"].split()
+               for s in logsystems:
+                       # allow per module overrides of PORTAGE_ELOG_CLASSES
+                       if ":" in s:
+                               s, levels = s.split(":", 1)
+                               levels = levels.split(",")
+                               mod_logentries = filter_loglevels(all_logentries[key], levels)
+                               mod_fulllog = combine_logentries(mod_logentries)
+                       else:
+                               mod_logentries = default_logentries
+                               mod_fulllog = default_fulllog
+                       if len(mod_logentries) == 0:
+                               continue
+                       # - is nicer than _ for module names, so allow people to use it.
+                       s = s.replace("-", "_")
                        try:
-                               m.process(mysettings, cpv, mod_logentries, mod_fulllog)
-                       finally:
-                               signal.alarm(0)
-                       if hasattr(m, "finalize") and not m.finalize in _elog_atexit_handlers:
-                               _elog_atexit_handlers.append(m.finalize)
-                               atexit_register(m.finalize, mysettings)
-               except (ImportError, AttributeError), e:
-                       writemsg("!!! Error while importing logging modules " + \
-                               "while loading \"mod_%s\":\n" % str(s))
-                       writemsg("%s\n" % str(e), noiselevel=-1)
-               except PortageException, e:
-                       writemsg("%s\n" % str(e), noiselevel=-1)
+                               # FIXME: ugly ad.hoc import code
+                               # TODO:  implement a common portage module loader
+                               name = "portage.elog.mod_" + s
+                               m = __import__(name)
+                               for comp in name.split(".")[1:]:
+                                       m = getattr(m, comp)
+                               def timeout_handler(signum, frame):
+                                       raise PortageException("Timeout in elog_process for system '%s'" % s)
+                               import signal
+                               signal.signal(signal.SIGALRM, timeout_handler)
+                               # Timeout after one minute (in case something like the mail
+                               # module gets hung).
+                               signal.alarm(60)
+                               try:
+                                       m.process(mysettings, str(key), mod_logentries, mod_fulllog)
+                               finally:
+                                       signal.alarm(0)
+                               if hasattr(m, "finalize") and not m.finalize in _elog_atexit_handlers:
+                                       _elog_atexit_handlers.append(m.finalize)
+                                       atexit_register(m.finalize, mysettings)
+                       except (ImportError, AttributeError), e:
+                               writemsg("!!! Error while importing logging modules " + \
+                                       "while loading \"mod_%s\":\n" % str(s))
+                               writemsg("%s\n" % str(e), noiselevel=-1)
+                       except PortageException, e:
+                               writemsg("%s\n" % str(e), noiselevel=-1)
 
-       # clean logfiles to avoid repetitions
-       for f in mylogfiles:
-               try:
-                       os.unlink(os.path.join(mysettings["T"], "logging", f))
-               except OSError:
-                       pass
diff --git a/pym/portage/elog/filtering.py b/pym/portage/elog/filtering.py
new file mode 100644 (file)
index 0000000..f4748fe
--- /dev/null
@@ -0,0 +1,17 @@
+# elog/messages.py - elog core functions
+# Copyright 2006-2007 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id: __init__.py 6458 2007-04-30 02:31:30Z genone $
+
+def filter_loglevels(logentries, loglevels):
+       # remove unwanted entries from all logentries
+       rValue = {}
+       loglevels = map(str.upper, loglevels)
+       for phase in logentries.keys():
+               for msgtype, msgcontent in logentries[phase]:
+                       if msgtype.upper() in loglevels or "*" in loglevels:
+                               if not rValue.has_key(phase):
+                                       rValue[phase] = []
+                               rValue[phase].append((msgtype, msgcontent))
+       return rValue
+       
diff --git a/pym/portage/elog/messages.py b/pym/portage/elog/messages.py
new file mode 100644 (file)
index 0000000..bfe4280
--- /dev/null
@@ -0,0 +1,89 @@
+# elog/messages.py - elog core functions
+# Copyright 2006-2007 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id: __init__.py 6458 2007-04-30 02:31:30Z genone $
+
+from portage.output import colorize
+from portage.const import EBUILD_PHASES
+from portage.util import writemsg
+
+from portage import listdir
+
+import os
+
+def collect_ebuild_messages(path):
+       """ Collect elog messages generated by the bash logging function stored 
+               at 'path'.
+       """
+       mylogfiles = listdir(path)
+       # shortcut for packages without any messages
+       if len(mylogfiles) == 0:
+               return {}
+       # exploit listdir() file order so we process log entries in chronological order
+       mylogfiles.reverse()
+       logentries = {}
+       for f in mylogfiles:
+               msgfunction, msgtype = f.split(".")
+               if msgfunction not in EBUILD_PHASES:
+                       writemsg("!!! can't process invalid log file: %s\n" % f,
+                               noiselevel=-1)
+                       continue
+               if not msgfunction in logentries:
+                       logentries[msgfunction] = []
+               msgcontent = open(os.path.join(path, f), "r").readlines()
+               logentries[msgfunction].append((msgtype, msgcontent))
+       # clean logfiles to avoid repetitions
+       for f in mylogfiles:
+               try:
+                       os.unlink(os.path.join(path, f))
+               except OSError:
+                       pass
+       return logentries
+
+_msgbuffer = {}
+def _elog_base(level, msg, phase="other", key=None, color=None):
+       """ Backend for the other messaging functions, should not be called 
+           directly.
+       """
+       if color == None:
+               color = "GOOD"
+       print colorize(color, " * ")+msg
+       if not _msgbuffer.has_key(key):
+               _msgbuffer[key] = {}
+       if not _msgbuffer[key].has_key(phase):
+               _msgbuffer[key][phase] = []
+       _msgbuffer[key][phase].append((level, msg))
+
+       #raise NotImplementedError()
+
+def collect_messages():
+       rValue = _msgbuffer
+       _reset_buffer()
+       return rValue
+
+def _reset_buffer():
+       """ Reset the internal message buffer when it has been processed, 
+           should not be called directly.
+       """
+       _msgbuffer = {}
+
+# creating and exporting the actual messaging functions
+_functions = { "einfo": ("INFO", "GOOD"),
+               "elog": ("LOG", "GOOD"),
+               "ewarn": ("WARN", "WARN"),
+               "eqawarn": ("QA", "WARN"),
+               "eerror": ("ERROR", "ERROR"),
+}
+
+def _make_msgfunction(level, color):
+       def _elog(msg, phase="other", key=None):
+               """ Display and log a message assigned to the given key/cpv 
+                   (or unassigned if no key is given).
+               """ 
+               _elog_base(level, msg,  phase=phase, key=key, color=color)
+       return _elog
+
+import sys
+for f in _functions.keys():
+       setattr(sys.modules[__name__], f, _make_msgfunction(_functions[f][0], _functions[f][1]))
+del f, _functions
index 3433bbbf1d9bba6c43e4fe5c430688c2ff16ce58..a486c8931e6b98542a8cf21a233d457bc0396bb7 100644 (file)
@@ -5,8 +5,8 @@
 
 import portage.elog_modules.mod_save, portage.process, portage.exception
 
-def process(mysettings, cpv, logentries, fulltext):
-       elogfilename = portage.elog_modules.mod_save.process(mysettings, cpv, logentries, fulltext)
+def process(mysettings, key, logentries, fulltext):
+       elogfilename = portage.elog_modules.mod_save.process(mysettings, key, logentries, fulltext)
        
        if (not "PORTAGE_ELOG_COMMAND" in mysettings.keys()) \
                        or len(mysettings["PORTAGE_ELOG_COMMAND"]) == 0:
@@ -14,7 +14,7 @@ def process(mysettings, cpv, logentries, fulltext):
        else:
                mylogcmd = mysettings["PORTAGE_ELOG_COMMAND"]
                mylogcmd = mylogcmd.replace("${LOGFILE}", elogfilename)
-               mylogcmd = mylogcmd.replace("${PACKAGE}", cpv)
+               mylogcmd = mylogcmd.replace("${PACKAGE}", key)
                retval = portage.process.spawn_bash(mylogcmd)
                if retval != 0:
                        raise portage.exception.PortageException("!!! PORTAGE_ELOG_COMMAND failed with exitcode %d" % retval)
index 73928b4372736633456dcb5bcfe5bc9e8469fb6c..125a8c40e2181b53a0aa2dcc269d16a27ab342d2 100644 (file)
@@ -7,19 +7,19 @@ from portage.output import EOutput
 from portage.const import EBUILD_PHASES
 
 _items = {}
-def process(mysettings, cpv, logentries, fulltext):
-       _items[cpv] = logentries
+def process(mysettings, key, logentries, fulltext):
+       _items[key] = logentries
 
 def finalize(mysettings):
        printer = EOutput()
-       for cpv in _items.keys():
+       for key in _items.keys():
                print
-               printer.einfo("Messages for package %s:" % cpv)
+               printer.einfo("Messages for package %s:" % key)
                print
                for phase in EBUILD_PHASES:
-                       if not phase in _items[cpv]:
+                       if not phase in _items[key]:
                                continue
-                       for msgtype, msgcontent in _items[cpv][phase]:
+                       for msgtype, msgcontent in _items[key][phase]:
                                fmap = {"INFO": printer.einfo,
                                                "WARN": printer.ewarn,
                                                "ERROR": printer.eerror,
index 4695e8ec463248634a2018b17758440b125aa30c..d0d2b33ac32edc4377f67ed57894ff6ae35a2dbd 100644 (file)
@@ -5,7 +5,7 @@
 
 import portage.mail, socket
 
-def process(mysettings, cpv, logentries, fulltext):
+def process(mysettings, key, logentries, fulltext):
        if mysettings.has_key("PORTAGE_ELOG_MAILURI"):
                myrecipient = mysettings["PORTAGE_ELOG_MAILURI"].split()[0]
        else:
@@ -13,7 +13,7 @@ def process(mysettings, cpv, logentries, fulltext):
        
        myfrom = mysettings["PORTAGE_ELOG_MAILFROM"]
        mysubject = mysettings["PORTAGE_ELOG_MAILSUBJECT"]
-       mysubject = mysubject.replace("${PACKAGE}", cpv)
+       mysubject = mysubject.replace("${PACKAGE}", key)
        mysubject = mysubject.replace("${HOST}", socket.getfqdn())
 
        mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, fulltext)
index a1f4ed57ffbca43c1dff7d364b749148c55f4e02..a8e6949354294f3b11be756a20f71532adf03265 100644 (file)
@@ -7,10 +7,10 @@ import portage.mail, socket, os, time
 from email.MIMEText import MIMEText as TextMessage
 
 _items = {}
-def process(mysettings, cpv, logentries, fulltext):
+def process(mysettings, key, logentries, fulltext):
        header = ">>> Messages generated for package %s by process %d on %s:\n\n" % \
-               (cpv, os.getpid(), time.strftime("%Y%m%d-%H%M%S", time.gmtime(time.time())))
-       _items[cpv] = header + fulltext
+               (key, os.getpid(), time.strftime("%Y%m%d-%H%M%S", time.gmtime(time.time())))
+       _items[key] = header + fulltext
 
 def finalize(mysettings):
        if len(_items) == 0:
@@ -31,8 +31,8 @@ def finalize(mysettings):
 
        mybody = "elog messages for the following packages generated by " + \
                "process %d on host %s:\n" % (os.getpid(), socket.getfqdn())
-       for cpv in _items.keys():
-                mybody += "- %s\n" % cpv
+       for key in _items.keys():
+                mybody += "- %s\n" % key
 
        mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, mybody, attachments=_items.values())
        portage.mail.send_mail(mysettings, mymessage)
index eff40edd950728386a2bc5a8d466eb3b11bbe3ca..31c410fac5404c02c7fe37065fd74f23c879935a 100644 (file)
@@ -6,8 +6,8 @@
 import os, time
 from portage.data import portage_uid, portage_gid
 
-def process(mysettings, cpv, logentries, fulltext):
-       cpv_path = cpv.replace("/", ":")
+def process(mysettings, key, logentries, fulltext):
+       path = key.replace("/", ":")
 
        if mysettings["PORT_LOGDIR"] != "":
                elogdir = os.path.join(mysettings["PORT_LOGDIR"], "elog")
@@ -18,7 +18,7 @@ def process(mysettings, cpv, logentries, fulltext):
        os.chown(elogdir, portage_uid, portage_gid)
        os.chmod(elogdir, 02770)
 
-       elogfilename = elogdir+"/"+cpv_path+":"+time.strftime("%Y%m%d-%H%M%S", time.gmtime(time.time()))+".log"
+       elogfilename = elogdir+"/"+path+":"+time.strftime("%Y%m%d-%H%M%S", time.gmtime(time.time()))+".log"
        elogfile = open(elogfilename, "w")
        elogfile.write(fulltext)
        elogfile.close()
index 2a6ac20ef2f08f4c8490e37fdf22fdcc363c820f..3c1325dbbe8408821b9e384c3a9101196369017c 100644 (file)
@@ -7,7 +7,7 @@ import os, time
 from portage.data import portage_uid, portage_gid
 from portage.const import EPREFIX
 
-def process(mysettings, cpv, logentries, fulltext):
+def process(mysettings, key, logentries, fulltext):
        if mysettings["PORT_LOGDIR"] != "":
                elogdir = os.path.join(mysettings["PORT_LOGDIR"], "elog")
        else:
@@ -21,7 +21,7 @@ def process(mysettings, cpv, logentries, fulltext):
        elogfilename = elogdir+"/summary.log"
        elogfile = open(elogfilename, "a")
        elogfile.write(">>> Messages generated by process %d on %s for package %s:\n\n" % \
-                       (os.getpid(), time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.time())), cpv))
+                       (os.getpid(), time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.time())), key))
        elogfile.write(fulltext)
        elogfile.write("\n")
        elogfile.close()
index 28aa15b3ce34c8870aec8b23c8d5d06d9af191c8..bb66b2b74ba6f0b0e27c1f87084b893a73d259dd 100644 (file)
@@ -6,7 +6,7 @@
 import syslog
 from portage.const import EBUILD_PHASES
 
-def process(mysettings, cpv, logentries, fulltext):
+def process(mysettings, key, logentries, fulltext):
        syslog.openlog("portage", syslog.LOG_ERR | syslog.LOG_WARNING | syslog.LOG_INFO | syslog.LOG_NOTICE, syslog.LOG_LOCAL5)
        for phase in EBUILD_PHASES:
                if not phase in logentries:
@@ -18,5 +18,5 @@ def process(mysettings, cpv, logentries, fulltext):
                                "LOG": syslog.LOG_NOTICE,
                                "QA": syslog.LOG_WARNING}
                        msgtext = "".join(msgcontent)
-                       syslog.syslog(pri[msgtype], "%s: %s: %s" % (cpv, phase, msgtext))
+                       syslog.syslog(pri[msgtype], "%s: %s: %s" % (key, phase, msgtext))
        syslog.closelog()
index 6cac55feec5e5739aee81e98a8e171cb53dcea03..0bb12c5edd1902614e9effe420f82b563413677e 100644 (file)
@@ -26,24 +26,24 @@ class NewsManager(object):
 
        TIMESTAMP_FILE = "news-timestamp"
 
-       def __init__( self, root, NEWS_PATH, UNREAD_PATH, LANGUAGE_ID='en' ):
+       def __init__(self, root, NEWS_PATH, UNREAD_PATH, LANGUAGE_ID='en'):
                self.NEWS_PATH = NEWS_PATH
                self.UNREAD_PATH = UNREAD_PATH
-               self.TIMESTAMP_PATH = os.path.join( root, NEWS_LIB_PATH, NewsManager.TIMESTAMP_FILE )
+               self.TIMESTAMP_PATH = os.path.join(root, NEWS_LIB_PATH, NewsManager.TIMESTAMP_FILE)
                self.target_root = root
                self.LANGUAGE_ID = LANGUAGE_ID
-               self.config = config( config_root = os.environ.get("PORTAGE_CONFIGROOT", "/"),
+               self.config = config(config_root = os.environ.get("PORTAGE_CONFIGROOT", "/"),
                                target_root = root, config_incrementals = INCREMENTALS)
-               self.vdb = vardbapi( settings = self.config, root = root,
-                       vartree = vartree( root = root, settings = self.config ) )
-               self.portdb = portdbapi( porttree_root = self.config["PORTDIR"], mysettings = self.config )
+               self.vdb = vardbapi(settings = self.config, root = root,
+                       vartree = vartree(root = root, settings = self.config))
+               self.portdb = portdbapi(porttree_root = self.config["PORTDIR"], mysettings = self.config)
 
                # Ensure that the unread path exists and is writable.
                dirmode  = 02070
                modemask =    02
                ensure_dirs(self.UNREAD_PATH, mode=dirmode, mask=modemask, gid=portage_gid)
 
-       def updateItems( self, repoid ):
+       def updateItems(self, repoid):
                """
                Figure out which news items from NEWS_PATH are both unread and relevant to
                the user (according to the GLEP 42 standards of relevancy).  Then add these
@@ -57,51 +57,50 @@ class NewsManager(object):
                timestamp_file = self.TIMESTAMP_PATH + repoid
                if os.path.exists(timestamp_file):
                        # Make sure the timestamp has correct permissions.
-                       apply_permissions( filename=timestamp_file, 
-                               uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=664 )
+                       apply_permissions(filename=timestamp_file, 
+                               uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664)
                        timestamp = os.stat(timestamp_file).st_mtime
                else:
                        timestamp = 0
 
-               path = os.path.join( self.portdb.getRepositoryPath( repoid ), self.NEWS_PATH )
+               path = os.path.join(self.portdb.getRepositoryPath(repoid), self.NEWS_PATH)
                newsdir_lock = None
                try:
-                       newsdir_lock = lockdir( self.portdb.getRepositoryPath(repoid) )
+                       newsdir_lock = lockdir(self.portdb.getRepositoryPath(repoid))
                        # Skip reading news for repoid if the news dir does not exist.  Requested by
                        # NightMorph :)
-                       if not os.path.exists( path ):
+                       if not os.path.exists(path):
                                return None
-                       news = os.listdir( path )
+                       news = os.listdir(path)
                        updates = []
-                       for item in news:
+                       for itemid in news:
                                try:
-                                       file = os.path.join( path, item, item + "." + self.LANGUAGE_ID + ".txt")
-                                       tmp = NewsItem( file , timestamp )
-                               except TypeError:
+                                       filename = os.path.join(path, itemid, itemid + "." + self.LANGUAGE_ID + ".txt")
+                                       item = NewsItem(filename, itemid, timestamp)
+                               except (TypeError, ValueError), e:
                                        continue
-
-                               if tmp.isRelevant( profile=os.readlink(PROFILE_PATH), config=config, vardb=self.vdb):
-                                       updates.append( tmp )
+                               if item.isRelevant(profile=os.readlink(PROFILE_PATH), config=config, vardb=self.vdb):
+                                       updates.append(item)
                finally:
                        if newsdir_lock:
                                unlockdir(newsdir_lock)
                
                del path
                
-               path = os.path.join( self.UNREAD_PATH, "news-" + repoid + ".unread" )
+               path = os.path.join(self.UNREAD_PATH, "news-"+repoid+".unread")
                try:
-                       unread_lock = lockfile( path )
-                       if not os.path.exists( path ):
+                       unread_lock = lockfile(path)
+                       if not os.path.exists(path):
                                #create the file if it does not exist
-                               open( path, "w" )
+                               open(path, "w")
                        # Ensure correct perms on the unread file.
                        apply_permissions( filename=path,
-                               uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=664 )
+                               uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664)
                        # Make sure we have the correct permissions when created
-                       unread_file = open( path, "a" )
+                       unread_file = open(path, "a")
 
                        for item in updates:
-                               unread_file.write( item.path + "\n" )
+                               unread_file.write(item.name + "\n")
                        unread_file.close()
                finally:
                        unlockfile(unread_lock)
@@ -110,7 +109,7 @@ class NewsManager(object):
                f = open(timestamp_file, "w")
                f.close()
 
-       def getUnreadItems( self, repoid, update=False ):
+       def getUnreadItems(self, repoid, update=False):
                """
                Determine if there are unread relevant items in news.repoid.unread.
                If there are unread items return their number.
@@ -119,18 +118,18 @@ class NewsManager(object):
                """
                
                if update:
-                       self.updateItems( repoid )
+                       self.updateItems(repoid)
                
-               unreadfile = os.path.join( self.UNREAD_PATH, "news-"+ repoid +".unread" )
+               unreadfile = os.path.join(self.UNREAD_PATH, "news-"+repoid+".unread")
                try:
                        try:
                                unread_lock = lockfile(unreadfile)
                                # Set correct permissions on the news-repoid.unread file
-                               apply_permissions( filename=unreadfile,
-                                       uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664 )
+                               apply_permissions(filename=unreadfile,
+                                       uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664)
                                        
-                               if os.path.exists( unreadfile ):
-                                       unread = open( unreadfile ).readlines()
+                               if os.path.exists(unreadfile):
+                                       unread = open(unreadfile).readlines()
                                        if len(unread):
                                                return len(unread)
                        except FileNotFound:
@@ -155,19 +154,20 @@ class NewsItem(object):
 
        """
        
-       def __init__( self, path, cache_mtime = 0 ):
+       def __init__(self, path, name, cache_mtime = 0):
                """ 
                For a given news item we only want if it path is a file and it's 
                mtime is newer than the cache'd timestamp.
                """
-               if not os.path.isfile( path ):
-                       raise TypeError
-               if not os.stat( path ).st_mtime > cache_mtime:
-                       raise TypeError
+               if not os.path.isfile(path):
+                       raise TypeError("%s is no regular file" % path)
+               if not os.stat(path).st_mtime > cache_mtime:
+                       raise ValueError("%s / %s timestamp mismatch" % (str(os.stat(path).st_mtime), str(cache_mtime)))
                self.path = path
+               self.name = name
                self._parsed = False
 
-       def isRelevant( self, vardb, config, profile ):
+       def isRelevant(self, vardb, config, profile):
                """
                This function takes a dict of keyword arguments; one should pass in any
                objects need to do to lookups (like what keywords we are on, what profile,
@@ -184,12 +184,12 @@ class NewsItem(object):
                           'profile' : profile }
 
                for restriction in self.restrictions:
-                       if restriction.checkRestriction( **kwargs ):
+                       if restriction.checkRestriction(**kwargs):
                                return True
                        
                return False # No restrictions were met; thus we aren't relevant :(
 
-       def parse( self ):
+       def parse(self):
                lines = open(self.path).readlines()
                self.restrictions = []
                for line in lines:
@@ -203,11 +203,11 @@ class NewsItem(object):
                        for regex, restriction in restricts.iteritems():
                                match = regex.match(line)
                                if match:
-                                       self.restrictions.append( restriction( match.groups()[0].strip() ) )
+                                       self.restrictions.append(restriction(match.groups()[0].strip()))
                                        continue
                self._parsed = True
 
-       def __getattr__( self, attr ):
+       def __getattr__(self, attr):
                if not self._parsed:
                        self.parse()
                return self.__dict__[attr]
@@ -221,7 +221,7 @@ class DisplayRestriction(object):
        are met, then it is displayed
        """
 
-       def checkRestriction( self, **kwargs ):
+       def checkRestriction(self, **kwargs):
                raise NotImplementedError("Derived class should over-ride this method")
 
 class DisplayProfileRestriction(DisplayRestriction):
@@ -230,10 +230,10 @@ class DisplayProfileRestriction(DisplayRestriction):
        if the user is running a specific profile.
        """
 
-       def __init__( self, profile ):
+       def __init__(self, profile):
                self.profile = profile
 
-       def checkRestriction( self, **kwargs ):
+       def checkRestriction(self, **kwargs):
                if self.profile == kwargs['profile']:
                        return True
                return False
@@ -244,10 +244,10 @@ class DisplayKeywordRestriction(DisplayRestriction):
        if the user is running a specific keyword.
        """
 
-       def __init__( self, keyword ):
+       def __init__(self, keyword):
                self.keyword = keyword
 
-       def checkRestriction( self, **kwargs ):
+       def checkRestriction(self, **kwargs):
                if kwargs['config']["ARCH"] == self.keyword:
                        return True
                return False
@@ -258,11 +258,11 @@ class DisplayInstalledRestriction(DisplayRestriction):
        if the user has that item installed.
        """
        
-       def __init__( self, cpv ):
+       def __init__(self, cpv):
                self.cpv = cpv
 
-       def checkRestriction( self, **kwargs ):
+       def checkRestriction(self, **kwargs):
                vdb = kwargs['vardb']
-               if vdb.match( self.cpv ):
+               if vdb.match(self.cpv):
                        return True
                return False