summaryrefslogtreecommitdiffstats
path: root/presto-utils
diff options
context:
space:
mode:
authorJonathan Dieter <jdieter@gmail.com>2007-06-19 20:58:07 +0300
committerJonathan Dieter <jdieter@gmail.com>2007-06-19 20:58:07 +0300
commitdce0600bc64c793ba6e8f67c56c286d8d97e7c4c (patch)
tree71c559e031b3c10ba56a187e0a017f09d4d25137 /presto-utils
parent93b2295180471308e969640472bdc601d1f10015 (diff)
downloadpresto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.tar.gz
presto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.tar.xz
presto-dce0600bc64c793ba6e8f67c56c286d8d97e7c4c.zip
Many bugfixes and a few enhancements
Signed-off-by: Jonathan Dieter <jdieter@gmail.com>
Diffstat (limited to 'presto-utils')
-rw-r--r--presto-utils/COPYING339
-rw-r--r--presto-utils/ChangeLog6
-rw-r--r--presto-utils/Makefile11
-rw-r--r--presto-utils/README22
-rwxr-xr-xpresto-utils/createdeltarpms2
-rwxr-xr-xpresto-utils/createprestorepo2
-rw-r--r--presto-utils/dumpMetadata.py497
-rwxr-xr-xpresto-utils/gendeltarpms.py324
-rwxr-xr-xpresto-utils/genprestometadata.py525
-rw-r--r--presto-utils/packagelist.py386
10 files changed, 2114 insertions, 0 deletions
diff --git a/presto-utils/COPYING b/presto-utils/COPYING
new file mode 100644
index 0000000..e77696a
--- /dev/null
+++ b/presto-utils/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/presto-utils/ChangeLog b/presto-utils/ChangeLog
new file mode 100644
index 0000000..1640457
--- /dev/null
+++ b/presto-utils/ChangeLog
@@ -0,0 +1,6 @@
+* Tue Jun 19 2007 Jonathan Dieter <jdieter@gmail.com> - 0.2.0
+ - Now works with createrepo and modifyrepo
+ - Many bugfixes
+
+* Tue May 7 2007 Jonathan Dieter <jdieter@gmail.com> - 0.1.0
+ - Initial release
diff --git a/presto-utils/Makefile b/presto-utils/Makefile
new file mode 100644
index 0000000..9e8c6eb
--- /dev/null
+++ b/presto-utils/Makefile
@@ -0,0 +1,11 @@
+clean:
+ rm -f *.pyc *.pyo *~
+
+install:
+ install -m 755 createprestorepo $(DESTDIR)/usr/bin/
+ install -m 755 createdeltarpms $(DESTDIR)/usr/bin/
+ mkdir -p $(DESTDIR)/usr/share/createprestorepo
+ install -m 755 genprestometadata.py $(DESTDIR)/usr/share/createprestorepo
+ install -m 644 dumpMetadata.py $(DESTDIR)/usr/share/createprestorepo
+ install -m 755 gendeltarpms.py $(DESTDIR)/usr/share/createprestorepo
+ install -m 644 packagelist.py $(DESTDIR)/usr/share/createprestorepo
diff --git a/presto-utils/README b/presto-utils/README
new file mode 100644
index 0000000..a81187a
--- /dev/null
+++ b/presto-utils/README
@@ -0,0 +1,22 @@
+Presto: A project to add delta rpm support into yum for Fedora users
+http://hosted.fedoraproject.org/projects/presto. A list of presto-enabled
+repositories is available there.
+
+createprestorepo: The presto repository creater
+
+Installation:
+=============
+1- Untar the package
+2- Run 'make install'
+
+Running:
+1- First run 'createdeltarpms <repo dir> <drpm dir>' where <repo dir> is the
+ base directory for your repository and <drpm dir> is the subdirectory you
+ want to create the deltarpms into
+2- Run 'createprestorepo <repo dir>' where <repo dir> is the base directory for
+ your repository.
+
+WARNING: createprestorepo does *NOT* yet know how to deal with the metadata
+ created by createrepo. You will have to manually move metadata from
+ .olddata to repodata after running both createprestorepo and
+ createrepo.
diff --git a/presto-utils/createdeltarpms b/presto-utils/createdeltarpms
new file mode 100755
index 0000000..99bedcc
--- /dev/null
+++ b/presto-utils/createdeltarpms
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createprestorepo/gendeltarpms.py "$@"
diff --git a/presto-utils/createprestorepo b/presto-utils/createprestorepo
new file mode 100755
index 0000000..5334a80
--- /dev/null
+++ b/presto-utils/createprestorepo
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec /usr/share/createprestorepo/genprestometadata.py "$@"
diff --git a/presto-utils/dumpMetadata.py b/presto-utils/dumpMetadata.py
new file mode 100644
index 0000000..0ec2c20
--- /dev/null
+++ b/presto-utils/dumpMetadata.py
@@ -0,0 +1,497 @@
+#!/usr/bin/python -t
+# base classes and functions for dumping out package Metadata
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2004 Duke University
+
+# $Id: dumpMetadata.py,v 1.36 2006/02/21 20:10:08 pnasrat Exp $
+
+import os
+import rpm
+import exceptions
+import md5
+import sha
+import types
+import struct
+import re
+import stat
+
+# done to fix gzip randomly changing the checksum
+import gzip
+from zlib import error as zlibError
+from gzip import write32u, FNAME
+
+__all__ = ["GzipFile","open"]
+
+class GzipFile(gzip.GzipFile):
+ def _write_gzip_header(self):
+ self.fileobj.write('\037\213') # magic header
+ self.fileobj.write('\010') # compression method
+ fname = self.filename[:-3]
+ flags = 0
+ if fname:
+ flags = FNAME
+ self.fileobj.write(chr(flags))
+ write32u(self.fileobj, long(0))
+ self.fileobj.write('\002')
+ self.fileobj.write('\377')
+ if fname:
+ self.fileobj.write(fname + '\000')
+
+
+def _gzipOpen(filename, mode="rb", compresslevel=9):
+ return GzipFile(filename, mode, compresslevel)
+
+
+
+def returnFD(filename):
+ try:
+ fdno = os.open(filename, os.O_RDONLY)
+ except OSError:
+ raise MDError, "Error opening file"
+ return fdno
+
+def returnHdr(ts, package):
+ """hand back the rpm header or raise an Error if the pkg is fubar"""
+ opened_here = 0
+ try:
+ if type(package) is types.StringType:
+ opened_here = 1
+ fdno = os.open(package, os.O_RDONLY)
+ else:
+ fdno = package # let's assume this is an fdno and go with it :)
+ except OSError:
+ raise MDError, "Error opening file"
+ ts.setVSFlags((rpm._RPMVSF_NOSIGNATURES|rpm.RPMVSF_NOMD5|rpm.RPMVSF_NEEDPAYLOAD))
+ try:
+ hdr = ts.hdrFromFdno(fdno)
+ except rpm.error:
+ raise MDError, "Error opening package"
+ if type(hdr) != rpm.hdr:
+ raise MDError, "Error opening package"
+ ts.setVSFlags(0)
+
+ if opened_here:
+ os.close(fdno)
+ del fdno
+
+ return hdr
+
+def getChecksum(sumtype, file, CHUNK=2**16):
+ """takes filename, hand back Checksum of it
+ sumtype = md5 or sha
+ filename = /path/to/file
+ CHUNK=65536 by default"""
+
+ # chunking brazenly lifted from Ryan Tomayko
+ opened_here = 0
+ try:
+ if type(file) is not types.StringType:
+ fo = file # assume it's a file-like-object
+ else:
+ opened_here = 1
+ fo = open(file, 'rb', CHUNK)
+
+ if sumtype == 'md5':
+ sum = md5.new()
+ elif sumtype == 'sha':
+ sum = sha.new()
+ else:
+ raise MDError, 'Error Checksumming file, wrong checksum type %s' % sumtype
+ chunk = fo.read
+ while chunk:
+ chunk = fo.read(CHUNK)
+ sum.update(chunk)
+
+ if opened_here:
+ fo.close()
+ del fo
+
+ return sum.hexdigest()
+ except:
+ raise MDError, 'Error opening file for checksum: %s' % file
+
+
+def utf8String(string):
+ """hands back a unicoded string"""
+ if string is None:
+ return ''
+ elif isinstance(string, unicode):
+ return string
+ try:
+ x = unicode(string, 'ascii')
+ return string
+ except UnicodeError:
+ encodings = ['utf-8', 'iso-8859-1', 'iso-8859-15', 'iso-8859-2']
+ for enc in encodings:
+ try:
+ x = unicode(string, enc)
+ except UnicodeError:
+ pass
+ else:
+ if x.encode(enc) == string:
+ return x.encode('utf-8')
+ newstring = ''
+ for char in string:
+ if ord(char) > 127:
+ newstring = newstring + '?'
+ else:
+ newstring = newstring + char
+ return newstring
+
+
+def byteranges(file):
+ """takes an rpm file or fileobject and returns byteranges for location of the header"""
+ opened_here = 0
+ if type(file) is not types.StringType:
+ fo = file
+ else:
+ opened_here = 1
+ fo = open(file, 'r')
+ #read in past lead and first 8 bytes of sig header
+ fo.seek(104)
+ # 104 bytes in
+ binindex = fo.read(4)
+ # 108 bytes in
+ (sigindex, ) = struct.unpack('>I', binindex)
+ bindata = fo.read(4)
+ # 112 bytes in
+ (sigdata, ) = struct.unpack('>I', bindata)
+ # each index is 4 32bit segments - so each is 16 bytes
+ sigindexsize = sigindex * 16
+ sigsize = sigdata + sigindexsize
+ # we have to round off to the next 8 byte boundary
+ disttoboundary = (sigsize % 8)
+ if disttoboundary != 0:
+ disttoboundary = 8 - disttoboundary
+ # 112 bytes - 96 == lead, 8 = magic and reserved, 8 == sig header data
+ hdrstart = 112 + sigsize + disttoboundary
+
+ fo.seek(hdrstart) # go to the start of the header
+ fo.seek(8,1) # read past the magic number and reserved bytes
+
+ binindex = fo.read(4)
+ (hdrindex, ) = struct.unpack('>I', binindex)
+ bindata = fo.read(4)
+ (hdrdata, ) = struct.unpack('>I', bindata)
+
+ # each index is 4 32bit segments - so each is 16 bytes
+ hdrindexsize = hdrindex * 16
+ # add 16 to the hdrsize to account for the 16 bytes of misc data b/t the
+ # end of the sig and the header.
+ hdrsize = hdrdata + hdrindexsize + 16
+
+ # header end is hdrstart + hdrsize
+ hdrend = hdrstart + hdrsize
+ if opened_here:
+ fo.close()
+ del fo
+ return (hdrstart, hdrend)
+
+
+class MDError(exceptions.Exception):
+ def __init__(self, args=None):
+ exceptions.Exception.__init__(self)
+ self.args = args
+
+
+
+class RpmMetaData:
+ """each drpm is one object, you pass it an rpm file
+ it opens the file, and pulls the information out in bite-sized chunks :)
+ """
+
+ mode_cache = {}
+
+ def __init__(self, ts, basedir, filename, options, is_drpm):
+ try:
+ stats = os.stat(os.path.join(basedir, filename))
+ self.size = stats[6]
+ self.mtime = stats[8]
+ del stats
+ except OSError, e:
+ raise MDError, "Error Stat'ing file %s %s" % (basedir, filename)
+ self.options = options
+ self.localurl = options['baseurl']
+ self.relativepath = filename
+ fd = returnFD(os.path.join(basedir, filename))
+ self.hdr = returnHdr(ts, fd)
+ os.lseek(fd, 0, 0)
+ fo = os.fdopen(fd, 'rb')
+ self.pkgid = self.doChecksumCache(fo)
+ fo.seek(0)
+ (self.rangestart, self.rangeend) = byteranges(fo)
+ self.is_drpm = False
+ if is_drpm:
+ fo.seek(self.rangeend)
+ self._getOldInfo(fo)
+ self.is_drpm = True
+ del fo
+ del fd
+
+ def arch(self):
+ if self.tagByName('sourcepackage') == 1:
+ return 'src'
+ else:
+ return self.tagByName('arch')
+
+ def _stringToNEVR(self, string):
+ i = string.rfind("-", 0, string.rfind("-")-1)
+ name = string[:i]
+ (epoch, ver, rel) = self._stringToVersion(string[i+1:])
+ return (name, epoch, ver, rel)
+
+ def _getLength(self, in_data):
+ length = 0
+ for val in in_data:
+ length = length * 256
+ length += ord(val)
+ return length
+
+ def _getOldInfo(self, fo):
+ try:
+ compobj = gzip.GzipFile("", "rb", 9, fo)
+ except:
+ raise zlibError("Data not stored in gzip format")
+
+ if compobj.read(4)[:3] != "DLT":
+ raise Exception("Not a deltarpm")
+
+ nevr_length = self._getLength(compobj.read(4))
+ nevr = compobj.read(nevr_length).strip("\x00")
+ seq_length = self._getLength(compobj.read(4))
+ seq = compobj.read(seq_length)
+ hex_seq = ""
+ for char in seq:
+ hex_seq += str("%02x" % ord(char))
+ self.oldnevrstring = nevr
+ self.oldnevr = self._stringToNEVR(nevr)
+ self.sequence = hex_seq
+ compobj.close()
+
+ def _stringToVersion(self, strng):
+ i = strng.find(':')
+ if i != -1:
+ epoch = strng[:i]
+ else:
+ epoch = '0'
+ j = strng.find('-')
+ if j != -1:
+ if strng[i + 1:j] == '':
+ version = None
+ else:
+ version = strng[i + 1:j]
+ release = strng[j + 1:]
+ else:
+ if strng[i + 1:] == '':
+ version = None
+ else:
+ version = strng[i + 1:]
+ release = None
+ return (epoch, version, release)
+
+ ###########
+ # Title: Remove duplicates from a sequence
+ # Submitter: Tim Peters
+ # From: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
+
+ def _uniq(self,s):
+ """Return a list of the elements in s, but without duplicates.
+
+ For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3],
+ unique("abcabc") some permutation of ["a", "b", "c"], and
+ unique(([1, 2], [2, 3], [1, 2])) some permutation of
+ [[2, 3], [1, 2]].
+
+ For best speed, all sequence elements should be hashable. Then
+ unique() will usually work in linear time.
+
+ If not possible, the sequence elements should enjoy a total
+ ordering, and if list(s).sort() doesn't raise TypeError it's
+ assumed that they do enjoy a total ordering. Then unique() will
+ usually work in O(N*log2(N)) time.
+
+ If that's not possible either, the sequence elements must support
+ equality-testing. Then unique() will usually work in quadratic
+ time.
+ """
+
+ n = len(s)
+ if n == 0:
+ return []
+
+ # Try using a dict first, as that's the fastest and will usually
+ # work. If it doesn't work, it will usually fail quickly, so it
+ # usually doesn't cost much to *try* it. It requires that all the
+ # sequence elements be hashable, and support equality comparison.
+ u = {}
+ try:
+ for x in s:
+ u[x] = 1
+ except TypeError:
+ del u # move on to the next method
+ else:
+ return u.keys()
+
+ # We can't hash all the elements. Second fastest is to sort,
+ # which brings the equal elements together; then duplicates are
+ # easy to weed out in a single pass.
+ # NOTE: Python's list.sort() was designed to be efficient in the
+ # presence of many duplicate elements. This isn't true of all
+ # sort functions in all languages or libraries, so this approach
+ # is more effective in Python than it may be elsewhere.
+ try:
+ t = list(s)
+ t.sort()
+ except TypeError:
+ del t # move on to the next method
+ else:
+ assert n > 0
+ last = t[0]
+ lasti = i = 1
+ while i < n:
+ if t[i] != last:
+ t[lasti] = last = t[i]
+ lasti += 1
+ i += 1
+ return t[:lasti]
+
+ # Brute force is all that's left.
+ u = []
+ for x in s:
+ if x not in u:
+ u.append(x)
+ return u
+
+ def tagByName(self, tag):
+ data = self.hdr[tag]
+ if type(data) is types.ListType:
+ if len(data) > 0:
+ return data[0]
+ else:
+ return ''
+ else:
+ return data
+
+ def listTagByName(self, tag):
+ """take a tag that should be a list and make sure it is one"""
+ lst = []
+ data = self.hdr[tag]
+ if data is None:
+ return lst
+
+ if type(data) is types.ListType:
+ lst.extend(data)
+ else:
+ lst.append(data)
+ return lst
+
+ def epoch(self):
+ if self.hdr['epoch'] is None:
+ return 0
+ else:
+ return self.tagByName('epoch')
+
+ def doChecksumCache(self, fo):
+ """return a checksum for a package:
+ - check if the checksum cache is enabled
+ if not - return the checksum
+ if so - check to see if it has a cache file
+ if so, open it and return the first line's contents
+ if not, grab the checksum and write it to a file for this pkg
+ """
+ if not self.options['cache']:
+ return getChecksum(self.options['sumtype'], fo)
+
+ csumtag = os.path.basename(self.relativepath) + ".cache"
+ csumfile = '%s/%s' % (self.options['cachedir'], csumtag)
+ if os.path.exists(csumfile) and self.mtime <= os.stat(csumfile)[8]:
+ csumo = open(csumfile, 'r')
+ checksum = csumo.readline()
+ csumo.close()
+
+ else:
+ checksum = getChecksum(self.options['sumtype'], fo)
+ csumo = open(csumfile, 'w')
+ csumo.write(checksum)
+ csumo.close()
+
+ return checksum
+
+
+
+def generateXML(doc, node, formatns, drpmObj, sumtype, pkgDeltas):
+ """takes an xml doc object and a package metadata entry node, populates a
+ package node with the md information"""
+ name = drpmObj.tagByName('name')
+ arch = drpmObj.arch()
+ epoch = str(drpmObj.epoch())
+ ver = str(drpmObj.tagByName('version'))
+ rel = str(drpmObj.tagByName('release'))
+ if not pkgDeltas.has_key('%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)):
+ pkgNode = node.newChild(None, "package", None)
+ pkgNode.newProp('type', 'rpm')
+ pkgNode.newChild(None, 'name', name)
+ pkgNode.newChild(None, 'arch', arch)
+ version = pkgNode.newChild(None, 'version', None)
+ version.newProp('epoch', epoch)
+ version.newProp('ver', ver)
+ version.newProp('rel', rel)
+ deltas = pkgNode.newChild(None, 'deltas', None)
+ pkgDeltas['%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)] = deltas
+ else:
+ deltas = pkgDeltas['%s-%s:%s-%s.%s' % (name, epoch, ver, rel, arch)]
+ (oldname, oldepoch, oldver, oldrel) = drpmObj.oldnevr
+ drpmNode = deltas.newChild(None, "oldrpm", None)
+ if name != oldname:
+ drpmNode.newChild(None, 'name', oldname)
+ # oldrpm arch is not stored in drpm, so we can only work within same arch
+ version = drpmNode.newChild(None, 'version', None)
+ if epoch != oldepoch:
+ version.newProp('epoch', oldepoch)
+ if ver != oldver:
+ version.newProp('ver', oldver)
+ version.newProp('rel', oldrel)
+ drpmNode.newChild(None, 'drpm_filename', drpmObj.relativepath)
+ drpmNode.newChild(None, 'size', str(drpmObj.size))
+ drpmNode.newChild(None, 'sequence', '%s-%s' % (drpmObj.oldnevrstring, drpmObj.sequence))
+ checksum = drpmNode.newChild(None, 'checksum', drpmObj.pkgid)
+ checksum.newProp('type', drpmObj.options['sumtype'])
+
+
+def repoXML(node, cmds):
+ """generate the repomd.xml file that stores the info on the other files"""
+ sumtype = cmds['sumtype']
+ workfiles = [(cmds['prestofile'], 'deltas')]
+
+
+ for (file, ftype) in workfiles:
+ zfo = _gzipOpen(os.path.join(cmds['outputdir'], cmds['tempdir'], file))
+ uncsum = getChecksum(sumtype, zfo)
+ zfo.close()
+ csum = getChecksum(sumtype, os.path.join(cmds['outputdir'], cmds['tempdir'], file))
+ timestamp = os.stat(os.path.join(cmds['outputdir'], cmds['tempdir'], file))[8]
+ data = node.newChild(None, 'data', None)
+ data.newProp('type', ftype)
+ location = data.newChild(None, 'location', None)
+ if cmds['baseurl'] is not None:
+ location.newProp('xml:base', cmds['baseurl'])
+ location.newProp('href', os.path.join(cmds['finaldir'], file))
+ checksum = data.newChild(None, 'checksum', csum)
+ checksum.newProp('type', sumtype)
+ timestamp = data.newChild(None, 'timestamp', str(timestamp))
+ unchecksum = data.newChild(None, 'open-checksum', uncsum)
+ unchecksum.newProp('type', sumtype)
+
diff --git a/presto-utils/gendeltarpms.py b/presto-utils/gendeltarpms.py
new file mode 100755
index 0000000..7db9d4f
--- /dev/null
+++ b/presto-utils/gendeltarpms.py
@@ -0,0 +1,324 @@
+#!/usr/bin/python -t
+# -*- mode: Python; indent-tabs-mode: nil; -*-
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import errno, os, sys
+import fnmatch, re
+import rpmUtils.transaction, rpmUtils.miscutils
+import commands
+import string
+import getopt
+from dumpMetadata import byteranges
+from packagelist import RpmItem, DrpmItem, PackageList
+
+import gzip
+from zlib import error as zlibError
+from gzip import write32u, FNAME
+
+DRPMWORTHKEEPINGTHRESH=0.5
+DEBUG=0
+__version__ = "0.2.0"
+
+def errorprint(stuff):
+ print >> sys.stderr, stuff
+
+def _(args):
+ """Stub function for translation"""
+ return args
+
+def usage(retval=1):
+ print _("""
+ createdeltarpms [options] directory-of-packages directory-of-deltarpms
+
+ Options:
+ -d, --dist-update <dir> = optional directory containing old distribution
+ to create an update from
+ -c, --count <number> = optional maximum number of deltarpms to create
+ per package. Default is maximum available
+ -n, --no-first = Don't create deltarpm against first rpm if number exceeds
+ count. Useful if you are running a continually-updated
+ distribution rather than one with set release cycles.
+ -q, --quiet = run quietly
+ -v, --verbose = run verbosely
+ -h, --help = show this help
+ -V, --version = output version
+ """)
+
+ sys.exit(retval)
+
+def getDeltaNevr(filename):
+ """Get nevr for src rpm of a deltarpm. Return a tuple of (n, e, v, r)"""
+ def _getLength(in_data):
+ length = 0
+ for val in in_data:
+ length = length * 256
+ length += ord(val)
+ return length
+
+ def _stringToVersion(strng):
+ i = strng.find(':')
+ if i != -1:
+ epoch = strng[:i]
+ else:
+ epoch = '0'
+ j = strng.find('-')
+ if j != -1:
+ if strng[i + 1:j] == '':
+ version = None
+ else:
+ version = strng[i + 1:j]
+ release = strng[j + 1:]
+ else:
+ if strng[i + 1:] == '':
+ version = None
+ else:
+ version = strng[i + 1:]
+ release = None
+ return (epoch, version, release)
+
+ def _stringToNEVR(string):
+ i = string.rfind("-", 0, string.rfind("-")-1)
+ name = string[:i]
+ (epoch, ver, rel) = _stringToVersion(string[i+1:])
+ return (name, epoch, ver, rel)
+
+ (range_start, range_end) = byteranges(filename)
+ fd = open(filename, "r")
+ fd.seek(range_end)
+ try:
+ compobj = gzip.GzipFile("", "rb", 9, fd)
+ except:
+ raise zlibError("Data not stored in gzip format")
+
+ if compobj.read(4)[:3] != "DLT":
+ raise Exception("Not a deltarpm")
+
+ nevr_length = _getLength(compobj.read(4))
+ nevr = compobj.read(nevr_length).strip("\x00")
+ oldnevr = _stringToNEVR(nevr)
+ compobj.close()
+ return oldnevr
+
+
+def getFileList(path, ext, filelist):
+ """Return all files in path matching ext, store them in filelist,
+ recurse dirs. Returns a list object"""
+
+ extlen = len(ext)
+ totalpath = os.path.normpath(path)
+ dir_list = os.listdir(totalpath)
+
+ for d in dir_list:
+ if os.path.isdir(totalpath + '/' + d):
+ filelist = getFileList(os.path.join(totalpath, d), ext, filelist)
+ elif string.lower(d[-extlen:]) == '%s' % (ext):
+ filelist.append(os.path.join(totalpath, d))
+
+ return filelist
+
+def createPrestoRepo(base_dir, drpm_dir, dst_dir=None, DrpmsPerPackage=0, DoFirst=True):
+ ts = rpmUtils.transaction.initReadOnlyTransaction()
+ changed = False
+
+ # Create list of .rpm files.
+ # We don't use "glob", so sub-directories are supported.
+ print 'Using base dir: %s' % base_dir
+ print 'Using destination dir: %s' % drpm_dir
+
+ try:
+ rpm_files = getFileList(base_dir, ".rpm", [])
+ except OSError, e:
+ errorprint(_("Error: Unable to find directory %s: %s" % (base_dir, e)))
+ return False
+ if dst_dir != None:
+ try:
+ dst_rpm_files = getFileList(dst_dir, ".rpm", [])
+ except OSError, e:
+ errorprint(_("Error: Unable to find directory %s: %s" % (dst_dir, e)))
+ return False
+ try:
+ drpm_files = getFileList(drpm_dir, ".drpm", [])
+ except:
+ drpm_files = []
+ if not len(rpm_files):
+ print ' Nothing found.'
+ return changed
+
+ if dst_dir != None and not len(dst_rpm_files):
+ print ' No new rpms found.'
+ return changed
+
+ # Check whether rel_dir exists, and if it doesn't, create it
+ if not os.access(drpm_dir, os.F_OK):
+ os.makedirs(drpm_dir, 0755)
+ elif not os.access(drpm_dir, os.W_OK):
+ print 'ERROR: Unable to write to %s' % drpm_dir
+ sys.exit(1)
+
+ # Add all rpms to PackageList
+ rpm_list = PackageList(base_dir, drpm_dir, DRPMWORTHKEEPINGTHRESH, DEBUG)
+ for f in rpm_files:
+ try:
+ hdr = rpmUtils.miscutils.hdrFromPackage(ts, f)
+ except:
+ print "Unable to open %s" % f
+ else:
+ nm = hdr['name'] + "." + hdr['arch']
+ e = hdr['epoch']
+ if e is None:
+ e = "0"
+
+ nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch'])
+ rpm = RpmItem(nevra, f)
+ rpm_list.addRpm(rpm)
+
+ # If we have a new distro, add its rpms with epoch bumped +5
+ if dst_dir != None:
+ for f in dst_rpm_files:
+ try:
+ hdr = rpmUtils.miscutils.hdrFromPackage(ts, f)
+ except:
+ print "Unable to open %s" % f
+ else:
+ nm = hdr['name'] + "." + hdr['arch']
+ e = hdr['epoch']
+ if e is None:
+ e = "0"
+ e = str(int(e) + 5)
+
+ nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch'])
+ rpm = RpmItem(nevra, f)
+ rpm_list.addRpm(rpm)
+
+ # Add all deltarpms to PackageList
+ for f in drpm_files:
+ try:
+ hdr = rpmUtils.miscutils.hdrFromPackage(ts, f)
+ except:
+ print "Unable to open %s" % f
+ else:
+ (sn, se, sv, sr) = getDeltaNevr(f)
+
+ nm = hdr['name'] + "." + hdr['arch']
+ e = hdr['epoch']
+ if e is None:
+ e = "0"
+
+ dst_nevra = (hdr['name'], e, hdr['version'], hdr['release'], hdr['arch'])
+ src_nevra = (sn, se, sv, sr, hdr['arch'])
+ drpm = DrpmItem(src_nevra, dst_nevra, f)
+ rpm_list.addDrpm(drpm)
+
+ if DEBUG:
+ rpm_list.dump()
+
+ # Build deltarpms
+ rpm_list.makeDeltaRpms(DrpmsPerPackage, DoFirst)
+ return True
+
+def parseArgs(args):
+ """
+ Parse the command line args return a commands dict and directory.
+ Sanity check all the things being passed in.
+ """
+ cmds = {}
+ cmds['quiet'] = 0
+ cmds['verbose'] = 0
+ cmds['dist-update'] = None
+ cmds['count'] = None
+ cmds['do-first'] = 1
+ try:
+ gopts, argsleft = getopt.getopt(args, 'hqvVd:c:n', ['help', 'quiet', 'verbose', 'version', 'dist-update=', 'count=', 'no-first'])
+ except getopt.error, e:
+ errorprint(_('Options Error: %s.') % e)
+ usage()
+
+ try:
+ for arg,a in gopts:
+ if arg in ['-h','--help']:
+ usage(retval=0)
+ elif arg in ['-V', '--version']:
+ print '%s' % __version__
+ sys.exit(0)
+ except ValueError, e:
+ errorprint(_('Options Error: %s') % e)
+ usage()
+
+
+ # make sure our dir makes sense before we continue
+ if len(argsleft) > 2:
+ errorprint(_('Error: You may only specify one rpm directory and one deltarpm directory.'))
+ usage()
+ elif len(argsleft) == 0:
+ errorprint(_('Error: Must specify an rpm directory to index and a destination deltarpm directory.'))
+ usage()
+ else:
+ directories = argsleft
+
+ try:
+ for arg,a in gopts:
+ if arg in ['-v', '--verbose']:
+ cmds['verbose'] = 1
+ elif arg in ["-q", '--quiet']:
+ cmds['quiet'] = 1
+ elif arg in ["-n", '--no-first']:
+ cmds['do-first'] = 0
+ elif arg in ['-d', '--dist-update']:
+ if cmds['dist-update'] is not None:
+ errorprint(_('Error: Only one directory for distribution updates allowed.'))
+ usage()
+ else:
+ cmds['dist-update'] = a
+ elif arg in ['-c', '--count']:
+ if cmds['count'] is not None:
+ errorprint(_('Error: Only one count allowed.'))
+ usage()
+ else:
+ cmds['count'] = int(a)
+
+ except ValueError, e:
+ errorprint(_('Options Error: %s') % e)
+ usage()
+
+ rpm_directory = directories[0]
+ drpm_directory = directories[1]
+
+ rpm_directory = os.path.normpath(rpm_directory)
+ drpm_directory = os.path.normpath(drpm_directory)
+
+ # Fixup first directory
+ directories[0] = rpm_directory
+ directories[1] = drpm_directory
+
+ # Set count
+ if cmds['count'] == None:
+ cmds['count'] = 0
+
+ return cmds, directories
+
+if __name__ == '__main__':
+ cmds, directories = parseArgs(sys.argv[1:])
+ if cmds['dist-update'] != None:
+ if createPrestoRepo(cmds['dist-update'], directories[1], directories[0], cmds['count'], cmds['do-first']):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+ else:
+ if createPrestoRepo(directories[0], directories[1], None, cmds['count'], cmds['do-first']):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
diff --git a/presto-utils/genprestometadata.py b/presto-utils/genprestometadata.py
new file mode 100755
index 0000000..6788ffc
--- /dev/null
+++ b/presto-utils/genprestometadata.py
@@ -0,0 +1,525 @@
+#!/usr/bin/python -t
+# primary functions and glue for generating the repository metadata
+#
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# Copyright 2004 Duke University
+# Copyright 2007 Jonathan Dieter
+
+
+import os
+import sys
+import getopt
+import rpm
+import libxml2
+import string
+import fnmatch
+import urlgrabber
+import commands
+import shutil
+
+import dumpMetadata
+from dumpMetadata import _gzipOpen
+__version__ = '0.2.0'
+
+def errorprint(stuff):
+ print >> sys.stderr, stuff
+
+def _(args):
+ """Stub function for translation"""
+ return args
+
+def usage(retval=1):
+ print _("""
+ createrepo [options] directory-of-packages
+
+ Options:
+ -u, --baseurl <url> = optional base url location for all files
+ -o, --outputdir <dir> = optional directory to output to
+ -x, --exclude = files globs to exclude, can be specified multiple times
+ -m, --merge = merge with repository created by createrepo
+ -q, --quiet = run quietly
+ -v, --verbose = run verbosely
+ -c, --cachedir <dir> = specify which dir to use for the checksum cache
+ -h, --help = show this help
+ -V, --version = output version
+ -p, --pretty = output xml files in pretty format.
+ """)
+
+ sys.exit(retval)
+
+class MetaDataGenerator:
+ def __init__(self, cmds):
+ self.cmds = cmds
+ self.ts = rpm.TransactionSet()
+ self.pkgcount = 0
+ self.newrpms = {}
+ self.files = []
+
+ def getFileList(self, basepath, path, ext, filelist):
+ """Return all files in path matching ext, store them in filelist,
+ recurse dirs. Returns a list object"""
+
+ extlen = len(ext)
+ totalpath = os.path.normpath(os.path.join(basepath, path))
+ try:
+ dir_list = os.listdir(totalpath)
+ except OSError, e:
+ errorprint(_('Error accessing directory %s, %s') % (totalpath, e))
+ sys.exit(1)
+
+ for d in dir_list:
+ if os.path.isdir(totalpath + '/' + d):
+ filelist = self.getFileList(basepath, os.path.join(path, d), ext, filelist)
+ else:
+ if string.lower(d[-extlen:]) == '%s' % (ext):
+ if totalpath.find(basepath) == 0:
+ relativepath = totalpath.replace(basepath, "", 1)
+ relativepath = relativepath.lstrip("/")
+ filelist.append(os.path.join(relativepath, d))
+ else:
+ raise "basepath '%s' not found in path '%s'" % (basepath, totalpath)
+
+ return filelist
+
+
+ def trimRpms(self, files):
+ badrpms = []
+ for file in files:
+ for glob in self.cmds['excludes']:
+ if fnmatch.fnmatch(file, glob):
+ # print 'excluded: %s' % file
+ if file not in badrpms:
+ badrpms.append(file)
+ for file in badrpms:
+ if file in files:
+ files.remove(file)
+ return files
+
+ def doPkgMetadata(self, directory):
+ """all the heavy lifting for the package metadata"""
+
+ # rpms we're going to be dealing with
+ files = self.getFileList(self.cmds['basedir'], directory, '.drpm', [])
+ files = self.trimRpms(files)
+ self.pkgcount = len(files)
+ self.openMetadataDocs()
+ self.writeMetadataDocs(files)
+ self.closeMetadataDocs()
+
+
+ def openMetadataDocs(self):
+ self._setupPresto()
+
+ def _setupPresto(self):
+ # setup the base metadata doc
+ self.prestodoc = libxml2.newDoc("1.0")
+ self.prestoroot = self.prestodoc.newChild(None, "metadata", None)
+ basens = self.prestoroot.newNs('http://linux.duke.edu/metadata/common', None)
+ self.formatns = self.prestoroot.newNs('http://linux.duke.edu/metadata/rpm', 'rpm')
+ self.prestoroot.setNs(basens)
+ prestofilepath = os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile'])
+ if self.cmds['merge'] == 1:
+ self.prestofile = open(prestofilepath, 'w')
+ else:
+ self.prestofile = _gzipOpen(prestofilepath, 'w')
+ self.prestofile.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+ self.prestofile.write('<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="%s">\n' %
+ self.pkgcount)
+
+
+ def writeMetadataDocs(self, files, current=0):
+ for file in files:
+ current+=1
+ try:
+ mdobj = dumpMetadata.RpmMetaData(self.ts, self.cmds['basedir'], file, self.cmds, True)
+ if not self.cmds['quiet']:
+ if self.cmds['verbose']:
+ print '%d/%d - %s' % (current, len(files), file)
+ else:
+ sys.stdout.write('\r' + ' ' * 80)
+ sys.stdout.write("\r%d/%d - %s" % (current, self.pkgcount, file))
+ sys.stdout.flush()
+ except dumpMetadata.MDError, e:
+ errorprint('\n%s - %s' % (e, file))
+ continue
+ else:
+ try:
+ dumpMetadata.generateXML(self.prestodoc, self.prestoroot, self.formatns, mdobj, self.cmds['sumtype'], self.newrpms)
+ except dumpMetadata.MDError, e:
+ errorprint(_('\nAn error occurred creating presto metadata: %s') % e)
+ continue
+ return current
+
+
+ def closeMetadataDocs(self):
+ if not self.cmds['quiet']:
+ print ''
+
+ # save them up to the tmp locations:
+ if not self.cmds['quiet']:
+ print _('Saving Presto metadata')
+ output = self.prestoroot.serialize('UTF-8', self.cmds['pretty'])
+ output = output[output.find("\n")+1:]
+ self.prestofile.write(output)
+ self.prestofile.write("\n")
+ self.prestofile.close()
+ self.prestodoc.freeDoc()
+
+ def doRepoMetadata(self):
+ if self.cmds['prestomdfile'] == None: # Merge
+ shutil.copyfile(os.path.join(self.cmds['outputdir'], self.cmds['finaldir'], "repomd.xml"), os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], "repomd.xml"))
+ command = "/usr/bin/modifyrepo %s %s" % (os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile']), os.path.join(self.cmds['outputdir'], self.cmds['tempdir']))
+ (code, out) = commands.getstatusoutput(command)
+ if code:
+ errorprint(_('\nAn error occurred trying to run modifyrepo: %s') % e)
+ sys.exit(1)
+ os.unlink(os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestofile']))
+ self.cmds['prestofile'] = "%s.gz" % self.cmds['prestofile']
+ self.cmds['prestomdfile'] = "repomd.xml"
+ else:
+ """wrapper to generate the prestomd.xml file that stores the info on the other files"""
+ repodoc = libxml2.newDoc("1.0")
+ reporoot = repodoc.newChild(None, "repomd", None)
+ repons = reporoot.newNs('http://linux.duke.edu/metadata/repo', None)
+ reporoot.setNs(repons)
+ repofilepath = os.path.join(self.cmds['outputdir'], self.cmds['tempdir'], self.cmds['prestomdfile'])
+
+ try:
+ dumpMetadata.repoXML(reporoot, self.cmds)
+ except dumpMetadata.MDError, e:
+ errorprint(_('Error generating repo xml file: %s') % e)
+ sys.exit(1)
+
+ try:
+ repodoc.saveFormatFileEnc(repofilepath, 'UTF-8', 1)
+ except:
+ errorprint(_('Error saving temp file for rep xml: %s') % repofilepath)
+ sys.exit(1)
+
+ del repodoc
+
+class SplitMetaDataGenerator(MetaDataGenerator):
+
+ def __init__(self, cmds):
+ MetaDataGenerator.__init__(self, cmds)
+ self.initialdir = self.cmds['basedir']
+
+ def _getFragmentUrl(self, url, fragment):
+ import urlparse
+ urlparse.uses_fragment.append('media')
+ if not url:
+ return url
+ (scheme, netloc, path, query, fragid) = urlparse.urlsplit(url)
+ return urlparse.urlunsplit((scheme, netloc, path, query, str(fragment)))
+
+ def doPkgMetadata(self, directories):
+ """all the heavy lifting for the package metadata"""
+ import types
+ if type(directories) == types.StringType:
+ MetaDataGenerator.doPkgMetadata(self, directories)
+ return
+ filematrix = {}
+ for mydir in directories:
+ filematrix[mydir] = self.getFileList(os.path.join(self.initialdir, mydir), '.', '.rpm', [])
+ self.trimRpms(filematrix[mydir])
+ self.pkgcount += len(filematrix[mydir])
+
+ mediano = 1
+ current = 0
+ self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], mediano)
+ self.cmds['basedir'] = os.path.join(self.initialdir, directories[0])
+ self.openMetadataDocs()
+ for mydir in directories:
+ self.cmds['basedir'] = os.path.join(self.initialdir, mydir)
+ self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], mediano)
+ current = self.writeMetadataDocs(filematrix[mydir], current)
+ mediano += 1
+ self.cmds['basedir'] = os.path.join(self.initialdir, directories[0])
+ self.cmds['baseurl'] = self._getFragmentUrl(self.cmds['baseurl'], 1)
+ self.closeMetadataDocs()
+
+
+def checkAndMakeDir(dir):
+ """
+ check out the dir and make it, if possible, return 1 if done, else return 0
+ """
+ if os.path.exists(dir):
+ if not os.path.isdir(dir):
+ errorprint(_('%s is not a dir') % dir)
+ result = False
+ else:
+ if not os.access(dir, os.W_OK):
+ errorprint(_('%s is not writable') % dir)
+ result = False
+ else:
+ result = True
+ else:
+ try:
+ os.mkdir(dir)
+ except OSError, e:
+ errorprint(_('Error creating dir %s: %s') % (dir, e))
+ result = False
+ else:
+ result = True
+ return result
+
+def parseArgs(args):
+ """
+ Parse the command line args return a commands dict and directory.
+ Sanity check all the things being passed in.
+ """
+ cmds = {}
+ cmds['quiet'] = 0
+ cmds['verbose'] = 0
+ cmds['excludes'] = []
+ cmds['baseurl'] = None
+ cmds['sumtype'] = 'sha'
+ cmds['merge'] = 0
+ cmds['pretty'] = 0
+ cmds['cachedir'] = None
+ cmds['basedir'] = os.getcwd()
+ cmds['cache'] = False
+ cmds['split'] = False
+ cmds['outputdir'] = ""
+ cmds['file-pattern-match'] = ['.*bin\/.*', '^\/etc\/.*', '^\/usr\/lib\/sendmail$']
+ cmds['dir-pattern-match'] = ['.*bin\/.*', '^\/etc\/.*']
+
+ try:
+ gopts, argsleft = getopt.getopt(args, 'phqVvms:x:u:c:o:', ['help', 'exclude=',
+ 'quiet', 'verbose', 'cachedir=', 'basedir=',
+ 'baseurl=', 'checksum=',
+ 'version', 'pretty', 'split', 'outputdir='])
+ except getopt.error, e:
+ errorprint(_('Options Error: %s.') % e)
+ usage()
+
+ try:
+ for arg,a in gopts:
+ if arg in ['-h','--help']:
+ usage(retval=0)
+ elif arg in ['-V', '--version']:
+ print '%s' % __version__
+ sys.exit(0)
+ elif arg == '--split':
+ cmds['split'] = True
+ except ValueError, e:
+ errorprint(_('Options Error: %s') % e)
+ usage()
+
+
+ # make sure our dir makes sense before we continue
+ if len(argsleft) > 1 and not cmds['split']:
+ errorprint(_('Error: Only one directory allowed per run.'))
+ usage()
+ elif len(argsleft) == 0:
+ errorprint(_('Error: Must specify a directory to index.'))
+ usage()
+ else:
+ directories = argsleft
+
+ try:
+ for arg,a in gopts:
+ if arg in ['-v', '--verbose']:
+ cmds['verbose'] = 1
+ elif arg in ["-q", '--quiet']:
+ cmds['quiet'] = 1
+ elif arg in ['-u', '--baseurl']:
+ if cmds['baseurl'] is not None:
+ errorprint(_('Error: Only one baseurl allowed.'))
+ usage()
+ else:
+ cmds['baseurl'] = a
+ elif arg in ['-x', '--exclude']:
+ cmds['excludes'].append(a)
+ elif arg in ['-p', '--pretty']:
+ cmds['pretty'] = 1
+ elif arg in ['-m', '--merge']:
+ cmds['merge'] = 1
+ elif arg in ['-c', '--cachedir']:
+ cmds['cache'] = True
+ cmds['cachedir'] = a
+ elif arg == '--basedir':
+ cmds['basedir'] = a
+ elif arg in ['-o','--outputdir']:
+ cmds['outputdir'] = a
+
+ except ValueError, e:
+ errorprint(_('Options Error: %s') % e)
+ usage()
+
+ directory = directories[0]
+# Fix paths
+ directory = os.path.normpath(directory)
+ if cmds['split']:
+ pass
+ elif os.path.isabs(directory):
+ cmds['basedir'] = directory
+ directory = '.'
+ else:
+ cmds['basedir'] = os.path.realpath(os.path.join(cmds['basedir'], directory))
+ directory = '.'
+ if not cmds['outputdir']:
+ cmds['outputdir'] = cmds['basedir']
+ if cmds['cachedir']:
+ a = cmds ['cachedir']
+ if not os.path.isabs(a):
+ a = os.path.join(cmds['basedir'] ,a)
+ if not checkAndMakeDir(a):
+ errorprint(_('Error: cannot open/write to cache dir %s' % a))
+ usage()
+ cmds['cachedir'] = a
+
+ #setup some defaults
+ if cmds['merge'] == 1:
+ cmds['prestofile'] = 'deltas.xml'
+ cmds['prestomdfile'] = None
+ else:
+ cmds['prestofile'] = 'presto.xml.gz'
+ cmds['prestomdfile'] = 'prestomd.xml'
+ cmds['tempdir'] = '.repodata'
+ cmds['finaldir'] = 'repodata'
+ cmds['olddir'] = '.olddata'
+
+ # Fixup first directory
+ directories[0] = directory
+ return cmds, directories
+
+def main(args):
+ cmds, directories = parseArgs(args)
+ directory = directories[0]
+ # start the sanity/stupidity checks
+ if not os.path.exists(os.path.join(cmds['basedir'], directory)):
+ errorprint(_('Directory must exist'))
+ sys.exit(1)
+
+ if not os.path.isdir(os.path.join(cmds['basedir'], directory)):
+ errorprint(_('Directory of packages must be a directory.'))
+ sys.exit(1)
+
+ if not os.access(cmds['outputdir'], os.W_OK):
+ errorprint(_('Directory must be writable.'))
+ sys.exit(1)
+
+ if cmds['split']:
+ oldbase = cmds['basedir']
+ cmds['basedir'] = os.path.join(cmds['basedir'], directory)
+ if not checkAndMakeDir(os.path.join(cmds['outputdir'], cmds['tempdir'])):
+ sys.exit(1)
+
+ if not checkAndMakeDir(os.path.join(cmds['outputdir'], cmds['finaldir'])):
+ sys.exit(1)
+
+ if os.path.exists(os.path.join(cmds['outputdir'], cmds['olddir'])):
+ errorprint(_('Old data directory exists, please remove: %s') % cmds['olddir'])
+ sys.exit(1)
+
+ # make sure we can write to where we want to write to:
+ for direc in ['tempdir', 'finaldir']:
+ for file in ['prestofile', 'prestomdfile']:
+ if cmds[file] != None:
+ filepath = os.path.join(cmds['outputdir'], cmds[direc], cmds[file])
+ if os.path.exists(filepath):
+ if not os.access(filepath, os.W_OK):
+ errorprint(_('error in must be able to write to metadata files:\n -> %s') % filepath)
+ usage()
+
+ if cmds['split']:
+ cmds['basedir'] = oldbase
+ mdgen = SplitMetaDataGenerator(cmds)
+ mdgen.doPkgMetadata(directories)
+ else:
+ mdgen = MetaDataGenerator(cmds)
+ mdgen.doPkgMetadata(directory)
+ mdgen.doRepoMetadata()
+
+ if os.path.exists(os.path.join(cmds['outputdir'], cmds['finaldir'])):
+ try:
+ os.rename(os.path.join(cmds['outputdir'], cmds['finaldir']),
+ os.path.join(cmds['outputdir'], cmds['olddir']))
+ except:
+ errorprint(_('Error moving final %s to old dir %s' % (os.path.join(cmds['outputdir'], cmds['finaldir']),
+ os.path.join(cmds['outputdir'], cmds['olddir']))))
+ sys.exit(1)
+
+ try:
+ os.rename(os.path.join(cmds['outputdir'], cmds['tempdir']),
+ os.path.join(cmds['outputdir'], cmds['finaldir']))
+ except:
+ errorprint(_('Error moving final metadata into place'))
+ # put the old stuff back
+ os.rename(os.path.join(cmds['outputdir'], cmds['olddir']),
+ os.path.join(cmds['outputdir'], cmds['finaldir']))
+ sys.exit(1)
+
+ for file in ['prestofile', 'prestomdfile']:
+ if cmds[file]:
+ fn = os.path.basename(cmds[file])
+ else:
+ continue
+ oldfile = os.path.join(cmds['outputdir'], cmds['olddir'], fn)
+ if os.path.exists(oldfile):
+ try:
+ os.remove(oldfile)
+ except OSError, e:
+ errorprint(_('Could not remove old metadata file: %s') % oldfile)
+ errorprint(_('Error was %s') % e)
+ sys.exit(1)
+
+
+ # Move everything else back from olddir (eg. repoview files)
+ olddir = os.path.join(cmds['outputdir'], cmds['olddir'])
+ finaldir = os.path.join(cmds['outputdir'], cmds['finaldir'])
+ for file in os.listdir(olddir):
+ oldfile = os.path.join(olddir, file)
+ finalfile = os.path.join(finaldir, file)
+ if os.path.exists(finalfile):
+ # Hmph? Just leave it alone, then.
+ try:
+ if os.path.isdir(oldfile):
+ shutil.rmtree(oldfile)
+ else:
+ os.remove(oldfile)
+ except OSError, e:
+ errorprint(_('Could not remove old non-metadata file: %s') % oldfile)
+ errorprint(_('Error was %s') % e)
+ sys.exit(1)
+ else:
+ try:
+ os.rename(oldfile, finalfile)
+ except OSError, e:
+ errorprint(_('Could not restore old non-metadata file: %s -> %s') % (oldfile, finalfile))
+ errorprint(_('Error was %s') % e)
+ sys.exit(1)
+
+#XXX: fix to remove tree as we mung basedir
+ try:
+ os.rmdir(os.path.join(cmds['outputdir'], cmds['olddir']))
+ except OSError, e:
+ errorprint(_('Could not remove old metadata dir: %s') % cmds['olddir'])
+ errorprint(_('Error was %s') % e)
+ errorprint(_('Please clean up this directory manually.'))
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ if sys.argv[1] == 'profile':
+ import hotshot
+ p = hotshot.Profile(os.path.expanduser("~/createprestorepo.prof"))
+ p.run('main(sys.argv[2:])')
+ p.close()
+ else:
+ main(sys.argv[1:])
+ else:
+ main(sys.argv[1:])
diff --git a/presto-utils/packagelist.py b/presto-utils/packagelist.py
new file mode 100644
index 0000000..e9019f9
--- /dev/null
+++ b/presto-utils/packagelist.py
@@ -0,0 +1,386 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# Copyright (c) 2007 Jonathan Dieter
+
+import rpmUtils.miscutils
+import os
+import commands
+
+class RpmItem:
+ def __init__(self, nevra, f):
+ (self.name, self.epoch, self.version, self.release, self.arch) = nevra
+ self.epoch = str(self.epoch)
+ self.filename = f
+
+ def getNevra(self):
+ return (self.name, self.epoch, self.version, self.release, self.arch)
+
+ def __eq__(self, b):
+ if b == None:
+ return False
+ try:
+ # An RpmItem is equal to another RpmItem if the nevra's are equal
+ if self.getNevra() == b.getNevra():
+ return True
+ else:
+ return False
+ except:
+ # An RpmItem is equal to a DrpmItem if the nevra is equal to the drpm's destination nevra
+ if self.getNevra() == b.getDstNevra():
+ return True
+ else:
+ return False
+
+class DrpmItem:
+ def __init__(self, src_nevra, dst_nevra, f):
+ (self.src_name, self.src_epoch, self.src_version, self.src_release, self.src_arch) = src_nevra
+ (self.dst_name, self.dst_epoch, self.dst_version, self.dst_release, self.dst_arch) = dst_nevra
+ self.src_epoch = str(self.src_epoch)
+ self.dst_epoch = str(self.dst_epoch)
+ self.filename = f
+
+ def exists(self):
+ return self.filename != None
+
+ def getSrcNevra(self):
+ return (self.src_name, self.src_epoch, self.src_version, self.src_release, self.src_arch)
+
+ def getDstNevra(self):
+ return (self.dst_name, self.dst_epoch, self.dst_version, self.dst_release, self.dst_arch)
+
+ def getSrcDstNevra(self):
+ return [self.getSrcNevra(), self.getDstNevra()]
+
+ def __eq__(self, b):
+ if b == None:
+ return False
+ try:
+ # A DrpmItem is equal to another DrpmItem if both source and destination nevra's are equal
+ if self.getSrcDstNevra() == b.getSrcDstNevra():
+ return True
+ else:
+ return False
+ except:
+ # A DrpmItem is equal to an RpmItem if the destination nevra is equal to the rpm's nevra
+ if self.getDstNevra() == b.getNevra():
+ return True
+ else:
+ return False
+
+
+class PackageList:
+ def __init__(self, base_dir, drpm_dir, threshold, debug):
+ self.base_dir = base_dir
+ self.drpm_dir = drpm_dir
+ self.threshold = threshold
+ self.debug = debug
+ self.rpm_list = {}
+ self.sorted = False
+
+ def addRpm(self, rpm):
+ """Add RpmItem to PackageList"""
+ self.sorted = False
+ rpm_key = "%s.%s" % (rpm.name, rpm.arch)
+ self.rpm_list.setdefault(rpm_key, {})
+ self.rpm_list[rpm_key].setdefault("rpms", [])
+ found = False
+ for found_rpm in self.rpm_list[rpm_key]["rpms"]:
+ if rpm == found_rpm:
+ found = True
+ break
+ if not found:
+ self.rpm_list[rpm_key]["rpms"].append(rpm)
+
+ def addDrpm(self, drpm):
+ """Add DrpmItem to PackageList"""
+ self.sorted = False
+ rpm_key = "%s.%s" % (drpm.src_name, drpm.src_arch)
+ self.rpm_list.setdefault(rpm_key, {})
+ self.rpm_list[rpm_key].setdefault("drpms", [])
+ found = False
+ for found_drpm in self.rpm_list[rpm_key]["drpms"]:
+ if drpm == found_drpm:
+ found = True
+ break
+ if not found:
+ self.rpm_list[rpm_key]["drpms"].append(drpm)
+
+ def sort(self):
+ """Sort all rpms and deltarpms in reverse order. This is necessary before running any
+ methods that try to work with rpms and deltarpms"""
+ self.sorted = True
+ for rpm_type in self.rpm_list.values():
+ for key in rpm_type.keys():
+ if key == "rpms" and len(rpm_type["rpms"]) > 1:
+ def sortByEVR(rpm1, rpm2):
+ (n1,e1,v1,r1,a1) = rpm1.getNevra()
+ (n2,e2,v2,r2,a2) = rpm2.getNevra()
+ rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2))
+ if rc == 0:
+ return 0
+ if rc > 0:
+ return -1
+ if rc < 0:
+ return 1
+ rpm_type["rpms"].sort(sortByEVR) # highest first in list
+ elif key == "drpms" and len(rpm_type["drpms"]) > 1:
+ def sortByEVR(drpm1, drpm2, src=False):
+ if src:
+ (n1,e1,v1,r1,a1) = drpm1.getSrcNevra()
+ (n2,e2,v2,r2,a2) = drpm2.getSrcNevra()
+ else:
+ (n1,e1,v1,r1,a1) = drpm1.getDstNevra()
+ (n2,e2,v2,r2,a2) = drpm2.getDstNevra()
+ rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2))
+ if rc == 0:
+ if src:
+ return 0
+ else:
+ return sortByEVR(drpm1, drpm2, True)
+ if rc > 0:
+ return -1
+ if rc < 0:
+ return 1
+ rpm_type["drpms"].sort(sortByEVR) # highest first in list
+
+ def dump(self):
+ """Dump list of all packages and their rpms and deltarpms"""
+ if not self.sorted:
+ self.sort()
+ temp_list = []
+ for x in self.rpm_list.keys():
+ temp_list.append(x)
+ temp_list.sort()
+ for x in temp_list:
+ print x
+ if self.rpm_list[x].has_key("rpms"):
+ print " RPMS:"
+ for rpm in self.rpm_list[x]["rpms"]:
+ print " %s:%s-%s" % (rpm.epoch, rpm.version, rpm.release)
+ if self.rpm_list[x].has_key("drpms"):
+ print " DRPMS:"
+ for drpm in self.rpm_list[x]["drpms"]:
+ print " %s:%s-%s => %s:%s-%s" % (drpm.src_epoch, drpm.src_version, drpm.src_release, drpm.dst_epoch, drpm.dst_version, drpm.dst_release)
+
+ def __doWork(self, nevra1, nevra2, f1, deltaCommand):
+ (n1,e1,v1,r1,a1) = nevra1
+ (n2,e2,v2,r2,a2) = nevra2
+
+ deltaRPMName = os.path.join(self.drpm_dir, '%s-%s-%s_%s-%s.%s.drpm' % (n1, v2, r2, v1, r1, a1))
+ deltaCommand += deltaRPMName
+ if self.debug:
+ print deltaCommand
+
+ if os.path.exists("%s.dontdelta" % deltaRPMName):
+ f = open("%s.dontdelta" % deltaRPMName, "r")
+ drpmsize = f.readline()[:-1]
+ drpmsize = int(drpmsize)
+ if not self.__drpmIsWorthKeeping(drpmsize, f1):
+ return False
+ else:
+ if self.debug:
+ print "Original drpm is now worth creating...deleting %s%s.dontdelta" % (self.base_dir, deltaRPMName)
+ try:
+ os.unlink("%s.dontdelta" % deltaRPMName)
+ except Exception, e:
+ print "Error deleting %s.dontdelta" % os.path.basename(deltaRPMName), str(e)
+
+ (code, out) = commands.getstatusoutput(deltaCommand)
+ if code:
+ print "Error genDeltaRPM for %s: exitcode was %s - Reported Error: %s" % (n1, code, out)
+ return False
+ else:
+ # Check whether or not we should keep the drpm
+ drpmsize = os.path.getsize(deltaRPMName)
+ if not self.__drpmIsWorthKeeping(drpmsize, f1):
+ if self.debug:
+ print 'deleting %s' % (deltaRPMName)
+ f = open("%s.dontdelta" % deltaRPMName, "w")
+ f.write("%i\n" % os.path.getsize(deltaRPMName))
+ f.close()
+ try:
+ os.unlink(deltaRPMName)
+ except Exception, e:
+ print "Error deleting deltarpm %s" % os.path.basename(deltaRPMName), str(e)
+ return False
+ else:
+ if e1 == e2:
+ print 'Generated delta rpm for %s.%s - %s-%s => %s-%s' % (n1, a1, v2, r2, v1, r1)
+ else:
+ print 'Generated delta rpm for %s.%s - %s:%s-%s => %s:%s-%s' % (n1, a1, e2, v2, r2, e1, v1, r1)
+ drpm = DrpmItem(nevra2, nevra1, deltaRPMName)
+ self.addDrpm(drpm)
+ self.sort()
+ if self.debug:
+ self.dump()
+ return True
+
+ def __combineDeltaRpm(self, combine_list, dstrpm):
+ nevra1 = combine_list[-1].getDstNevra()
+ nevra2 = combine_list[0].getSrcNevra()
+ f1 = dstrpm.filename
+
+ deltaCommand = 'combinedeltarpm '
+ for drpm in combine_list:
+ deltaCommand += "%s " % drpm.filename
+
+ return self.__doWork(nevra1, nevra2, f1, deltaCommand)
+
+
+ def __buildDeltaRpm(self, srcrpm, dstrpm):
+ nevra1 = dstrpm.getNevra()
+ nevra2 = srcrpm.getNevra()
+
+ f1 = dstrpm.filename
+ f2 = srcrpm.filename
+ deltaCommand = 'makedeltarpm %s %s ' % (f2, f1)
+
+ return self.__doWork(nevra1, nevra2, f1, deltaCommand)
+
+ def __makeDeltaRpm(self, src_nevra, dstrpm, package):
+ foundSrc = False
+ foundDst = False
+ tempQueue = []
+ if package.has_key("drpms"):
+ for drpm in package["drpms"]:
+ if drpm.getDstNevra() == dstrpm.getNevra() and drpm.getSrcNevra() == src_nevra:
+ return True
+ if drpm.getSrcNevra() == src_nevra:
+ for item in tempQueue:
+ if drpm.getDstNevra() == item.getSrcNevra():
+ combine_list = [drpm, item]
+ return self.__combineDeltaRpm(combine_list, dstrpm)
+ elif drpm.getDstNevra() == dstrpm.getNevra():
+ tempQueue.append(drpm)
+
+ for rpm in package["rpms"]:
+ if rpm.getNevra() == src_nevra:
+ return self.__buildDeltaRpm(rpm, dstrpm)
+
+ print "Found nothing!"
+ return False
+
+
+ def makeDeltaRpms(self, DrpmsPerPackage=0, DoFirst=True, OnlyLastPackage=True):
+ """Create deltarpms for packages.
+ Attributes:
+ OnlyLastPackage (Boolean): Only create deltarpms to point to last destination package
+ DrpmsPerPackage (Int): Number of deltarpms to make for each package (0 = all possible)
+ DoFirst (Boolean): Create deltarpm for first source package"""
+ if not self.sorted:
+ self.sort()
+ temp_list = []
+ for x in self.rpm_list.keys():
+ temp_list.append(x)
+ temp_list.sort()
+ if self.debug:
+ print "Building drpms"
+ for rpm_name in temp_list:
+ package = self.rpm_list[rpm_name]
+ if not package.has_key("rpms") or len(package["rpms"]) == 0:
+ if self.debug:
+ print "No rpms for %s: Doing nothing" % rpm_name
+ continue
+
+ if (not package.has_key("drpms") or len(package["drpms"]) == 0) and len(package["rpms"]) == 1:
+ if self.debug:
+ print "Only one rpm available for %s: Doing nothing" % rpm_name
+ continue
+
+ build_list = []
+
+ for rpm in package["rpms"]:
+ if [rpm.getNevra(), 1] not in build_list:
+ build_list.append([rpm.getNevra(), 1])
+
+ if package.has_key("drpms"):
+ for drpm in package["drpms"]:
+ if [drpm.getSrcNevra(), 1] not in build_list and [drpm.getSrcNevra(), 0] not in build_list:
+ build_list.append([drpm.getSrcNevra(), 0])
+ if [drpm.getDstNevra(), 1] not in build_list and [drpm.getDstNevra(), 0] not in build_list:
+ build_list.append([drpm.getDstNevra(), 0])
+
+ def sortByEVR(rpm1, rpm2):
+ (n1,e1,v1,r1,a1) = rpm1[0]
+ (n2,e2,v2,r2,a2) = rpm2[0]
+ rc = rpmUtils.miscutils.compareEVR((e1,v1,r1),(e2,v2,r2))
+ if rc == 0:
+ return 0
+ if rc > 0:
+ return -1
+ if rc < 0:
+ return 1
+ build_list.sort(sortByEVR)
+
+ if OnlyLastPackage:
+ if build_list[0][1] != 1:
+ continue
+
+ rpm = None
+ for x in package["rpms"]:
+ if x.getNevra() == build_list[0][0]:
+ rpm = x
+ break
+ if rpm == None:
+ continue
+
+ newpkg_list = [rpm]
+ else:
+ newpkg_list = []
+ for build in build_list[:-1]:
+ if build[1] != 1:
+ continue
+
+ rpm = None
+ for x in package["rpms"]:
+ if x.getNevra() == build[0]:
+ rpm = x
+ break
+ if rpm == None:
+ continue
+
+ newpkg_list.append(rpm)
+
+ for newpkg in newpkg_list:
+ start = False
+ count = 0
+ do_last = True
+ for build in build_list[:-1]:
+ if start:
+ self.__makeDeltaRpm(build[0], newpkg, package)
+ if not self.sorted:
+ self.sort()
+ count += 1
+ if DrpmsPerPackage != 0:
+ if count >= DrpmsPerPackage and not DoFirst:
+ do_last = False
+ break
+ elif count >= DrpmsPerPackage - 1 and DoFirst:
+ break
+ if build[0] == newpkg.getNevra():
+ start = True
+
+ if DoFirst == 1 and start and do_last:
+ self.__makeDeltaRpm(build_list[-1][0], newpkg, package)
+ if not self.sorted:
+ self.sort()
+ return
+
+ def __drpmIsWorthKeeping(self, drpmsize, newrpm):
+ newsize = os.path.getsize(newrpm)
+ # Delete the drpm if it's too large
+ if drpmsize > self.threshold * newsize:
+ return False
+ return True
+