-rw-r--r-- | noncore/settings/packagemanager/ChangeLog | 7 | ||||
-rw-r--r-- | noncore/settings/packagemanager/oipkg.cpp | 12 | ||||
-rw-r--r-- | noncore/settings/packagemanager/opackagemanager.cpp | 7 |
3 files changed, 16 insertions, 10 deletions
diff --git a/noncore/settings/packagemanager/ChangeLog b/noncore/settings/packagemanager/ChangeLog index 608cd98..38756b1 100644 --- a/noncore/settings/packagemanager/ChangeLog +++ b/noncore/settings/packagemanager/ChangeLog @@ -1,60 +1,65 @@ +2005-02-16 Dan Williams <drw@handhelds.org> + + * Fixed stupid bug where last package in status file was not shown as installed when it should be + * Removed printf's + 2005-01-02 Dan Williams <drw@handhelds.org> * Released version 0.6.1 * Implemented native package linking code to remove need for ipkg-link * Implement package in OIpkg (removed from InstallDlg) as this is ipkg specific * Many small code tweaks - + 2004-12-21 Dan Williams <drw@handhelds.org> * Released version 0.6.0 * Added support for Ipkg 'src/gz' feeds * Improve server and destination tabs UI's in configuration dialog * Fix app linking to link all dependent packages as well as selected packages * Hide 'Retrive File List' button once list is retrieved in Package Info dialog 2004-11-18 Dan Williams <drw@handhelds.org> * Released version 0.5.0 * All v1.0 functionality implemented * Implemented installation of local packages * Implemented linking of non-root apps (using ipkg-link) * Many UI tweaks for installation and filter dialogs 2004-04-21 Dan Williams <drw@handhelds.org> * Released version 0.4.0 * Added saving of ipkg configuration information * Re-initialize ipkg when configuration information changes * Added QWhatsThis for all UI controls * Remove Location from OConfItem as it is not used/needed * Re-ordered includes to follow Opie standards 2004-02-13 Dan Williams <drw@handhelds.org> * Released version 0.3.0 * Fix handling of filtering options in View menu * Do proper version string comparison * Fix string alignment code in PromptDlg to eliminate QT warning messages 2004-02-12 Dan Williams <drw@handhelds.org> * Package information dialog implemented * What's This app icon enabled * Changed all QDialog::exec() occurences to QPEApplication::execDialog() 2004-01-23 Dan Williams <drw@handhelds.org> * Added package download functionality * Have Opie update links after install/removal so that apps will display properly in Launcher 2004-01-20 Dan Williams <drw@handhelds.org> * Released version 0.2.0 * Converted to use libipkg in place of spawning ipkg process 2004-01-13 Dan Williams <drw@handhelds.org> * Released version 0.1.0 * Initial check-in of new package management client to eventually replace AQPkg diff --git a/noncore/settings/packagemanager/oipkg.cpp b/noncore/settings/packagemanager/oipkg.cpp index f2d7e39..e7e292e 100644 --- a/noncore/settings/packagemanager/oipkg.cpp +++ b/noncore/settings/packagemanager/oipkg.cpp @@ -238,528 +238,532 @@ OPackageList *OIpkg::availablePackages( const QString &server ) return NULL; QTextStream t( &f ); // Process all information in package list file OPackage *package = NULL; QString line = t.readLine(); while ( !t.eof() ) { // Determine key/value pair int pos = line.find( ':', 0 ); QString key; if ( pos > -1 ) key = line.mid( 0, pos ); else key = QString::null; QString value = line.mid( pos+2, line.length()-pos ); // Allocate new package and insert into list if ( package == NULL && !key.isEmpty() ) { package = new OPackage( value ); package->setSource( server ); pl->append( package ); } // Update package data if ( key == "Package" ) package->setName( value ); else if ( key == "Version" ) package->setVersion( value ); else if ( key == "Section" ) package->setCategory( value ); //DataManager::setAvailableCategories( value ); else if ( key.isEmpty() && value.isEmpty() ) package = NULL; // Skip past all description lines if ( key == "Description" ) { line = t.readLine(); while ( !line.isEmpty() && line.find( ':', 0 ) == -1 && !t.eof() ) line = t.readLine(); } else line = t.readLine(); } f.close(); return pl; } OPackageList *OIpkg::installedPackages( const QString &destName, const QString &destPath ) { // Load Ipkg configuration info if not already cached if ( !m_confInfo ) loadConfiguration(); // Build new server list (caller is responsible for deleting) OPackageList *pl = new OPackageList; // Open status file QString path = destPath; if ( path.right( 1 ) != "/" ) path.append( "/" ); path.append( IPKG_STATUS_PATH ); QFile f( path ); if ( !f.open( IO_ReadOnly ) ) return NULL; QTextStream t( &f ); // Process all information in status file bool newPackage = false; QString line = t.readLine(); QString name; QString version; QString status; while ( !t.eof() ) { // Determine key/value pair int pos = line.find( ':', 0 ); QString key; if ( pos > -1 ) key = line.mid( 0, pos ); else key = QString::null; QString value = line.mid( pos+2, line.length()-pos ); // Allocate new package and insert into list if ( newPackage && !key.isEmpty() ) { // Add to list only if it has a valid name and is installed if ( !name.isNull() && status.contains( " installed" ) ) { pl->append( new OPackage( name, QString::null, version, QString::null, destName ) ); name = QString::null; version = QString::null; status = QString::null; newPackage = false; } } // Update package data if ( key == "Package" ) name = value; else if ( key == "Version" ) version = value; else if ( key == "Status" ) status = value; else if ( key.isEmpty() && value.isEmpty() ) newPackage = true; // Skip past all description lines if ( key == "Description" ) { line = t.readLine(); while ( !line.isEmpty() && line.find( ':', 0 ) == -1 && !t.eof() ) line = t.readLine(); } else line = t.readLine(); } f.close(); + // Make sure to add to list last entry + if ( !name.isNull() && status.contains( " installed" ) ) + pl->append( new OPackage( name, QString::null, version, QString::null, destName ) ); + return pl; } OConfItem *OIpkg::findConfItem( OConfItem::Type type, const QString &name ) { // Find configuration item in list OConfItemListIterator configIt( *m_confInfo ); OConfItem *config = 0l; for ( ; configIt.current(); ++configIt ) { config = configIt.current(); if ( config->type() == type && config->name() == name ) break; } if ( config && config->type() == type && config->name() == name ) return config; return 0l; } bool OIpkg::executeCommand( OPackage::Command command, const QStringList ¶meters, const QString &destination, const QObject *receiver, const char *slotOutput, bool rawOutput ) { if ( command == OPackage::NotDefined ) return false; // Set ipkg run-time options/arguments m_ipkgArgs.force_depends = ( m_ipkgExecOptions & FORCE_DEPENDS ); m_ipkgArgs.force_reinstall = ( m_ipkgExecOptions & FORCE_REINSTALL ); // TODO m_ipkgArgs.force_remove = ( m_ipkgExecOptions & FORCE_REMOVE ); m_ipkgArgs.force_overwrite = ( m_ipkgExecOptions & FORCE_OVERWRITE ); m_ipkgArgs.verbosity = m_ipkgExecVerbosity; if ( m_ipkgArgs.dest ) free( m_ipkgArgs.dest ); if ( !destination.isNull() ) { int len = destination.length() + 1; m_ipkgArgs.dest = (char *)malloc( len ); strncpy( m_ipkgArgs.dest, destination, destination.length() ); m_ipkgArgs.dest[ len - 1 ] = '\0'; } else m_ipkgArgs.dest = 0l; // Connect output signal to widget if ( !rawOutput ) { // TODO - connect to local slot and parse output before emitting signalIpkgMessage } switch( command ) { case OPackage::Update : { connect( this, SIGNAL(signalIpkgMessage(const QString &)), receiver, slotOutput ); ipkg_lists_update( &m_ipkgArgs ); }; break; case OPackage::Upgrade : { connect( this, SIGNAL(signalIpkgMessage(const QString &)), receiver, slotOutput ); ipkg_packages_upgrade( &m_ipkgArgs ); // Re-link non-root destinations to make sure everything is in sync OConfItemList *destList = destinations(); OConfItemListIterator it( *destList ); for ( ; it.current(); ++it ) { OConfItem *dest = it.current(); if ( dest->name() != "root" ) linkPackageDir( dest->name() ); } delete destList; }; break; case OPackage::Install : { connect( this, SIGNAL(signalIpkgMessage(const QString &)), receiver, slotOutput ); for ( QStringList::ConstIterator it = parameters.begin(); it != parameters.end(); ++it ) { ipkg_packages_install( &m_ipkgArgs, (*it) ); } if ( destination != "root" ) linkPackageDir( destination ); }; break; case OPackage::Remove : { connect( this, SIGNAL(signalIpkgMessage(const QString &)), receiver, slotOutput ); // Get list of destinations for unlinking of packages not installed to root OConfItemList *destList = destinations(); - + for ( QStringList::ConstIterator it = parameters.begin(); it != parameters.end(); ++it ) { unlinkPackage( (*it), destList ); ipkg_packages_remove( &m_ipkgArgs, (*it), true ); } delete destList; }; break; case OPackage::Download : { connect( this, SIGNAL(signalIpkgMessage(const QString &)), receiver, slotOutput ); for ( QStringList::ConstIterator it = parameters.begin(); it != parameters.end(); ++it ) { ipkg_packages_download( &m_ipkgArgs, (*it) ); } }; break; case OPackage::Info : { connect( this, SIGNAL(signalIpkgStatus(const QString &)), receiver, slotOutput ); ipkg_packages_info( &m_ipkgArgs, (*parameters.begin()), &fIpkgStatus, 0l ); }; break; case OPackage::Files : { connect( this, SIGNAL(signalIpkgList(const QString &)), receiver, slotOutput ); ipkg_package_files( &m_ipkgArgs, (*parameters.begin()), &fIpkgFiles, 0l ); }; break; default : break; }; return true; } void OIpkg::ipkgMessage( char *msg ) { emit signalIpkgMessage( msg ); } void OIpkg::ipkgStatus( char *status ) { emit signalIpkgStatus( status ); } void OIpkg::ipkgList( char *filelist ) { emit signalIpkgList( filelist ); } void OIpkg::loadConfiguration() { if ( m_confInfo ) delete m_confInfo; // Load configuration item list m_confInfo = new OConfItemList(); QStringList confFiles; QDir confDir( IPKG_CONF_DIR ); if ( confDir.exists() ) { confDir.setNameFilter( "*.conf" ); confDir.setFilter( QDir::Files ); confFiles = confDir.entryList( "*.conf", QDir::Files ); } confFiles << IPKG_CONF; QStringList::Iterator lastFile = confFiles.end(); for ( QStringList::Iterator it = confFiles.begin(); it != lastFile; ++it ) { // Create absolute file path if necessary QString absFile = (*it); if ( !absFile.startsWith( "/" ) ) absFile.prepend( QString( IPKG_CONF_DIR ) + "/" ); // Read in file QFile f( absFile ); if ( f.open( IO_ReadOnly ) ) { QTextStream s( &f ); while ( !s.eof() ) { QString line = s.readLine().simplifyWhiteSpace(); // Parse line and save info to the conf options list if ( !line.isEmpty() ) { if ( !line.startsWith( "#" ) || line.startsWith( "#src" ) || line.startsWith( "#dest" ) || line.startsWith( "#arch" ) || line.startsWith( "#option" ) ) { int pos = line.find( ' ', 1 ); // Type QString typeStr = line.left( pos ); OConfItem::Type type; QString features; if ( typeStr == "src" || typeStr == "#src" ) type = OConfItem::Source; else if ( typeStr == "src/gz" || typeStr == "#src/gz" ) { type = OConfItem::Source; features = "Compressed"; } else if ( typeStr == "dest" || typeStr == "#dest" ) type = OConfItem::Destination; else if ( typeStr == "option" || typeStr == "#option" ) type = OConfItem::Option; else if ( typeStr == "arch" || typeStr == "#arch" ) type = OConfItem::Arch; else type = OConfItem::NotDefined; ++pos; int endpos = line.find( ' ', pos ); // Name QString name = line.mid( pos, endpos - pos ); // Value QString value = ""; if ( endpos > -1 ) value = line.right( line.length() - endpos - 1 ); // Active bool active = !line.startsWith( "#" ); // Add to list m_confInfo->append( new OConfItem( type, name, value, features, active ) ); } } } f.close(); } } // Load Ipkg execution options from application configuration file if ( m_config ) { m_config->setGroup( "Ipkg" ); m_ipkgExecOptions = m_config->readNumEntry( "ExecOptions", m_ipkgExecOptions ); m_ipkgExecVerbosity = m_config->readNumEntry( "Verbosity", m_ipkgExecVerbosity ); } } OConfItemList *OIpkg::filterConfItems( OConfItem::Type typefilter ) { // Load Ipkg configuration info if not already cached if ( !m_confInfo ) loadConfiguration(); // Build new server list (caller is responsible for deleting) OConfItemList *sl = new OConfItemList; // If typefilter is empty, retrieve all items bool retrieveAll = ( typefilter == OConfItem::NotDefined ); // Parse configuration info for servers OConfItemListIterator it( *m_confInfo ); for ( ; it.current(); ++it ) { OConfItem *item = it.current(); if ( retrieveAll || item->type() == typefilter ) { sl->append( item ); } } return sl; } const QString &OIpkg::rootPath() { if ( m_rootPath.isEmpty() ) { OConfItem *rootDest = findConfItem( OConfItem::Destination, "root" ); rootDest ? m_rootPath = rootDest->value() : m_rootPath = '/'; if ( m_rootPath.right( 1 ) == '/' ) m_rootPath.truncate( m_rootPath.length() - 1 ); } return m_rootPath; } void OIpkg::linkPackageDir( const QString &dest ) { if ( !dest.isNull() ) { OConfItem *destConfItem = findConfItem( OConfItem::Destination, dest ); - + emit signalIpkgMessage( tr( "Linking packages installed in: %1" ).arg( dest ) ); // Set package destination directory QString destDir = destConfItem->value(); QString destInfoDir = destDir; if ( destInfoDir.right( 1 ) != '/' ) destInfoDir.append( '/' ); destInfoDir.append( IPKG_INFO_PATH ); // Get list of installed packages in destination QDir packageDir( destInfoDir ); QStringList packageFiles; if ( packageDir.exists() ) { packageDir.setNameFilter( "*.list" ); packageDir.setFilter( QDir::Files ); packageFiles = packageDir.entryList( "*.list", QDir::Files ); } // Link all files for every package installed in desination QStringList::Iterator lastFile = packageFiles.end(); for ( QStringList::Iterator it = packageFiles.begin(); it != lastFile; ++it ) { //emit signalIpkgMessage( QString( "Processing: %1/%2" ).arg( destInfoDir ).arg (*it) ); QString packageFileName = destInfoDir; packageFileName.append( '/' ); packageFileName.append( (*it) ); QFile packageFile( packageFileName ); if ( packageFile.open( IO_ReadOnly ) ) { QTextStream t( &packageFile ); QString linkFile; while ( !t.eof() ) { // Get the name of the file to link and build the sym link filename linkFile = t.readLine(); QString linkDest( linkFile.right( linkFile.length() - destDir.length() ) ); linkDest.prepend( rootPath() ); // If file installed file is actually symbolic link, use actual file for linking QFileInfo fileInfo( linkFile ); if ( fileInfo.isSymLink() && !fileInfo.readLink().isEmpty() ) linkFile = fileInfo.readLink(); - + // See if directory exists in 'root', if not, create fileInfo.setFile( linkDest ); QString linkDestDirName = fileInfo.dirPath( true ); QDir linkDestDir( linkDestDirName ); if ( !linkDestDir.exists() ) { linkDestDir.mkdir( linkDestDirName ); } else { // Remove any previous link to make sure we will be pointing to the current version if ( QFile::exists( linkDest ) ) QFile::remove( linkDest ); } // Link the file //emit signalIpkgMessage( QString( "Linking '%1' to '%2'" ).arg( linkFile ).arg( linkDest ) ); if ( symlink( linkFile, linkDest ) == -1 ) emit signalIpkgMessage( tr( "Error linkling '%1' to '%2'" ) .arg( linkFile ) .arg( linkDest ) ); } packageFile.close(); } } } } void OIpkg::unlinkPackage( const QString &package, OConfItemList *destList ) { if ( !package.isNull() ) { // Find destination package is installed in if ( destList ) { OConfItemListIterator it( *destList ); for ( ; it.current(); ++it ) { OConfItem *dest = it.current(); QString destInfoFileName = QString( "%1/%2/%3.list" ).arg( dest->value() ) .arg( IPKG_INFO_PATH ) .arg( package ); //emit signalIpkgMessage( QString( "Looking for '%1'" ).arg ( destInfoFileName ) ); - + // If found and destination is not 'root', remove symbolic links if ( QFile::exists( destInfoFileName ) && dest->name() != "root" ) { QFile destInfoFile( destInfoFileName ); if ( destInfoFile.open( IO_ReadOnly ) ) { QTextStream t( &destInfoFile ); QString linkFile; while ( !t.eof() ) { // Get the name of the file to link and build the sym link filename linkFile = t.readLine(); QString linkDest( linkFile.right( linkFile.length() - dest->value().length() ) ); linkDest.prepend( rootPath() ); //emit signalIpkgMessage( QString( "Deleting: '%1'" ).arg( linkDest ) ); QFile::remove( linkDest ); } destInfoFile.close(); } emit signalIpkgMessage( tr( "Links removed for: %1" ).arg( package ) ); return; } } } } } diff --git a/noncore/settings/packagemanager/opackagemanager.cpp b/noncore/settings/packagemanager/opackagemanager.cpp index ac16954..c9fdec1 100644 --- a/noncore/settings/packagemanager/opackagemanager.cpp +++ b/noncore/settings/packagemanager/opackagemanager.cpp @@ -1,290 +1,287 @@ /* This file is part of the Opie Project Copyright (c) 2003 Dan Williams <drw@handhelds.org> =. .=l. .>+-= _;:, .> :=|. This program is free software; you can .> <`_, > . <= redistribute it and/or modify it under :`=1 )Y*s>-.-- : the terms of the GNU Library General Public .="- .-=="i, .._ License as published by the Free Software - . .-<_> .<> Foundation; either version 2 of the License, ._= =} : or (at your option) any later version. .%`+i> _;_. .i_,=:_. -<s. 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 -_. . . )=. = Library General Public License along with -- :-=` this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "opackagemanager.h" #include "oipkgconfigdlg.h" #include <qpe/qpeapplication.h> #include <ctype.h> OPackageManager::OPackageManager( Config *config, QObject *parent, const char *name ) : QObject( parent, name ) , m_config( config ) , m_ipkg( m_config, this ) , m_packages( 9973 ) , m_categories() { m_packages.setAutoDelete( true ); } void OPackageManager::loadAvailablePackages() { m_packages.clear(); OConfItemList *serverList = m_ipkg.servers(); if ( serverList ) { // Initialize status messaging emit initStatus( serverList->count() ); int serverCount = 0; bool categoryAdded = false; for ( OConfItemListIterator serverIt( *serverList ); serverIt.current(); ++serverIt ) { OConfItem *server = serverIt.current(); // Process server only if it is active if ( server->active() ) { // Update status QString status = tr( "Reading available packages:\n\t" ); status.append( server->name() ); emit statusText( status ); ++serverCount; emit statusBar( serverCount ); qApp->processEvents(); OPackageList *packageList = m_ipkg.availablePackages( server->name() ); if ( packageList ) { for ( OPackageListIterator packageIt( *packageList ); packageIt.current(); ++packageIt ) { OPackage *package = packageIt.current(); // Load package info - if ( !m_packages.find( package->name() ) ) { - printf( "ADD AVAILABLE '%s'\n", package->name().latin1()); + if ( !m_packages.find( package->name() ) ) m_packages.insert( package->name(), package ); - } else + else { // If new package is newer version, replace existing package OPackage *currPackage = m_packages[package->name()]; if ( compareVersions( package->version(), currPackage->version() ) == 1 ) m_packages.replace( package->name(), package ); } // Add category to list if it doesn't already exist if ( m_categories.grep( package->category() ).isEmpty() ) { m_categories << package->category(); categoryAdded = true; } } } } } delete serverList; // Sort category list if categories were added if ( categoryAdded ) m_categories.sort(); } } void OPackageManager::loadInstalledPackages() { OConfItemList *destList = m_ipkg.destinations(); if ( destList ) { // Initialize status messaging emit initStatus( destList->count() ); int destCount = 0; bool categoryAdded = false; for ( OConfItemListIterator destIt( *destList ); destIt.current(); ++destIt ) { OConfItem *destination = destIt.current(); // Process destination only if it is active if ( destination->active() ) { // Update status QString status = tr( "Reading installed packages:\n\t" ); status.append( destination->name() ); emit statusText( status ); ++destCount; emit statusBar( destCount ); qApp->processEvents(); - printf( "DESGTINATION %s\n", destination->name().latin1()); OPackageList *packageList = m_ipkg.installedPackages( destination->name(), destination->value() ); if ( packageList ) { for ( OPackageListIterator packageIt( *packageList ); packageIt.current(); ++packageIt ) { OPackage *package = packageIt.current(); OPackage *currPackage = m_packages[package->name()]; if ( currPackage ) { // Package is in a current feed, update installed version, destination currPackage->setVersionInstalled( package->versionInstalled() ); currPackage->setDestination( package->destination() ); delete package; } else { // Package isn't in a current feed, add to list - printf( "ADD INSTALLED '%s'\n", package->name().latin1()); m_packages.insert( package->name(), package ); // Add category to list if it doesn't already exist if ( m_categories.grep( package->category() ).isEmpty() ) { m_categories << package->category(); categoryAdded = true; } } } } } } delete destList; // Sort category list if categories were added if ( categoryAdded ) m_categories.sort(); } } OPackageList *OPackageManager::packages() { // TODO - look to see if list is loaded, if not, load available & installed OPackageList *pl = new OPackageList; for ( QDictIterator<OPackage> packageIt( m_packages ); packageIt.current(); ++packageIt ) pl->append( packageIt.current() ); return pl; } OPackageList *OPackageManager::filterPackages( const QString &name,const QString &server, const QString &destination, Status status, const QString &category ) { // TODO - look to see if list is loaded, if not, load available & installed OPackageList *pl = new OPackageList; for ( QDictIterator<OPackage> packageIt( m_packages ); packageIt.current(); ++packageIt ) { OPackage *package = packageIt.current(); bool nameMatch = ( name.isNull() || package->name().contains( name ) ); bool serverMatch = ( server.isNull() || package->source() == server ); bool destinationMatch = ( destination.isNull() || package->destination() == destination ); bool statusMatch; switch ( status ) { case All : statusMatch = true; break; case NotInstalled : statusMatch = package->versionInstalled().isNull(); break; case Installed : statusMatch = !package->versionInstalled().isNull(); break; case Updated : statusMatch = ( !package->versionInstalled().isNull() && compareVersions( package->version(), package->versionInstalled() ) == 1 ); break; default : statusMatch = true; break; }; bool categoryMatch = ( category.isNull() || package->category() == category ); if ( nameMatch && serverMatch && destinationMatch && statusMatch && categoryMatch ) pl->append( packageIt.current() ); } return pl; } QStringList OPackageManager::servers() { QStringList sl; OConfItemList *serverList = m_ipkg.servers(); if ( serverList ) { for ( OConfItemListIterator serverIt( *serverList ); serverIt.current(); ++serverIt ) { OConfItem *server = serverIt.current(); // Add only active servers if ( server->active() ) sl << server->name(); } } return sl; } QStringList OPackageManager::destinations() { QStringList dl; OConfItemList *destList = m_ipkg.destinations(); if ( destList ) { for ( OConfItemListIterator destIt( *destList ); destIt.current(); ++destIt ) { OConfItem *destination = destIt.current(); // Add only active destinations if ( destination->active() ) dl << destination->name(); } } return dl; } OConfItem *OPackageManager::findConfItem( OConfItem::Type type, const QString &name ) { return m_ipkg.findConfItem( type, name ); } OPackage *OPackageManager::findPackage( const QString &name ) { return m_packages[ name ]; } int OPackageManager::compareVersions( const QString &ver1, const QString &ver2 ) { // TODO - should this be in OIpkg??? int epoch1, epoch2; QString version1, revision1; QString version2, revision2; |