From: William Douglas Date: Wed, 27 Feb 2013 21:39:49 +0000 (-0800) Subject: Initial addition of the systemd update mechanism. X-Git-Tag: accepted/trunk/20130310.193633^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fc93cfc4f532206960e57072f90014e26082da3d;p=platform%2Fupstream%2Fswup.git Initial addition of the systemd update mechanism. Create target and service for system-update. Also add update script that just removes the /system-update symlink and reboots (for now). Signed-off-by: William Douglas --- diff --git a/Makefile b/Makefile index 2353027..9655ebb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ +all: install: install -D -m 755 swup.py ${DESTDIR}/usr/bin/swup + install -D -m 755 system-update.sh ${DESTDIR}/usr/bin/system-update install -D -m 755 tools/updateinfo/updateinfo.py ${DESTDIR}/usr/bin/updateinfo + install -D -m 644 system-update.target ${DESTDIR}/usr/lib/systemd/system/system-update.target + install -D -m 644 system-update.service ${DESTDIR}/usr/lib/systemd/system/system-update.service diff --git a/packaging/swup.changes b/packaging/swup.changes index a8494f8..1c2cfc0 100644 --- a/packaging/swup.changes +++ b/packaging/swup.changes @@ -1,3 +1,12 @@ +* Sun Mar 10 2013 Anas Nashif accepted/trunk/20130305.082416@6c0cc3e +- Bump version: 0.2 + +* Tue Mar 05 2013 Anas Nashif submit/trunk/20130305.063957@1b22b82 +- do not install system-update.target + +* Mon Mar 04 2013 Anas Nashif submit/trunk/20130227.214201@9a3dcbd +- Add systemd service + * Mon Feb 25 2013 Anas Nashif submit/trunk/20130225.214101@faf331d - Do not be verbose diff --git a/packaging/swup.spec b/packaging/swup.spec index d2bec7b..e1e43b3 100644 --- a/packaging/swup.spec +++ b/packaging/swup.spec @@ -1,11 +1,12 @@ Name: swup -Version: 0.1 +Version: 0.2 Release: 0 License: GPL-2.0+ Summary: Software Update Tool Url: http://www.tizen.org Group: System/Management Source: %{name}-%{version}.tar.bz2 +BuildRequires: systemd Requires: deltarpm Requires: python-lxml Requires: python-yaml @@ -21,11 +22,17 @@ Software Update Tool. %install %make_install +rm -f %{buildroot}%{_unitdir}/system-update.target +%install_service system-update.target.wants system-update.service %files %defattr(-,root,root) %{_bindir}/swup +%{_bindir}/system-update %{_bindir}/updateinfo +%{_unitdir}/system-update.service +#%{_unitdir}/system-update.target +%{_unitdir}/system-update.target.wants/system-update.service %changelog diff --git a/swup.py b/swup.py index 9742720..4da1d10 100755 --- a/swup.py +++ b/swup.py @@ -15,9 +15,8 @@ import rpm import subprocess as sub import distutils -update_repo="http://anashif-desktop.jf.intel.com/~anashif/tizen-pc/updates" -update_cache="/var/cache/updates" - +update_repo="http://10.0.0.140/~anashif/updates" +update_cache="/var/cache/updatemanager" class FakeSecHead(object): @@ -61,14 +60,14 @@ def probe_updates(): print "Checking for new updates..." response = urllib2.urlopen("%s/data/updatemd.xml" % update_repo ) updatemd = response.read() - if not os.path.exists("%s/data" %update_cache): - os.mkdir("%s/data" %update_cache) + if not os.path.exists("%s/download/metadata" %update_cache): + os.mkdir("%s/download/metadata" %update_cache) - fp = open("%s/data/updatemd.xml" % update_cache , "w") + fp = open("%s/download/metadata/updatemd.xml" % update_cache , "w") fp.write(updatemd) fp.close() - updatemd_local = open("%s/data/updatemd.xml" % update_cache ) + updatemd_local = open("%s/download/metadata/updatemd.xml" % update_cache ) root = etree.XML(updatemd_local.read()) data = root.xpath("//data[@type='updates']")[0] loc = data.xpath("location")[0] @@ -76,8 +75,8 @@ def probe_updates(): chksum = data.xpath("checksum")[0] chksum_type = chksum.attrib['type'] - if os.path.exists("%s/data/updates.xml" % update_cache): - cur_sum = checksum("%s/data/updates.xml" % update_cache, checksum_type=chksum_type) + if os.path.exists("%s/download/metadata/updates.xml" % update_cache): + cur_sum = checksum("%s/download/metadata/updates.xml" % update_cache, checksum_type=chksum_type) if cur_sum == chksum.text: print "Using file from cache, no new updates on server." else: @@ -90,8 +89,7 @@ def probe_updates(): def parse_updates(): updates = {} - - fp = open("%s/data/updates.xml" % update_cache , "r") + fp = open("%s/download/metadata/updates.xml" % update_cache , "r") updates_root = etree.XML(fp.read()) updates_el = updates_root.xpath("//update") for update in updates_el: @@ -110,22 +108,22 @@ def parse_updates(): def download_update(update_data): u = update_data location = u['location'] - if not os.path.exists("%s/downloads" % (update_cache)): - os.mkdir("%s/downloads" % (update_cache)) - if not os.path.exists("%s/downloads/%s" % (update_cache,location)): + if not os.path.exists("%s/download" % (update_cache)): + os.mkdir("%s/download" % (update_cache)) + if not os.path.exists("%s/download/%s/download_done" % (update_cache, u['id'] )): print "Downloading %s/%s" % (update_repo, location) update_file = urllib2.urlopen("%s/%s" % (update_repo, location) ) location = os.path.basename(location) announced_csum = u['checksum'] update_raw = update_file.read() - fp = open("%s/downloads/%s" % (update_cache,location) , "w") + fp = open("%s/download/%s" % (update_cache,location) , "w") fp.write(update_raw) fp.close() - downloaded_csum = checksum("%s/downloads/%s" % (update_cache,location), "sha256") + downloaded_csum = checksum("%s/download/%s" % (update_cache,location), "sha256") # Verify Checksum if downloaded_csum != announced_csum: print "Error: Checksum mismatch" - os.remove("%s/downloads/%s" % (update_cache,location)) + os.remove("%s/download/%s" % (update_cache,location)) else: print "%s already downloaded" % location @@ -145,13 +143,13 @@ def get_new_update_list(location): up = urllib2.urlopen("%s/%s" % (update_repo, location) ) import gzip update_raw = up.read() - fp = open("%s/data/updates.xml.gz" % update_cache , "w") + fp = open("%s/download/metadata/updates.xml.gz" % update_cache , "w") fp.write(update_raw) fp.close() - f = gzip.open("%s/data/updates.xml.gz" % update_cache, 'rb') + f = gzip.open("%s/download/metadata/updates.xml.gz" % update_cache, 'rb') file_content = f.read() f.close() - fp = open("%s/data/updates.xml" % update_cache , "w") + fp = open("%s/download/metadata/updates.xml" % update_cache , "w") fp.write(file_content) fp.close() @@ -186,32 +184,34 @@ def pack(target): print_info('%s.zip' %target) def unpack(location, update_id): - os.mkdir("%s/downloads/%s" %(update_cache, update_id)) - zfile = zipfile.ZipFile("%s/downloads/%s" % (update_cache,location)) + #os.mkdir("%s/download/%s" %(update_cache, update_id)) + zfile = zipfile.ZipFile("%s/download/%s/%s" % (update_cache,update_id, location)) for name in zfile.namelist(): (dirname, filename) = os.path.split(name) - #print "Decompressing " + filename + " on " + dirname - if not os.path.exists("%s/downloads/%s" % (update_cache, dirname)): - os.mkdir("%s/downloads/%s" % (update_cache, dirname)) + print "Decompressing " + filename + " on " + dirname + if not os.path.exists("%s/download/%s/%s" % (update_cache, update_id, dirname)): + os.mkdir("%s/download/%s/%s" % (update_cache, update_id, dirname)) if filename != "": - fd = open("%s/downloads/%s" % (update_cache, name),"w") + fd = open("%s/download/%s/%s" % (update_cache, update_id, name),"w") fd.write(zfile.read(name)) fd.close() + os.rename("%s/download/%s/%s" % (update_cache,update_id, update_id), "%s/download/%s/content" % (update_cache,update_id)) + def prepare_update(update_data, download): u = update_data location = u['location'] update_id = u['id'] # unzip - if os.path.exists("%s/downloads/%s" % (update_cache,location)) and not os.path.exists("%s/downloads/%s" % (update_cache,update_id)): + if os.path.exists("%s/download/%s" % (update_cache,update_id)) and not os.path.exists("%s/download/%s/content" % (update_cache,update_id)): print "Unpacking %s" %location unpack(location, update_id) else: print "Update %s already unpacked" % update_id repodir = "%s/repos.d" %update_cache - repourl = "file://%s/downloads/%s" % (update_cache, update_id) + repourl = "file://%s/download/%s/content" % (update_cache, update_id) if not os.path.exists("%s/%s.repo" % (repourl, update_id)): os.system("zypper --quiet --reposd-dir %s ar --no-gpgcheck --no-keep-packages %s %s" %(repodir, repourl, update_id)) if not download: @@ -222,18 +222,24 @@ def install_update(update_data): location = u['location'] update_id = u['id'] # unzip - if not os.path.exists("%s/downloads/%s" % (update_cache,update_id)): + if not os.path.exists("%s/download/%s/content" % (update_cache,update_id)): prepare_update(update_data, False) repodir = "%s/repos.d" %update_cache - repourl = "file://%s/downloads/%s" % (update_cache, update_id) - if os.path.exists("%s/%s.repo" % (repourl, update_id)): + repourl = "file://%s/download/%s/content" % (update_cache, update_id) + if not os.path.exists("%s/%s.repo" % (repodir, update_id)): os.system("zypper --quiet --reposd-dir %s ar --no-gpgcheck --no-keep-packages %s %s" %(repodir, repourl, update_id)) - os.system("zypper --quiet --non-interactive --reposd-dir %s patch --repo %s " % (repodir, update_id) ) + print "zypper -n --reposd-dir %s patch --with-interactive --repo %s " % (repodir, update_id) + os.system("zypper -n --reposd-dir %s patch --with-interactive --repo %s " % (repodir, update_id) ) if not os.path.exists("%s/installed" % (update_cache)): os.mkdir("%s/installed" % (update_cache)) - shutil.copyfile("%s/downloads/%s/%s" %(update_cache, update_id, update_id), "%s/installed/%s" % (update_cache, update_id)) + shutil.copyfile("%s/download/%s/content/%s" %(update_cache, update_id, update_id), "%s/installed/%s" % (update_cache, update_id)) + print "Finished installing %s." % update_id + for line in fileinput.input("/etc/os-release", inplace=1): + print line.replace(current_version, u["version"]), + for line in fileinput.input("/etc/tizen-release", inplace=1): + print line.replace(current_version, u["version"]), def apply_update(update_data): pass @@ -281,7 +287,11 @@ parser.add_option("-q", "--quiet", if not os.path.exists(update_cache): os.mkdir("%s" % update_cache) - os.mkdir("%s/downloads" % update_cache) +if not os.path.exists("%s/download" % update_cache): + os.mkdir("%s/download" % update_cache) + os.mkdir("%s/download/metadata" % update_cache) + +if not os.path.exists("%s/repos.d" % update_cache): os.mkdir("%s/repos.d" % update_cache) if options.osver: @@ -299,15 +309,15 @@ if options.downloadonly: if options.prepare is not None: probe_updates() updates = parse_updates() - if not updates.has_key(options.install): - print "%s is not available for installation. Abort." %options.install + if not updates.has_key(options.prepare): + print "%s is not available for installation. Abort." %options.prepare sys.exit() - u = updates[options.install] + u = updates[options.prepare] download_update(u) prepare_update(u, False) if options.install is not None: - probe_updates() + #probe_updates() updates = parse_updates() if not updates.has_key(options.install): print "%s is not available for installation. Abort." %options.install diff --git a/system-update.service b/system-update.service new file mode 100644 index 0000000..9ba9d73 --- /dev/null +++ b/system-update.service @@ -0,0 +1,13 @@ +[Unit] +Description=Tizen System Updater +DefaultDependencies=no +OnFailure=reboot.target +Requires=systemd-remount-fs.service connman.service +After=systemd-remount-fs.service connman.service + +[Service] +Type=oneshot +#StandardInput=tty-force +#StandardOutput=tty-force +RemainAfterExit=no +ExecStart=/usr/bin/system-update diff --git a/system-update.sh b/system-update.sh new file mode 100755 index 0000000..7c6bff6 --- /dev/null +++ b/system-update.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +. /etc/os-release +rm /system-update + +# take btrfs snapshot + +# call updater + +for i in `ls /var/cache/updatemanager/install`; do + UPDATE=$(echo $i | sed -e 's/^[0-9]*-//') + mkdir -p /var/cache/zypp/packages/$UPDATE/rpms + /usr/bin/swup -i $UPDATE | tee /var/log/system-update.log + rm /var/cache/updatemanager/install/$i +done + +# check update status +# update failed revert to snapshot + +# reboot +/sbin/reboot diff --git a/system-update.target b/system-update.target new file mode 100644 index 0000000..7f337ad --- /dev/null +++ b/system-update.target @@ -0,0 +1,5 @@ +[Unit] +Description=System Update +Documentation=man:systemd.special(7) +Requires=sysinit.target +After=sysinit.target diff --git a/tools/getpacs/getpacs b/tools/getpacs/getpacs index 03a75cd..e1448f3 100755 --- a/tools/getpacs/getpacs +++ b/tools/getpacs/getpacs @@ -2,14 +2,18 @@ BUILD_OLD=$1 BUILD_NEW=$2 +if [ -z "$BUILD_OLD" -o -z "$BUILD_NEW" ]; then + echo "You need to provide old and new snapshot IDs" + exit 1 +fi USER="nashif" PASS="" WGET="/usr/bin/wget -q --timestamping " DAILY="/pc/releases/daily/trunk" WEEKLY="/pc/releases/weekly/trunk" -SNAPSHOTS="/${RELEASE_TYPE}" +SNAPSHOTS="/snapshots/trunk/pc/" -RELEASE_TYPE=$DAILY +RELEASE_TYPE=$SNAPSHOTS BASE_DIR=$PWD wget https://$USER:$PASS@download.tz.otcshare.org/${RELEASE_TYPE}/${BUILD_OLD}/images/gnome/gnome-${BUILD_OLD}.packages -O old @@ -49,4 +53,4 @@ done IFS=$OLD_IFS -rm $BASE_DIR/new $BASE_DIR/old $BASE_DIR/p0 $BASE_DIR/{p1,new_packages} +#rm $BASE_DIR/new $BASE_DIR/old $BASE_DIR/p0 $BASE_DIR/{p1,new_packages} diff --git a/tools/updateinfo/markup.py b/tools/updateinfo/markup.py new file mode 100644 index 0000000..d5e9a6a --- /dev/null +++ b/tools/updateinfo/markup.py @@ -0,0 +1,527 @@ +# This code is in the public domain, it comes +# with absolutely no warranty and you can do +# absolutely whatever you want with it. + +__date__ = '1 October 2012' +__version__ = '1.9' +__doc__= """ +This is markup.py - a Python module that attempts to +make it easier to generate HTML/XML from a Python program +in an intuitive, lightweight, customizable and pythonic way. + +The code is in the public domain. + +Version: %s as of %s. + +Documentation and further info is at http://markup.sourceforge.net/ + +Please send bug reports, feature requests, enhancement +ideas or questions to nogradi at gmail dot com. + +Installation: drop markup.py somewhere into your Python path. +""" % ( __version__, __date__ ) + +try: + basestring + import string +except: + # python 3 + basestring = str + string = str + +# tags which are reserved python keywords will be referred +# to by a leading underscore otherwise we end up with a syntax error +import keyword + +class element: + """This class handles the addition of a new element.""" + + def __init__( self, tag, case='lower', parent=None ): + self.parent = parent + + if case == 'upper': + self.tag = tag.upper( ) + elif case == 'lower': + self.tag = tag.lower( ) + elif case =='given': + self.tag = tag + else: + self.tag = tag + + def __call__( self, *args, **kwargs ): + if len( args ) > 1: + raise ArgumentError( self.tag ) + + # if class_ was defined in parent it should be added to every element + if self.parent is not None and self.parent.class_ is not None: + if 'class_' not in kwargs: + kwargs['class_'] = self.parent.class_ + + if self.parent is None and len( args ) == 1: + x = [ self.render( self.tag, False, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ] + return '\n'.join( x ) + elif self.parent is None and len( args ) == 0: + x = [ self.render( self.tag, True, myarg, mydict ) for myarg, mydict in _argsdicts( args, kwargs ) ] + return '\n'.join( x ) + + if self.tag in self.parent.twotags: + for myarg, mydict in _argsdicts( args, kwargs ): + self.render( self.tag, False, myarg, mydict ) + elif self.tag in self.parent.onetags: + if len( args ) == 0: + for myarg, mydict in _argsdicts( args, kwargs ): + self.render( self.tag, True, myarg, mydict ) # here myarg is always None, because len( args ) = 0 + else: + raise ClosingError( self.tag ) + elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags: + raise DeprecationError( self.tag ) + else: + raise InvalidElementError( self.tag, self.parent.mode ) + + def render( self, tag, single, between, kwargs ): + """Append the actual tags to content.""" + + out = "<%s" % tag + for key, value in list( kwargs.items( ) ): + if value is not None: # when value is None that means stuff like <... checked> + key = key.strip('_') # strip this so class_ will mean class, etc. + if key == 'http_equiv': # special cases, maybe change _ to - overall? + key = 'http-equiv' + elif key == 'accept_charset': + key = 'accept-charset' + out = "%s %s=\"%s\"" % ( out, key, escape( value ) ) + else: + out = "%s %s" % ( out, key ) + if between is not None: + out = "%s>%s" % ( out, between, tag ) + else: + if single: + out = "%s />" % out + else: + out = "%s>" % out + if self.parent is not None: + self.parent.content.append( out ) + else: + return out + + def close( self ): + """Append a closing tag unless element has only opening tag.""" + + if self.tag in self.parent.twotags: + self.parent.content.append( "" % self.tag ) + elif self.tag in self.parent.onetags: + raise ClosingError( self.tag ) + elif self.parent.mode == 'strict_html' and self.tag in self.parent.deptags: + raise DeprecationError( self.tag ) + + def open( self, **kwargs ): + """Append an opening tag.""" + + if self.tag in self.parent.twotags or self.tag in self.parent.onetags: + self.render( self.tag, False, None, kwargs ) + elif self.mode == 'strict_html' and self.tag in self.parent.deptags: + raise DeprecationError( self.tag ) + +class page: + """This is our main class representing a document. Elements are added + as attributes of an instance of this class.""" + + def __init__( self, mode='strict_html', case='lower', onetags=None, twotags=None, separator='\n', class_=None ): + """Stuff that effects the whole document. + + mode -- 'strict_html' for HTML 4.01 (default) + 'html' alias for 'strict_html' + 'loose_html' to allow some deprecated elements + 'xml' to allow arbitrary elements + + case -- 'lower' element names will be printed in lower case (default) + 'upper' they will be printed in upper case + 'given' element names will be printed as they are given + + onetags -- list or tuple of valid elements with opening tags only + twotags -- list or tuple of valid elements with both opening and closing tags + these two keyword arguments may be used to select + the set of valid elements in 'xml' mode + invalid elements will raise appropriate exceptions + + separator -- string to place between added elements, defaults to newline + + class_ -- a class that will be added to every element if defined""" + + valid_onetags = [ "AREA", "BASE", "BR", "COL", "FRAME", "HR", "IMG", "INPUT", "LINK", "META", "PARAM" ] + valid_twotags = [ "A", "ABBR", "ACRONYM", "ADDRESS", "B", "BDO", "BIG", "BLOCKQUOTE", "BODY", "BUTTON", + "CAPTION", "CITE", "CODE", "COLGROUP", "DD", "DEL", "DFN", "DIV", "DL", "DT", "EM", "FIELDSET", + "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEAD", "HTML", "I", "IFRAME", "INS", + "KBD", "LABEL", "LEGEND", "LI", "MAP", "NOFRAMES", "NOSCRIPT", "OBJECT", "OL", "OPTGROUP", + "OPTION", "P", "PRE", "Q", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "STYLE", + "SUB", "SUP", "TABLE", "TBODY", "TD", "TEXTAREA", "TFOOT", "TH", "THEAD", "TITLE", "TR", + "TT", "UL", "VAR" ] + deprecated_onetags = [ "BASEFONT", "ISINDEX" ] + deprecated_twotags = [ "APPLET", "CENTER", "DIR", "FONT", "MENU", "S", "STRIKE", "U" ] + + self.header = [ ] + self.content = [ ] + self.footer = [ ] + self.case = case + self.separator = separator + + # init( ) sets it to True so we know that has to be printed at the end + self._full = False + self.class_= class_ + + if mode == 'strict_html' or mode == 'html': + self.onetags = valid_onetags + self.onetags += list( map( string.lower, self.onetags ) ) + self.twotags = valid_twotags + self.twotags += list( map( string.lower, self.twotags ) ) + self.deptags = deprecated_onetags + deprecated_twotags + self.deptags += list( map( string.lower, self.deptags ) ) + self.mode = 'strict_html' + elif mode == 'loose_html': + self.onetags = valid_onetags + deprecated_onetags + self.onetags += list( map( string.lower, self.onetags ) ) + self.twotags = valid_twotags + deprecated_twotags + self.twotags += list( map( string.lower, self.twotags ) ) + self.mode = mode + elif mode == 'xml': + if onetags and twotags: + self.onetags = onetags + self.twotags = twotags + elif ( onetags and not twotags ) or ( twotags and not onetags ): + raise CustomizationError( ) + else: + self.onetags = russell( ) + self.twotags = russell( ) + self.mode = mode + else: + raise ModeError( mode ) + + def __getattr__( self, attr ): + + # tags should start with double underscore + if attr.startswith("__") and attr.endswith("__"): + raise AttributeError( attr ) + # tag with single underscore should be a reserved keyword + if attr.startswith( '_' ): + attr = attr.lstrip( '_' ) + if attr not in keyword.kwlist: + raise AttributeError( attr ) + + return element( attr, case=self.case, parent=self ) + + def __str__( self ): + + if self._full and ( self.mode == 'strict_html' or self.mode == 'loose_html' ): + end = [ '', '' ] + else: + end = [ ] + + return self.separator.join( self.header + self.content + self.footer + end ) + + def __call__( self, escape=False ): + """Return the document as a string. + + escape -- False print normally + True replace < and > by < and > + the default escape sequences in most browsers""" + + if escape: + return _escape( self.__str__( ) ) + else: + return self.__str__( ) + + def add( self, text ): + """This is an alias to addcontent.""" + self.addcontent( text ) + + def addfooter( self, text ): + """Add some text to the bottom of the document""" + self.footer.append( text ) + + def addheader( self, text ): + """Add some text to the top of the document""" + self.header.append( text ) + + def addcontent( self, text ): + """Add some text to the main part of the document""" + self.content.append( text ) + + + def init( self, lang='en', css=None, metainfo=None, title=None, header=None, + footer=None, charset=None, encoding=None, doctype=None, bodyattrs=None, script=None, base=None ): + """This method is used for complete documents with appropriate + doctype, encoding, title, etc information. For an HTML/XML snippet + omit this method. + + lang -- language, usually a two character string, will appear + as in html mode (ignored in xml mode) + + css -- Cascading Style Sheet filename as a string or a list of + strings for multiple css files (ignored in xml mode) + + metainfo -- a dictionary in the form { 'name':'content' } to be inserted + into meta element(s) as + (ignored in xml mode) + + base -- set the tag in + + bodyattrs --a dictionary in the form { 'key':'value', ... } which will be added + as attributes of the element as + (ignored in xml mode) + + script -- dictionary containing src:type pairs, + or a list of [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for all + + title -- the title of the document as a string to be inserted into + a title element as my title (ignored in xml mode) + + header -- some text to be inserted right after the element + (ignored in xml mode) + + footer -- some text to be inserted right before the element + (ignored in xml mode) + + charset -- a string defining the character set, will be inserted into a + + element (ignored in xml mode) + + encoding -- a string defining the encoding, will be put into to first line of + the document as in + xml mode (ignored in html mode) + + doctype -- the document type string, defaults to + + in html mode (ignored in xml mode)""" + + self._full = True + + if self.mode == 'strict_html' or self.mode == 'loose_html': + if doctype is None: + doctype = "" + self.header.append( doctype ) + self.html( lang=lang ) + self.head( ) + if charset is not None: + self.meta( http_equiv='Content-Type', content="text/html; charset=%s" % charset ) + if metainfo is not None: + self.metainfo( metainfo ) + if css is not None: + self.css( css ) + if title is not None: + self.title( title ) + if script is not None: + self.scripts( script ) + if base is not None: + self.base( href='%s' % base ) + self.head.close() + if bodyattrs is not None: + self.body( **bodyattrs ) + else: + self.body( ) + if header is not None: + self.content.append( header ) + if footer is not None: + self.footer.append( footer ) + + elif self.mode == 'xml': + if doctype is None: + if encoding is not None: + doctype = "" % encoding + else: + doctype = "" + self.header.append( doctype ) + + def css( self, filelist ): + """This convenience function is only useful for html. + It adds css stylesheet(s) to the document via the element.""" + + if isinstance( filelist, basestring ): + self.link( href=filelist, rel='stylesheet', type='text/css', media='all' ) + else: + for file in filelist: + self.link( href=file, rel='stylesheet', type='text/css', media='all' ) + + def metainfo( self, mydict ): + """This convenience function is only useful for html. + It adds meta information via the element, the argument is + a dictionary of the form { 'name':'content' }.""" + + if isinstance( mydict, dict ): + for name, content in list( mydict.items( ) ): + self.meta( name=name, content=content ) + else: + raise TypeError( "Metainfo should be called with a dictionary argument of name:content pairs." ) + + def scripts( self, mydict ): + """Only useful in html, mydict is dictionary of src:type pairs or a list + of script sources [ 'src1', 'src2', ... ] in which case 'javascript' is assumed for type. + Will be rendered as """ + + if isinstance( mydict, dict ): + for src, type in list( mydict.items( ) ): + self.script( '', src=src, type='text/%s' % type ) + else: + try: + for src in mydict: + self.script( '', src=src, type='text/javascript' ) + except: + raise TypeError( "Script should be given a dictionary of src:type pairs or a list of javascript src's." ) + + +class _oneliner: + """An instance of oneliner returns a string corresponding to one element. + This class can be used to write 'oneliners' that return a string + immediately so there is no need to instantiate the page class.""" + + def __init__( self, case='lower' ): + self.case = case + + def __getattr__( self, attr ): + + # tags should start with double underscore + if attr.startswith("__") and attr.endswith("__"): + raise AttributeError( attr ) + # tag with single underscore should be a reserved keyword + if attr.startswith( '_' ): + attr = attr.lstrip( '_' ) + if attr not in keyword.kwlist: + raise AttributeError( attr ) + + return element( attr, case=self.case, parent=None ) + +oneliner = _oneliner( case='lower' ) +upper_oneliner = _oneliner( case='upper' ) +given_oneliner = _oneliner( case='given' ) + +def _argsdicts( args, mydict ): + """A utility generator that pads argument list and dictionary values, will only be called with len( args ) = 0, 1.""" + + if len( args ) == 0: + args = None, + elif len( args ) == 1: + args = _totuple( args[0] ) + else: + raise Exception( "We should have never gotten here." ) + + mykeys = list( mydict.keys( ) ) + myvalues = list( map( _totuple, list( mydict.values( ) ) ) ) + + maxlength = max( list( map( len, [ args ] + myvalues ) ) ) + + for i in range( maxlength ): + thisdict = { } + for key, value in zip( mykeys, myvalues ): + try: + thisdict[ key ] = value[i] + except IndexError: + thisdict[ key ] = value[-1] + try: + thisarg = args[i] + except IndexError: + thisarg = args[-1] + + yield thisarg, thisdict + +def _totuple( x ): + """Utility stuff to convert string, int, long, float, None or anything to a usable tuple.""" + + if isinstance( x, basestring ): + out = x, + elif isinstance( x, ( int, long, float ) ): + out = str( x ), + elif x is None: + out = None, + else: + out = tuple( x ) + + return out + +def escape( text, newline=False ): + """Escape special html characters.""" + + if isinstance( text, basestring ): + if '&' in text: + text = text.replace( '&', '&' ) + if '>' in text: + text = text.replace( '>', '>' ) + if '<' in text: + text = text.replace( '<', '<' ) + if '\"' in text: + text = text.replace( '\"', '"' ) + if '\'' in text: + text = text.replace( '\'', '"' ) + if newline: + if '\n' in text: + text = text.replace( '\n', '
' ) + + return text + +_escape = escape + +def unescape( text ): + """Inverse of escape.""" + + if isinstance( text, basestring ): + if '&' in text: + text = text.replace( '&', '&' ) + if '>' in text: + text = text.replace( '>', '>' ) + if '<' in text: + text = text.replace( '<', '<' ) + if '"' in text: + text = text.replace( '"', '\"' ) + + return text + +class dummy: + """A dummy class for attaching attributes.""" + pass + +doctype = dummy( ) +doctype.frameset = """""" +doctype.strict = """""" +doctype.loose = """""" + +class russell: + """A dummy class that contains anything.""" + + def __contains__( self, item ): + return True + + +class MarkupError( Exception ): + """All our exceptions subclass this.""" + def __str__( self ): + return self.message + +class ClosingError( MarkupError ): + def __init__( self, tag ): + self.message = "The element '%s' does not accept non-keyword arguments (has no closing tag)." % tag + +class OpeningError( MarkupError ): + def __init__( self, tag ): + self.message = "The element '%s' can not be opened." % tag + +class ArgumentError( MarkupError ): + def __init__( self, tag ): + self.message = "The element '%s' was called with more than one non-keyword argument." % tag + +class InvalidElementError( MarkupError ): + def __init__( self, tag, mode ): + self.message = "The element '%s' is not valid for your mode '%s'." % ( tag, mode ) + +class DeprecationError( MarkupError ): + def __init__( self, tag ): + self.message = "The element '%s' is deprecated, instantiate markup.page with mode='loose_html' to allow it." % tag + +class ModeError( MarkupError ): + def __init__( self, mode ): + self.message = "Mode '%s' is invalid, possible values: strict_html, html (alias for strict_html), loose_html, xml." % mode + +class CustomizationError( MarkupError ): + def __init__( self ): + self.message = "If you customize the allowed elements, you must define both types 'onetags' and 'twotags'." + +if __name__ == '__main__': + import sys + sys.stdout.write( __doc__ ) diff --git a/tools/updateinfo/updateinfo.py b/tools/updateinfo/updateinfo.py index 5a83e53..efc073e 100755 --- a/tools/updateinfo/updateinfo.py +++ b/tools/updateinfo/updateinfo.py @@ -38,6 +38,122 @@ class Updates: if patch and updates: self.add_update(patch, updates) + def _desc_to_html(self, page, desc): + in_ul = False + for line in desc.splitlines(): + if line.startswith('- ') or line.startswith('* '): + if not in_ul: + page.ul() + in_ul = True + page.li(line[2:]) + else: + if in_ul: + page.ul.close() + in_ul = False + page.p(line) + if in_ul: + page.ul.close() + + def get_title(self, t, is_html = False): + if is_html: + import markup + page = markup.page() + page.init( title = t, style = CSS_STRIKE) + page.h1(t) + return str(page).rstrip('').rstrip().rstrip('') + else: + return t + + def get_summary_info(self, sum, is_html = False): + if is_html: + import markup + page = markup.page() + page.h2(sum['Title']) + self._desc_to_html(page, sum['Description']) + + return str(page) + + else: + return '%s\n%s' %(sum['Title'], sum['Description']) + + def get_patch_info(self, update, is_html = False): + if is_html: + import markup + page = markup.page() + #page.h3(update['Title']) + self._desc_to_html(page, update['Description']) + + if update.has_key("Bugs"): + page.p.open() + page.add('Resolved Bugs: ') + firstone = True + for bug in update['Bugs']: + if firstone: + firstone = False + else: + page.add(', ') + page.a(bug, href='http://bugs.tizen.org/show_bug.cgi?id=%s' %bug, class_="strike_link") + page.p.close() + + if update.has_key("CVEs"): + page.p.open() + page.add('Resolved CVE Issues: ') + firstone = True + for cve in update['CVEs']: + if firstone: + firstone = False + else: + page.add(', ') + page.a(cve, href='http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s\n' % cve, class_="strike_link") + page.p.close() + + return str(page) + else: + INFO = """ + + Patch <%s>: + Title: %s + Type: %s + Project: %s + Repository: %s + Release: %s + Status: %s + Packages: %s + Description: + %s + """ % (update['ID'], + update['Title'], + update['Type'], + update['Project'], + update['Repository'], + update['Release'], + update['Status'], + ", ".join(update['Packages']), + '\n '.join(update['Description'].splitlines())) + + if update.has_key("CVEs"): + INFO += " CVEs:\n" + cve_info = '' + for cve in update['CVEs']: + cve_info += ' http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s\n' %cve + INFO += cve_info + + if update.has_key("Bugs"): + INFO += " Bugs:\n" + bug_info = '' + for bug in update['Bugs']: + bug_info += ' http://bugs.tizen.org/show_bug.cgi?id=%s\n' %bug + INFO += bug_info + + if update.has_key('Reboot') and update['Reboot']: + INFO += ' NOTE: reboot needed\n' + if update.has_key('Relogin') and update['Relogin']: + INFO += ' NOTE: relogin needed\n' + if update.has_key('Restart') and update['Restart']: + INFO += ' NOTE: restart needed\n' + + return INFO + def _new_doc(self): print "Creating new updates.xml file..." doc = minidom.Document() @@ -64,8 +180,7 @@ class Updates: else: self._new_doc() self.next = 0 - - def _insert(self, parent, name, attrs={}, text=None): + def _insert(self, parent, name, attrs={}, text=None, data=None): """ Helper function to trivialize inserting an element into the doc """ child = self.doc.createElement(name) for item in attrs.items(): @@ -73,6 +188,9 @@ class Updates: if text: txtnode = self.doc.createTextNode(unicode(text)) child.appendChild(txtnode) + if data: + txtnode = self.doc.createCDATASection(unicode(data)) + child.appendChild(txtnode) parent.appendChild(child) return child @@ -99,7 +217,15 @@ class Updates: issued_time = times[0] self._insert(root, 'issued', attrs={ 'date' : issued_time }) - self._insert(root, 'description', text=update['Description']) + if update.has_key('Reboot') and update['Reboot']: + self._insert(root, 'reboot_required', text='True') + if update.has_key('Relogin') and update['Relogin']: + self._insert(root, 'relogin_required', text='True') + if update.has_key('Restart') and update['Restart']: + self._insert(root, 'restart_required', text='True') + + html = self.get_patch_info(update, True) + self._insert(root, 'description', data=html) class UpdateInfo: def __init__(self, patch = None, updates = None, cache = None): @@ -144,7 +270,7 @@ class UpdateInfo: self._new_doc() self.next = 0 - def _insert(self, parent, name, attrs={}, text=None): + def _insert(self, parent, name, attrs={}, text=None, data=None): """ Helper function to trivialize inserting an element into the doc """ child = self.doc.createElement(name) for item in attrs.items(): @@ -152,6 +278,9 @@ class UpdateInfo: if text: txtnode = self.doc.createTextNode(unicode(text)) child.appendChild(txtnode) + if data: + txtnode = self.doc.createCDATASection(unicode(data)) + child.appendChild(txtnode) parent.appendChild(child) return child @@ -190,13 +319,14 @@ class UpdateInfo: 'id' : cve }) - for bug in update['Bugs']: - self._insert(refs, 'reference', attrs={ - 'type' : 'bugzilla', - 'href' : 'http://bugs.tizen.org/show_bug.cgi?id=%s' %bug, - 'id' : bug, - 'title': 'Bug number %s' %bug - }) + if update.has_key("Bugs"): + for bug in update['Bugs']: + self._insert(refs, 'reference', attrs={ + 'type' : 'bugzilla', + 'href' : 'http://bugs.tizen.org/show_bug.cgi?id=%s' %bug, + 'id' : bug, + 'title': 'Bug number %s' %bug + }) root.appendChild(refs) ## Errata description @@ -209,7 +339,7 @@ class UpdateInfo: #self._insert(collection, 'name', text=update.release.long_name) for u in updates: - filename = u['binary'] + filename = "rpms/%s" % (os.path.basename(u['binary'])) if u['header'][rpm.RPMTAG_SOURCEPACKAGE] or 'debuginfo' in u['binary']: continue pkg = self._insert(collection, 'package', attrs={