diff options
Diffstat (limited to 'src/libs/kdtools')
53 files changed, 11241 insertions, 0 deletions
diff --git a/src/libs/kdtools/LICENSE.LGPL b/src/libs/kdtools/LICENSE.LGPL new file mode 100644 index 000000000..ea164db15 --- /dev/null +++ b/src/libs/kdtools/LICENSE.LGPL @@ -0,0 +1,488 @@ + + The KD Tools Library is Copyright (C) 2001-2009 Klarälvdalens Datakonsult AB. + + You may use, distribute and copy the KD Tools Library under the terms of + GNU Library General Public License version 2, which is displayed below. + +------------------------------------------------------------------------- + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, 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 library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, 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 companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/src/libs/kdtools/environment.cpp b/src/libs/kdtools/environment.cpp new file mode 100644 index 000000000..4b031ecc4 --- /dev/null +++ b/src/libs/kdtools/environment.cpp @@ -0,0 +1,41 @@ +#include "environment.h" + +#include <QHash> +#include <QProcess> +#include <QProcessEnvironment> + +using namespace KDUpdater; + +Environment &Environment::instance() +{ + static Environment s_instance; + return s_instance; +} + +QString Environment::value(const QString &key, const QString &defvalue) const +{ + const QHash<QString, QString>::ConstIterator it = m_tempValues.constFind(key); + if (it != m_tempValues.constEnd()) + return *it; + return QProcessEnvironment::systemEnvironment().value(key, defvalue); +} + +void Environment::setTemporaryValue(const QString &key, const QString &value) +{ + m_tempValues.insert(key, value); +} + +QProcessEnvironment Environment::applyTo(const QProcessEnvironment &qpe_) const +{ + QProcessEnvironment qpe(qpe_); + QHash<QString, QString>::ConstIterator it = m_tempValues.constBegin(); + const QHash<QString, QString>::ConstIterator end = m_tempValues.constEnd(); + for ( ; it != end; ++it) + qpe.insert(it.key(), it.value()); + return qpe; +} + +void Environment::applyTo(QProcess *proc) +{ + proc->setProcessEnvironment(applyTo(proc->processEnvironment())); +} diff --git a/src/libs/kdtools/environment.h b/src/libs/kdtools/environment.h new file mode 100644 index 000000000..917da4f45 --- /dev/null +++ b/src/libs/kdtools/environment.h @@ -0,0 +1,39 @@ +#ifndef LIBINSTALLER_ENVIRONMENT_H +#define LIBINSTALLER_ENVIRONMENT_H + +#include "kdtoolsglobal.h" + +#include <QString> +#include <QHash> + +QT_BEGIN_NAMESPACE +class QProcess; +class QProcessEnvironment; +QT_END_NAMESPACE + +namespace KDUpdater { + +class KDTOOLS_EXPORT Environment +{ +public: + static Environment &instance(); + + ~Environment() {} + + QString value(const QString &key, const QString &defaultValue = QString()) const; + void setTemporaryValue(const QString &key, const QString &value); + + QProcessEnvironment applyTo(const QProcessEnvironment &qpe) const; + void applyTo(QProcess *process); + +private: + Environment() {} + +private: + Q_DISABLE_COPY(Environment) + QHash<QString, QString> m_tempValues; +}; + +} // namespace KDUpdater + +#endif diff --git a/src/libs/kdtools/kdgenericfactory.cpp b/src/libs/kdtools/kdgenericfactory.cpp new file mode 100644 index 000000000..9352f83cd --- /dev/null +++ b/src/libs/kdtools/kdgenericfactory.cpp @@ -0,0 +1,250 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdgenericfactory.h" + +/*! + \class KDGenericFactory + \ingroup core + \brief Template based generic factory implementation + \since_c 2.1 + + (The exception safety of this class has not been evaluated yet.) + + KDGenericFactory is an implemention of of the factory pattern. It can be used to + "produce" instances of different classes having a common superclass + T_Product. The user of the + factory registers those producable classes in the factory by using an identifier + (T_Identifier, defaulting to QString). That identifer can then be used to + produce as many instances of the registered product as he wants. + + The advanced user can even choose the type of the map the factory is using to store its + FactoryFunctions by passing a T_Map template parameter. It defaults to QHash. KDGenericFactory + expects it to be a template class accepting T_Identifier and FactoryFunction as parameters. + Additionally it needs to provide: + + \li\link QHash::const_iterator a nested %const_iterator \endlink typedef for an iterator type that when dereferenced has type ((const) reference to) FactoryFunction (Qt convention), + \li\link QHash::insert %insert( T_Identifier, FactoryFunction ) \endlink, which must overwrite any existing entries with the same identifier. + \li\link QHash::find %find( T_Identifier ) \endlink, + \li\link QHash::end %end() \endlink, + \li\link QHash::size %size() \endlink, + \li\link QHash::remove %remove( T_Identifier ) \endlink, and + \li\link QHash::keys %keys ) \endlink, returning a QList<T_Identifier>. + + The only two class templates that currently match this concept are + QHash and QMap. QMultiHash and QMulitMap do not work, since they + violate the requirement on insert() above, and std::map and + std::unordered_map do not match because they don't have keys() and + because a dereferenced iterator has type + std::pair<const T_Identifier,FactoryFunction> + instead of just FactoryFunction. + + \section general-use General Use + + The following example shows how the general use case of KDGenericFactory looks like: + + \code + + class Fruit + { + }; + + class Apple : public Fruit + { + }; + + class Pear : public Fruit + { + }; + + int main() + { + // creates a common fruit "factory" + KDGenericFactory< Fruit > fruitPlantation; + // registers the product "Apple" + fruitPlantation.registerProduct< Apple >( "Apple" ); + // registers the product "Pear" + fruitPlantation.registerProduct< Pear >( "Pear" ); + + // lets create some stuff - here comes our tasty apple: + Fruit* myApple = fruitPlantation.create( "Apple" ); + + // and a pear, please: + Fruit* myPear = fruitPlantation.create( "Pear" ); + + // ohh - that doesn't work, returns a null pointer: + Fruit* myCherry = fruitPlantation.create( "Cherry" ); + } + + \endcode +*/ + +/*! + \fn KDGenericFactory::~KDGenericFactory + + Destructor. +*/ + +/*! + \typedef KDGenericFactory::FactoryFunction + + This typedef defines a factory function producing an object of type T_Product. +*/ + +/*! + \fn KDGenericFactory::registerProduct( const T_Identifier& name ) + + Registers a product of type T, identified by \a name in the factory. + Any type with the same name gets unregistered. + + If a product was registered via this method, it will be created using its + default constructor. +*/ + +/*! + \fn KDGenericFactory::unregisterProduct( const T_Identifier& name ) + + Unregisters the previously registered product identified by \a name from the factory. + If no such product is known, nothing is done. +*/ + +/*! + \fn KDGenericFactory::productCount() const + + Returns the number of different products known to the factory. +*/ + +/*! + \fn KDGenericFactory::availableProducts() const + + Returns the list of products known to the factory. +*/ + +/*! + \fn KDGenericFactory::create( const T_Identifier& name ) const + + Creates and returns a product of the type identified by \a name. + Ownership of the product is transferred to the caller. +*/ + +/*! + \fn KDGenericFactory::registerProductionFunction( const T_Identifier& name, FactoryFunction create ) + + Subclasses can use this method to register their own FactoryFunction \a create to create products of + type T, identified by \a name. When a product is registered via this method, it will be created + by calling create(). +*/ + +#ifdef KDTOOLSCORE_UNITTESTS + +#include <KDUnitTest/test.h> + +#include <QStringList> +#include <QMap> + +class Fruit +{ +public: + virtual ~Fruit() {} +}; + +class Apple : public Fruit +{ +}; + +class Pear : public Fruit +{ +}; + +std::ostream& operator<<( std::ostream& stream, const QStringList& list ) +{ + stream << "QStringList("; + for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it ) + { + stream << " " << it->toLocal8Bit().data(); + if( it + 1 != list.end() ) + stream << ","; + } + stream << " )"; + return stream; +} + +class KDGenericFactoryTest : public KDUnitTest::Test { +public: + KDGenericFactoryTest() : Test( "KDGenericFactory" ) {} + void run() { + doRun<QHash>(); + doRun<QMap>(); + } + + template <template <typename U, typename V> class T_Map> + void doRun(); +}; + +KDAB_EXPORT_UNITTEST( KDGenericFactoryTest, "kdcoretools" ) + +template <template <typename U, typename V> class T_Map> +void KDGenericFactoryTest::doRun() { + + { + KDGenericFactory< Fruit, QString, T_Map > fruitPlantation; + assertEqual( fruitPlantation.productCount(), 0U ); + assertEqual( fruitPlantation.availableProducts(), QStringList() ); + + fruitPlantation.template registerProduct< Apple >( QLatin1String( "Apple" ) ); + assertEqual( fruitPlantation.productCount(), 1U ); + assertEqual( fruitPlantation.availableProducts(), QStringList( QLatin1String( "Apple" ) ) ); + + fruitPlantation.template registerProduct< Pear >( QLatin1String( "Pear" ) ); + assertEqual( fruitPlantation.productCount(), 2U ); + + Fruit* fruit = 0; + fruit = fruitPlantation.create( QLatin1String( "Apple" ) ); + assertNotNull( fruit ); + assertNotNull( dynamic_cast< Apple* >( fruit ) ); + + fruit = fruitPlantation.create( QLatin1String( "Pear" ) ); + assertNotNull( fruit ); + assertNotNull( dynamic_cast< Pear* >( fruit ) ); + + fruit = fruitPlantation.create( QLatin1String( "Cherry" ) ); + assertNull( fruit ); + + fruitPlantation.unregisterProduct( QLatin1String( "Apple" ) ); + assertEqual( fruitPlantation.productCount(), 1U ); + assertEqual( fruitPlantation.availableProducts(), QStringList( QLatin1String( "Pear" ) ) ); + fruit = fruitPlantation.create( QLatin1String( "Apple" ) ); + assertNull( fruit ); + + fruit = fruitPlantation.create( QLatin1String( "Pear" ) ); + assertNotNull( fruit ); + assertNotNull( dynamic_cast< Pear* >( fruit ) ); + + + fruitPlantation.unregisterProduct( QLatin1String( "Pear" ) ); + assertEqual( fruitPlantation.productCount(), 0U ); + fruit = fruitPlantation.create( QLatin1String( "Pear" ) ); + assertNull( fruit ); + } + +} +#endif // KDTOOLSCORE_UNITTESTS diff --git a/src/libs/kdtools/kdgenericfactory.h b/src/libs/kdtools/kdgenericfactory.h new file mode 100644 index 000000000..d12c35785 --- /dev/null +++ b/src/libs/kdtools/kdgenericfactory.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS__KDGENERICFACTORY_H +#define KDTOOLS__KDGENERICFACTORY_H + +#include <kdtoolsglobal.h> + +#include <QtCore/QHash> + +template <typename T_Product, typename T_Identifier = QString> +class KDGenericFactory +{ +public: + virtual ~KDGenericFactory() {} + + typedef T_Product *(*FactoryFunction)(); + + template <typename T> + void registerProduct(const T_Identifier &name) + { +#ifdef Q_CC_MSVC + FactoryFunction function = &KDGenericFactory::create<T>; +#else // compile fix for old gcc + FactoryFunction function = &create<T>; +#endif + map.insert(name, function); + } + + T_Product *create(const T_Identifier &name) const + { + const typename QHash<T_Identifier, FactoryFunction>::const_iterator it = map.find(name); + if (it == map.end()) + return 0; + return (*it)(); + } + +private: + template <typename T> + static T_Product *create() + { + return new T; + } + + QHash<T_Identifier, FactoryFunction> map; +}; + +#endif diff --git a/src/libs/kdtools/kdjob.cpp b/src/libs/kdtools/kdjob.cpp new file mode 100644 index 000000000..47a30a836 --- /dev/null +++ b/src/libs/kdtools/kdjob.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdjob.h" + +#include <QtCore/QDebug> +#include <QtCore/QEventLoop> +#include <QtCore/QTimer> + + +// -- KDJob::Private + +class KDJob::Private +{ + KDJob *const q; + +public: + explicit Private(KDJob *qq) + : q(qq) + , error(KDJob::NoError) + , caps(KDJob::NoCapabilities) + , autoDelete(true) + , totalAmount(100) + , processedAmount(0) + , m_timeout(-1) + { + connect(&m_timer, SIGNAL(timeout()), q, SLOT(cancel())); + } + + ~Private() + { + m_timer.stop(); + } + + void delayedStart() + { + q->doStart(); + emit q->started(q); + } + + void waitForSignal(const char *sig) + { + QEventLoop loop; + q->connect(q, sig, &loop, SLOT(quit())); + + if (m_timeout >= 0) + m_timer.start(m_timeout); + else + m_timer.stop(); + + loop.exec(); + } + + int error; + QString errorString; + KDJob::Capabilities caps; + bool autoDelete; + quint64 totalAmount; + quint64 processedAmount; + int m_timeout; + QTimer m_timer; +}; + + +// -- KDJob + +KDJob::KDJob(QObject *parent) + : QObject(parent), + d(new Private(this)) +{ + connect(this, SIGNAL(finished(KDJob*)), this, SLOT(onFinished())); +} + +KDJob::~KDJob() +{ + delete d; +} + +bool KDJob::autoDelete() const +{ + return d->autoDelete; +} + +void KDJob::setAutoDelete(bool autoDelete) +{ + d->autoDelete = autoDelete; +} + +int KDJob::error() const +{ + return d->error; +} + +QString KDJob::errorString() const +{ + return d->errorString; +} + +void KDJob::emitFinished() +{ + emit finished(this); +} + +void KDJob::emitFinishedWithError(int error, const QString &errorString) +{ + d->error = error; + d->errorString = errorString; + emitFinished(); +} + +void KDJob::setError(int error) +{ + d->error = error; +} + +void KDJob::setErrorString(const QString &errorString) +{ + d->errorString = errorString; +} + +void KDJob::waitForStarted() +{ + d->waitForSignal(SIGNAL(started(KDJob*))); +} + +void KDJob::waitForFinished() +{ + d->waitForSignal(SIGNAL(finished(KDJob*))); +} + +KDJob::Capabilities KDJob::capabilities() const +{ + return d->caps; +} + +bool KDJob::hasCapability(Capability c) const +{ + return d->caps.testFlag(c); +} + +void KDJob::setCapabilities(Capabilities c) +{ + d->caps = c; +} + +void KDJob::start() +{ + QMetaObject::invokeMethod(this, "delayedStart", Qt::QueuedConnection); +} + +void KDJob::cancel() +{ + if (d->caps & Cancelable) { + doCancel(); + if (error() == NoError) { + setError(Canceled); + setErrorString(tr("Canceled")); + } + emitFinished(); + } else { + qDebug() << "The current job can not be canceled, missing \"Cancelable\" capability!"; + } +} + +quint64 KDJob::totalAmount() const +{ + return d->totalAmount; +} + +quint64 KDJob::processedAmount() const +{ + return d->processedAmount; +} + +void KDJob::setTotalAmount(quint64 amount) +{ + if (d->totalAmount == amount) + return; + d->totalAmount = amount; + emit progress(this, d->processedAmount, d->totalAmount); +} + +/*! + Returns the timeout in milliseconds before the jobs cancel slot gets triggered. A return value of -1 + means there is currently no timeout used for the job. +*/ +int KDJob::timeout() const +{ + return d->m_timeout; +} + +/*! + Sets the timeout in \a milliseconds before the jobs cancel slot gets triggered. \note Only jobs that + have the \bold KDJob::Cancelable capability can be canceled by an timeout. A value of -1 will stop the + timeout mechanism. +*/ +void KDJob::setTimeout(int milliseconds) +{ + d->m_timeout = milliseconds; +} + +void KDJob::setProcessedAmount(quint64 amount) +{ + if (d->processedAmount == amount) + return; + d->processedAmount = amount; + emit progress(this, d->processedAmount, d->totalAmount); +} + +void KDJob::onFinished() +{ + d->m_timer.stop(); + if (d->autoDelete) + deleteLater(); +} + +#include "moc_kdjob.cpp" diff --git a/src/libs/kdtools/kdjob.h b/src/libs/kdtools/kdjob.h new file mode 100644 index 000000000..37f441526 --- /dev/null +++ b/src/libs/kdtools/kdjob.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_KDJOB_H +#define KDTOOLS_KDJOB_H + +#include "kdtoolsglobal.h" + +#include <QtCore/QObject> + +class KDTOOLS_EXPORT KDJob : public QObject +{ + Q_OBJECT + class Private; + + Q_PROPERTY(int timeout READ timeout WRITE setTimeout) + Q_PROPERTY(bool autoDelete READ autoDelete WRITE setAutoDelete) + +public: + explicit KDJob(QObject *parent = 0); + ~KDJob(); + + enum Error { + NoError = 0, + Canceled = 1, + UserDefinedError = 128 + }; + + enum Capability { + NoCapabilities = 0x0, + Cancelable = 0x1 + }; + + Q_DECLARE_FLAGS(Capabilities, Capability) + + int error() const; + QString errorString() const; + + bool autoDelete() const; + void setAutoDelete(bool autoDelete); + + Capabilities capabilities() const; + bool hasCapability(Capability c) const; + + void waitForStarted(); + void waitForFinished(); + + quint64 totalAmount() const; + quint64 processedAmount() const; + + int timeout() const; + void setTimeout(int milliseconds); + +public Q_SLOTS: + void start(); + void cancel(); + +Q_SIGNALS: + void started(KDJob *job); + void finished(KDJob *job); + + void infoMessage(KDJob *job, const QString &message); + void progress(KDJob *job, quint64 processed, quint64 total); + +protected: + virtual void doStart() = 0; + virtual void doCancel() = 0; + + void setCapabilities(Capabilities c); + + void setTotalAmount(quint64 amount); + void setProcessedAmount(quint64 amount); + + void setError(int error); + void setErrorString(const QString &errorString); + + void emitFinished(); + void emitFinishedWithError(int error, const QString &errorString); + +private Q_SLOTS: + void onFinished(); + +private: + Private *d; + Q_PRIVATE_SLOT(d, void delayedStart()) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(KDJob::Capabilities) + +#endif // KDTOOLS_KDJOB_H diff --git a/src/libs/kdtools/kdlockfile.cpp b/src/libs/kdtools/kdlockfile.cpp new file mode 100644 index 000000000..3a440b6b0 --- /dev/null +++ b/src/libs/kdtools/kdlockfile.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdlockfile.h" + +#include "kdlockfile_p.h" + +KDLockFile::Private::Private(const QString &filename_) + : filename(filename_) + , handle(0) + , locked(false) +{ +} + +KDLockFile::KDLockFile(const QString &name) + : d( new Private(name)) +{ +} + +KDLockFile::~KDLockFile() +{ + delete d; +} + +bool KDLockFile::lock() +{ + return d->lock(); +} + +QString KDLockFile::errorString() const +{ + return d->errorString; +} + +bool KDLockFile::unlock() +{ + return d->unlock(); +} + + +#ifdef KDTOOLSCORE_UNITTESTS + +#include <KDUnitTest/Test> +#include <QDebug> +#include <QDir> + +KDAB_UNITTEST_SIMPLE( KDLockFile, "kdcoretools" ) { + { + KDLockFile f( QLatin1String("/jlksdfdsfjkldsf-doesnotexist/file") ); + const bool locked = f.lock(); + assertFalse( locked ); + qDebug() << f.errorString(); + assertTrue( !f.errorString().isEmpty() ); + if ( !locked ) + assertTrue( f.unlock() ); + } + { + KDLockFile f( QDir::currentPath() + QLatin1String("/kdlockfile-test") ); + const bool locked = f.lock(); + assertTrue( locked ); + if ( !locked ) + qDebug() << f.errorString(); + assertEqual( locked, f.errorString().isEmpty() ); + const bool unlocked = f.unlock(); + assertTrue( unlocked ); + if ( !unlocked ) + qDebug() << f.errorString(); + assertEqual( unlocked, f.errorString().isEmpty() ); + } +} + +#endif // KDTOOLSCORE_UNITTESTS diff --git a/src/libs/kdtools/kdlockfile.h b/src/libs/kdtools/kdlockfile.h new file mode 100644 index 000000000..1674070bf --- /dev/null +++ b/src/libs/kdtools/kdlockfile.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_KDLOCKFILE_H +#define KDTOOLS_KDLOCKFILE_H + +#include <kdtoolsglobal.h> + +class KDTOOLS_EXPORT KDLockFile +{ +public: + explicit KDLockFile(const QString &name); + ~KDLockFile(); + + QString errorString() const; + + bool lock(); + bool unlock(); + +private: + Q_DISABLE_COPY(KDLockFile) + class Private; + Private *d; +}; + +#endif // KDTOOLS_KDLOCKFILE_H diff --git a/src/libs/kdtools/kdlockfile_p.h b/src/libs/kdtools/kdlockfile_p.h new file mode 100644 index 000000000..da4c4dc4c --- /dev/null +++ b/src/libs/kdtools/kdlockfile_p.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef __KDTOOLSCORE_KDLOCKFILE_P_H__ +#define __KDTOOLSCORE_KDLOCKFILE_P_H__ + +#include "kdlockfile.h" +#include <QtCore/QString> +#ifdef Q_OS_WIN +#include <windows.h> +#endif + +class KDLockFile::Private +{ +public: + explicit Private( const QString& filename ); + ~Private(); + bool lock(); + bool unlock(); + + QString errorString; + +private: + QString filename; +#ifdef Q_OS_WIN + HANDLE handle; +#else + int handle; +#endif + bool locked; +}; + +#endif // LOCKFILE_P_H diff --git a/src/libs/kdtools/kdlockfile_unix.cpp b/src/libs/kdtools/kdlockfile_unix.cpp new file mode 100644 index 000000000..c40eb3b7d --- /dev/null +++ b/src/libs/kdtools/kdlockfile_unix.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdlockfile_p.h" + +#include <QtCore/QCoreApplication> + +#include <cerrno> + +#include <sys/file.h> + +KDLockFile::Private::~Private() +{ + unlock(); +} + +bool KDLockFile::Private::lock() +{ + if (locked) + return true; + + errorString.clear(); + errno = 0; + handle = open(filename.toLatin1().constData(), O_CREAT | O_RDWR | O_NONBLOCK, 0600); + if (handle == -1) { + errorString = QObject::tr("Could not create lock file %1: %2").arg(filename, QLatin1String(strerror(errno))); + return false; + } + const QString pid = QString::number(qApp->applicationPid()); + const QByteArray data = pid.toLatin1(); + errno = 0; + qint64 written = 0; + while (written < data.size()) { + const qint64 n = write(handle, data.constData() + written, data.size() - written); + if (n < 0) { + errorString = QObject::tr("Could not write PID to lock file %1: %2").arg( filename, QLatin1String( strerror( errno ) ) ); + return false; + } + written += n; + } + errno = 0; + locked = flock(handle, LOCK_NB | LOCK_EX) != -1; + if (!locked) + errorString = QObject::tr("Could not lock lock file %1: %2").arg(filename, QLatin1String(strerror(errno))); + return locked; +} + +bool KDLockFile::Private::unlock() +{ + errorString.clear(); + if (!locked) + return true; + errno = 0; + locked = flock(handle, LOCK_UN | LOCK_NB) == -1; + if (locked) + errorString = QObject::tr("Could not unlock lock file %1: %2").arg(filename, QLatin1String(strerror(errno))); + return !locked; +} diff --git a/src/libs/kdtools/kdlockfile_win.cpp b/src/libs/kdtools/kdlockfile_win.cpp new file mode 100644 index 000000000..f139bd0d4 --- /dev/null +++ b/src/libs/kdtools/kdlockfile_win.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdlockfile.h" +#include "kdlockfile_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QFileInfo> + +KDLockFile::Private::~Private() +{ + unlock(); +} + +bool KDLockFile::Private::lock() +{ + const QFileInfo fi(filename); + handle = CreateFile(filename.toStdWString().data(), + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, + NULL, fi.exists() ? OPEN_EXISTING : CREATE_NEW, + FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + + if (!handle) + return false; + QString pid = QString::number(qApp->applicationPid()); + QByteArray data = pid.toLatin1(); + DWORD bytesWritten; + const bool wrotePid = WriteFile(handle, data.data(), data.size(), &bytesWritten, NULL); + if (!wrotePid) + return false; + FlushFileBuffers(handle); + + const bool locked = LockFile(handle, 0, 0, fi.size(), 0); + + this->locked = locked; + return locked; +} + +bool KDLockFile::Private::unlock() +{ + const QFileInfo fi(filename); + if (locked) { + const bool success = UnlockFile(handle, 0, 0, 0, fi.size()); + this->locked = !success; + CloseHandle(handle); + return success; + } + return true; +} diff --git a/src/libs/kdtools/kdrunoncechecker.cpp b/src/libs/kdtools/kdrunoncechecker.cpp new file mode 100644 index 000000000..8e0af3ddc --- /dev/null +++ b/src/libs/kdtools/kdrunoncechecker.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdrunoncechecker.h" +#include "kdlockfile.h" +#include "kdsysinfo.h" + +#include <QtCore/QList> +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> + +#include <algorithm> + +using namespace KDUpdater; + +class KDRunOnceChecker::Private +{ +public: + Private(const QString &filename); + + KDLockFile m_lockfile; + bool m_hasLock; +}; + +KDRunOnceChecker::Private::Private(const QString &filename) + : m_lockfile(filename) + , m_hasLock(false) +{} + +KDRunOnceChecker::KDRunOnceChecker(const QString &filename) + :d(new Private(filename)) +{} + +KDRunOnceChecker::~KDRunOnceChecker() +{ + delete d; +} + +class ProcessnameEquals +{ +public: + ProcessnameEquals(const QString &name): m_name(name) {} + + bool operator()(const ProcessInfo &info) + { +#ifndef Q_WS_WIN + if (info.name == m_name) + return true; + const QFileInfo fi(info.name); + if (fi.fileName() == m_name || fi.baseName() == m_name) + return true; + return false; +#else + if (info.name.toLower() == m_name.toLower()) + return true; + if (info.name.toLower() == QDir::toNativeSeparators(m_name.toLower())) + return true; + const QFileInfo fi(info.name); + if (fi.fileName().toLower() == m_name.toLower() || fi.baseName().toLower() == m_name.toLower()) + return true; + return info.name == m_name; +#endif + } + +private: + QString m_name; +}; + +bool KDRunOnceChecker::isRunning(Dependencies depends) +{ + bool running = false; + switch (depends) { + case Lockfile: { + const bool locked = d->m_hasLock || d->m_lockfile.lock(); + if (locked) + d->m_hasLock = true; + running = running || ! locked; + } + break; + case ProcessList: { + const QList<ProcessInfo> allProcesses = runningProcesses(); + const QString appName = qApp->applicationFilePath(); + //QList< ProcessInfo >::const_iterator it = std::find_if(allProcesses.constBegin(), allProcesses.constEnd(), ProcessnameEquals(appName)); + const int count = std::count_if(allProcesses.constBegin(), allProcesses.constEnd(), ProcessnameEquals(appName)); + running = running || /*it != allProcesses.constEnd()*/count > 1; + } + break; + case Both: { + const QList<ProcessInfo> allProcesses = runningProcesses(); + const QString appName = qApp->applicationFilePath(); + //QList<ProcessInfo>::const_iterator it = std::find_if(allProcesses.constBegin(), allProcesses.constEnd(), ProcessnameEquals(appName)); + const int count = std::count_if(allProcesses.constBegin(), allProcesses.constEnd(), ProcessnameEquals(appName)); + const bool locked = d->m_hasLock || d->m_lockfile.lock(); + if (locked) + d->m_hasLock = true; + running = running || ( /*it != allProcesses.constEnd()*/count > 1 && !locked); + } + break; + } + + return running; +} diff --git a/src/libs/kdtools/kdrunoncechecker.h b/src/libs/kdtools/kdrunoncechecker.h new file mode 100644 index 000000000..127102274 --- /dev/null +++ b/src/libs/kdtools/kdrunoncechecker.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_RUNONCECHECKER_H +#define KDTOOLS_RUNONCECHECKER_H + +#include <kdtoolsglobal.h> + +#include <QString> + +class KDTOOLS_EXPORT KDRunOnceChecker +{ +public: + enum Dependencies { ProcessList, Lockfile, Both }; + + explicit KDRunOnceChecker(const QString &filename = QString()); + ~KDRunOnceChecker(); + bool isRunning(Dependencies depends); + +private: + Q_DISABLE_COPY(KDRunOnceChecker) + class Private; + Private *d; +}; + +#endif // KDTOOLS_RUNONCECHECKER_H diff --git a/src/libs/kdtools/kdsavefile.cpp b/src/libs/kdtools/kdsavefile.cpp new file mode 100644 index 000000000..d866968cf --- /dev/null +++ b/src/libs/kdtools/kdsavefile.cpp @@ -0,0 +1,521 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdsavefile.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QPointer> +#include <QtCore/QTemporaryFile> + +#ifdef Q_OS_WIN +# include <io.h> +#endif +#include <memory> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> + +static int permissionsToMode(QFile::Permissions p, bool *ok) +{ + Q_ASSERT(ok); + int m = 0; +#ifdef Q_OS_WIN + //following qfsfileengine_win.cpp: QFSFileEngine::setPermissions() + if (p & QFile::ReadOwner || p & QFile::ReadUser || p & QFile::ReadGroup || p & QFile::ReadOther) + m |= _S_IREAD; + if (p & QFile::WriteOwner || p & QFile::WriteUser || p & QFile::WriteGroup || p & QFile::WriteOther) + m |= _S_IWRITE; + *ok = m != 0; +#else + if (p & QFile::ReadUser) + m |= S_IRUSR; + if (p & QFile::WriteUser) + m |= S_IWUSR; + if (p & QFile::ExeUser) + m |= S_IXUSR; + if (p & QFile::ReadGroup) + m |= S_IRGRP; + if (p & QFile::WriteGroup) + m |= S_IWGRP; + if (p & QFile::ExeGroup) + m |= S_IXGRP; + if (p & QFile::ReadOther) + m |= S_IROTH; + if (p & QFile::WriteOther) + m |= S_IWOTH; + if (p & QFile::ExeOther) + m |= S_IXOTH; + *ok = true; +#endif + return m; +} + +static bool sync(int fd) +{ +#ifdef Q_OS_WIN + return _commit(fd) == 0; +#else + return fsync(fd) == 0; +#endif +} + +static QString makeAbsolute(const QString &path) +{ + if (QDir::isAbsolutePath(path)) + return path; + return QDir::currentPath() + QLatin1String("/") + path; +} + +static int myOpen(const QString &path, int flags, int mode) +{ +#ifdef Q_OS_WIN + int fd; + _wsopen_s(&fd, reinterpret_cast<const wchar_t *>(path.utf16()), flags, _SH_DENYRW, mode); + return fd; +#else + return open(QFile::encodeName(path).constData(), flags, mode); +#endif +} + +static void myClose(int fd) +{ +#ifdef Q_OS_WIN + _close(fd); +#else + close(fd); +#endif +} + +static bool touchFile(const QString &path, QFile::Permissions p) +{ + bool ok; + const int mode = permissionsToMode(p, &ok); + if (!ok) + return false; + const int fd = myOpen(QDir::toNativeSeparators(path), O_WRONLY|O_CREAT, mode); + if (fd < 0) { + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) + return false; + if (!file.setPermissions(p)) { + QFile::remove(path); + return false; + } + return true; + } + sync(fd); + myClose(fd); + return true; +} + +static QFile *createFile(const QString &path, QIODevice::OpenMode m, QFile::Permissions p, bool *openOk) +{ + Q_ASSERT(openOk); + if (!touchFile(path, p)) + return 0; + std::auto_ptr<QFile> file(new QFile(path)); + *openOk = file->open(m | QIODevice::Append); + if (!*openOk) + QFile::remove(path); // try to remove empty file + return file.release(); +} + +/*! + Generates a temporary file name based on template \a path + \internal + */ +static QString generateTempFileName(const QString &path) +{ + const QString tmp = path + QLatin1String("tmp.dsfdf.%1"); //TODO: use random suffix + int count = 1; + while (QFile::exists(tmp.arg(count))) + ++count; + return tmp.arg(count); +} + +/*! + \class KDSaveFile KDSaveFile + \ingroup core + \brief Secure and robust writing to a file + +*/ + +class KDSaveFile::Private +{ + KDSaveFile *const q; +public: + explicit Private(const QString &fname, KDSaveFile *qq) + : q(qq), +#ifdef Q_OS_WIN + backupExtension(QLatin1String(".bak")), +#else + backupExtension(QLatin1String("~")), +#endif + permissions(QFile::ReadUser|QFile::WriteUser), filename(fname), tmpFile() + { + //TODO respect umask instead of hardcoded default permissions + } + + ~Private() + { + deleteTempFile(); + } + + bool deleteTempFile() + { + if (!tmpFile) + return true; + const QString name = tmpFile->fileName(); + delete tmpFile; + //force a real close by deleting the object, before deleting the actual file. Needed on Windows + QFile tmp(name); + return tmp.remove(); + } + + bool recreateTemporaryFile(QIODevice::OpenMode mode) + { + deleteTempFile(); + bool ok; + tmpFile = createFile(generateTempFileName( filename ), mode, permissions, &ok); + QObject::connect(tmpFile, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + QObject::connect(tmpFile, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + QObject::connect(tmpFile, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + QObject::connect(tmpFile, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + return ok; + } + + QString generateBackupName() const + { + const QString bf = filename + backupExtension; + if (!QFile::exists(bf)) + return bf; + int count = 1; + while (QFile::exists(bf + QString::number(count))) + ++count; + return bf + QString::number(count); + } + + void propagateErrors() + { + if (!tmpFile) + return; + q->setErrorString(tmpFile->errorString()); + } + + QString backupExtension; + QFile::Permissions permissions; + QString filename; + QPointer<QFile> tmpFile; +}; + + +KDSaveFile::KDSaveFile(const QString &filename, QObject *parent) + : QIODevice(parent), d( new Private(makeAbsolute(filename), this)) +{} + +KDSaveFile::~KDSaveFile() +{ + delete d; +} + +void KDSaveFile::close() +{ + d->deleteTempFile(); + QIODevice::close(); +} + +bool KDSaveFile::open(OpenMode mode) +{ + setOpenMode(QIODevice::NotOpen); + if (mode & QIODevice::Append) { + setErrorString(tr("Append mode not supported.")); + return false; + } + + if ((mode & QIODevice::WriteOnly) == 0){ + setErrorString(tr("Read-only access not supported.")); + return false; + } + + // for some reason this seems to be problematic... let's remove it for now, will break in commit anyhow, if it's serious + //if ( QFile::exists( d->filename ) && !QFileInfo( d->filename ).isWritable() ) { + // setErrorString( tr("The target file %1 exists and is not writable").arg( d->filename ) ); + // return false; + //} + const bool opened = d->recreateTemporaryFile(mode); + if (opened) { + setOpenMode(mode); + } + + //if target file already exists, apply permissions of existing file to temp file + return opened; +} + +bool KDSaveFile::atEnd() +{ + return d->tmpFile ? d->tmpFile->atEnd() : QIODevice::atEnd(); +} + +qint64 KDSaveFile::bytesAvailable() const +{ + return d->tmpFile ? d->tmpFile->bytesAvailable() : QIODevice::bytesAvailable(); +} + +qint64 KDSaveFile::bytesToWrite() const +{ + return d->tmpFile ? d->tmpFile->bytesToWrite() : QIODevice::bytesToWrite(); +} + +bool KDSaveFile::canReadLine() const +{ + return d->tmpFile ? d->tmpFile->canReadLine() : QIODevice::canReadLine(); +} + +bool KDSaveFile::isSequential() const +{ + return d->tmpFile ? d->tmpFile->isSequential() : QIODevice::isSequential(); +} + +qint64 KDSaveFile::pos() const +{ + return d->tmpFile ? d->tmpFile->pos() : QIODevice::pos(); +} + +bool KDSaveFile::reset() +{ + return d->tmpFile ? d->tmpFile->reset() : QIODevice::reset(); +} + +bool KDSaveFile::seek(qint64 pos) +{ + const bool ret = d->tmpFile ? d->tmpFile->seek(pos) : QIODevice::seek(pos); + if (!ret) + d->propagateErrors(); + return ret; +} + +qint64 KDSaveFile::size() const +{ + return d->tmpFile ? d->tmpFile->size() : QIODevice::size(); +} + +bool KDSaveFile::waitForBytesWritten(int msecs) +{ + return d->tmpFile ? d->tmpFile->waitForBytesWritten(msecs) : QIODevice::waitForBytesWritten(msecs); +} + +bool KDSaveFile::waitForReadyRead(int msecs) +{ + return d->tmpFile ? d->tmpFile->waitForReadyRead(msecs) : QIODevice::waitForReadyRead(msecs); +} + +bool KDSaveFile::commit(KDSaveFile::CommitMode mode) +{ + if (!d->tmpFile) + return false; + const QString tmpfname = d->tmpFile->fileName(); + d->tmpFile->flush(); + delete d->tmpFile; + QFile orig(d->filename); + QString backup; + if (orig.exists()) { + backup = d->generateBackupName(); + if (!orig.rename(backup)) { + setErrorString(tr("Could not backup existing file %1: %2").arg( d->filename, orig.errorString())); + QFile tmp(tmpfname); + if (!tmp.remove()) // TODO how to report this error? + qWarning() << "Could not remove temp file" << tmpfname << tmp.errorString(); + if (mode != OverwriteExistingFile) + return false; + } + } + QFile target(tmpfname); + if (!target.rename(d->filename)) { + setErrorString(target.errorString()); + return false; + } + if (mode == OverwriteExistingFile) { + QFile tmp(backup); + const bool removed = !tmp.exists() || tmp.remove(backup); + if (!removed) + qWarning() << "Could not remove the backup: " << tmp.errorString(); + } + + return true; +} + +QString KDSaveFile::fileName() const +{ + return d->filename; +} + +void KDSaveFile::setFileName(const QString &filename) +{ + const QString fn = makeAbsolute(filename); + if (fn == d->filename) + return; + close(); + delete d->tmpFile; + d->filename = fn; +} + +qint64 KDSaveFile::readData(char *data, qint64 maxSize) +{ + if (!d->tmpFile) { + setErrorString(tr("TODO")); + return -1; + } + const qint64 ret = d->tmpFile->read(data, maxSize); + d->propagateErrors(); + return ret; +} + +qint64 KDSaveFile::readLineData(char *data, qint64 maxSize) +{ + if (!d->tmpFile) { + setErrorString(tr("TODO")); + return -1; + } + const qint64 ret = d->tmpFile->readLine(data, maxSize); + d->propagateErrors(); + return ret; +} + +qint64 KDSaveFile::writeData(const char *data, qint64 maxSize) +{ + if (!d->tmpFile) { + setErrorString(tr("TODO")); + return -1; + } + const qint64 ret = d->tmpFile->write(data, maxSize); + d->propagateErrors(); + return ret; +} + +bool KDSaveFile::flush() +{ + return d->tmpFile ? d->tmpFile->flush() : false; +} + +bool KDSaveFile::resize(qint64 sz) +{ + return d->tmpFile ? d->tmpFile->resize(sz) : false; +} + +int KDSaveFile::handle() const +{ + return d->tmpFile ? d->tmpFile->handle() : -1; +} + +QFile::Permissions KDSaveFile::permissions() const +{ + return d->tmpFile ? d->tmpFile->permissions() : d->permissions; +} + +bool KDSaveFile::setPermissions(QFile::Permissions p) +{ + d->permissions = p; + if (d->tmpFile) + return d->tmpFile->setPermissions(p); + return false; +} + +void KDSaveFile::setBackupExtension(const QString &ext) +{ + d->backupExtension = ext; +} + +QString KDSaveFile::backupExtension() const +{ + return d->backupExtension; +} + +/** + * TODO + * + * + */ + +#ifdef KDTOOLSCORE_UNITTESTS + +#include <KDUnitTest/Test> + +KDAB_UNITTEST_SIMPLE( KDSaveFile, "kdcoretools" ) { + //TODO test contents (needs blocking and checked write() ) + { + const QString testfile1 = QLatin1String("kdsavefile-test1"); + QByteArray testData("lalalala"); + KDSaveFile saveFile( testfile1 ); + assertTrue( saveFile.open( QIODevice::WriteOnly ) ); + saveFile.write( testData.constData(), testData.size() ); + assertTrue( saveFile.commit() ); + assertTrue( QFile::exists( testfile1 ) ); + assertTrue( QFile::remove( testfile1 ) ); + } + { + const QString testfile1 = QLatin1String("kdsavefile-test1"); + QByteArray testData("lalalala"); + KDSaveFile saveFile( testfile1 ); + assertTrue( saveFile.open( QIODevice::WriteOnly ) ); + saveFile.write( testData.constData(), testData.size() ); + saveFile.close(); + assertFalse( QFile::exists( testfile1 ) ); + } + { + const QString testfile1 = QLatin1String("kdsavefile-test1"); + QByteArray testData("lalalala"); + KDSaveFile saveFile( testfile1 ); + assertTrue( saveFile.open( QIODevice::WriteOnly ) ); + saveFile.write( testData.constData(), testData.size() ); + assertTrue( saveFile.commit() ); + assertTrue( QFile::exists( testfile1 ) ); + + KDSaveFile sf2( testfile1 ); + sf2.setBackupExtension( QLatin1String(".bak") ); + assertTrue( sf2.open( QIODevice::WriteOnly ) ); + sf2.write( testData.constData(), testData.size() ); + sf2.commit(); //commit in backup mode (default) + const QString backup = testfile1 + sf2.backupExtension(); + assertTrue( QFile::exists( backup ) ); + assertTrue( QFile::remove( backup ) ); + + KDSaveFile sf3( testfile1 ); + sf3.setBackupExtension( QLatin1String(".bak") ); + assertTrue( sf3.open( QIODevice::WriteOnly ) ); + sf3.write( testData.constData(), testData.size() ); + sf3.commit( KDSaveFile::OverwriteExistingFile ); + const QString backup2 = testfile1 + sf3.backupExtension(); + assertFalse( QFile::exists( backup2 ) ); + + assertTrue( QFile::remove( testfile1 ) ); + } + { + const QString testfile1 = QLatin1String("kdsavefile-test1"); + KDSaveFile sf( testfile1 ); + assertFalse( sf.open( QIODevice::ReadOnly ) ); + assertFalse( sf.open( QIODevice::WriteOnly|QIODevice::Append ) ); + assertTrue( sf.open( QIODevice::ReadWrite ) ); + } +} + +#endif // KDTOOLSCORE_UNITTESTS diff --git a/src/libs/kdtools/kdsavefile.h b/src/libs/kdtools/kdsavefile.h new file mode 100644 index 000000000..dc42e1930 --- /dev/null +++ b/src/libs/kdtools/kdsavefile.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_KDSAVEFILE_H +#define KDTOOLS_KDSAVEFILE_H + +#include <kdtoolsglobal.h> + +#include <QtCore/QIODevice> +#include <QtCore/QFile> +#include <QtCore/QString> + +class KDTOOLS_EXPORT KDSaveFile : public QIODevice +{ + Q_OBJECT + +public: + explicit KDSaveFile(const QString &filename = QString(), QObject *parent = 0); + ~KDSaveFile(); + + enum CommitMode { + BackupExistingFile = 1, + OverwriteExistingFile = 2 + }; + + bool commit(CommitMode = BackupExistingFile); + + QString fileName() const; + void setFileName(const QString &filename); + + QFile::Permissions permissions() const; + bool setPermissions(QFile::Permissions); + + QString backupExtension() const; + void setBackupExtension(const QString &extension); + + bool flush(); + bool resize(qint64 size); + int handle() const; + + bool atEnd(); + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + bool canReadLine() const; + void close(); + bool isSequential() const; + bool open(OpenMode mode = QIODevice::ReadWrite); //only valid: WriteOnly, ReadWrite + qint64 pos() const; + bool reset(); + bool seek(qint64 pos); + qint64 size() const; + bool waitForBytesWritten(int msecs); + bool waitForReadyRead(int msecs); + +private: + qint64 readData(char *data, qint64 maxSize); + qint64 readLineData(char *data, qint64 maxSize); + qint64 writeData(const char *data, qint64 maxSize); + +private: + class Private; + Private *d; +}; + +#endif // KDTOOLS_KDSAVEFILE_H diff --git a/src/libs/kdtools/kdselfrestarter.cpp b/src/libs/kdtools/kdselfrestarter.cpp new file mode 100644 index 000000000..22457bd92 --- /dev/null +++ b/src/libs/kdtools/kdselfrestarter.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdselfrestarter.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> +#include <QtCore/QProcess> + +class KDSelfRestarter::Private +{ +public: + Private(int argc, char *argv[]) + : restartOnQuit(false) + { + executable = QString::fromLocal8Bit(argv[0]); + workingPath = QDir::currentPath(); + for (int i = 1; i < argc; ++i) + args << QString::fromLocal8Bit(argv[i]); + } + + Private() + { + executable = qApp->applicationFilePath(); + workingPath = QDir::currentPath(); + args = qApp->arguments().mid(1); + } + + ~Private() + { + if (restartOnQuit) + QProcess::startDetached(executable, args, workingPath); + } + + QString executable; + QStringList args; + bool restartOnQuit; + QString workingPath; + static KDSelfRestarter *instance; +}; + +KDSelfRestarter *KDSelfRestarter::Private::instance = 0; + +KDSelfRestarter::KDSelfRestarter(int argc, char *argv[]) + : d(new Private(argc, argv)) +{ + Q_ASSERT_X(!Private::instance, Q_FUNC_INFO, "Cannot create more than one KDSelfRestarter instance"); + Private::instance = this; +} + +KDSelfRestarter::~KDSelfRestarter() +{ + Q_ASSERT_X(Private::instance == this, Q_FUNC_INFO, "Cannot create more than one KDSelfRestarter instance"); + delete d; + Private::instance = 0; +} + +void KDSelfRestarter::setRestartOnQuit(bool restart) +{ + Q_ASSERT_X(Private::instance, Q_FUNC_INFO, "KDSelfRestarter instance must be created in main()"); + if (Private::instance) + Private::instance->d->restartOnQuit = restart; +} + +bool KDSelfRestarter::restartOnQuit() +{ + return Private::instance ? Private::instance->d->restartOnQuit : false; +} diff --git a/src/libs/kdtools/kdselfrestarter.h b/src/libs/kdtools/kdselfrestarter.h new file mode 100644 index 000000000..bbf46a2b9 --- /dev/null +++ b/src/libs/kdtools/kdselfrestarter.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_KDSELFRESTARTER_H +#define KDTOOLS_KDSELFRESTARTER_H + +#include "kdtoolsglobal.h" + +class KDTOOLS_EXPORT KDSelfRestarter +{ +public: + KDSelfRestarter(int argc, char *argv[]); + ~KDSelfRestarter(); + + static bool restartOnQuit(); + static void setRestartOnQuit(bool restart); + +private: + Q_DISABLE_COPY(KDSelfRestarter) + class Private; + Private *d; +}; + +#endif // KDTOOLS_KDSELFRESTARTER_H diff --git a/src/libs/kdtools/kdsysinfo.cpp b/src/libs/kdtools/kdsysinfo.cpp new file mode 100644 index 000000000..8e56d01ac --- /dev/null +++ b/src/libs/kdtools/kdsysinfo.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdsysinfo.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> + +using namespace KDUpdater; + +struct PathLongerThan +{ + bool operator()(const VolumeInfo &lhs, const VolumeInfo &rhs) const + { + return lhs.mountPath().length() > rhs.mountPath().length(); + } +}; + +VolumeInfo::VolumeInfo() + : m_size(0) + , m_availableSize(0) +{ +} + +VolumeInfo VolumeInfo::fromPath(const QString &path) +{ + QDir targetPath(QDir::cleanPath(path)); + QList<VolumeInfo> volumes = mountedVolumes(); + + // sort by length to get the longest mount point (not just "/") first + qSort(volumes.begin(), volumes.end(), PathLongerThan()); + foreach (const VolumeInfo &volume, volumes) { + const QDir volumePath(volume.mountPath()); + if (targetPath == volumePath) + return volume; +#ifdef Q_OS_WIN + if (QDir::toNativeSeparators(path).toLower().startsWith(volume.mountPath().toLower())) +#else + // we need to take some care here, as canonical path might return an empty string if the target + // does not exist yet + if (targetPath.exists()) { + // the target exist, we can solve the path and if it fits return + if (targetPath.canonicalPath().startsWith(volume.mountPath())) + return volume; + continue; + } + + // the target directory does not exist yet, we need to cd up till we find the first existing dir + QStringList parts = targetPath.absolutePath().split(QDir::separator(),QString::SkipEmptyParts); + while (targetPath.absolutePath() != QDir::rootPath()) { + if (targetPath.exists()) + break; + parts.pop_back(); + if (parts.isEmpty()) + targetPath = QDir(QDir::rootPath()); + else + targetPath = QDir(parts.join(QDir::separator())); + } + + if (targetPath.canonicalPath().startsWith(volume.mountPath())) +#endif + return volume; + } + return VolumeInfo(); +} + +QString VolumeInfo::mountPath() const +{ + return m_mountPath; +} + +void VolumeInfo::setMountPath(const QString &path) +{ + m_mountPath = path; +} + +QString VolumeInfo::fileSystemType() const +{ + return m_fileSystemType; +} + +void VolumeInfo::setFileSystemType(const QString &type) +{ + m_fileSystemType = type; +} + +QString VolumeInfo::volumeDescriptor() const +{ + return m_volumeDescriptor; +} + +void VolumeInfo::setVolumeDescriptor(const QString &descriptor) +{ + m_volumeDescriptor = descriptor; +} + +quint64 VolumeInfo::size() const +{ + return m_size; +} + +void VolumeInfo::setSize(const quint64 &size) +{ + m_size = size; +} + +quint64 VolumeInfo::availableSize() const +{ + return m_availableSize; +} + +void VolumeInfo::setAvailableSize(const quint64 &available) +{ + m_availableSize = available; +} + +bool VolumeInfo::operator==(const VolumeInfo &other) const +{ + return m_volumeDescriptor == other.m_volumeDescriptor; +} + +QDebug operator<<(QDebug dbg, VolumeInfo volume) +{ + return dbg << "KDUpdater::Volume(" << volume.mountPath() << ")"; +} + +QDebug operator<<(QDebug dbg, ProcessInfo process) +{ + return dbg << "KDUpdater::ProcessInfo(" << process.id << ", " << process.name << ")"; +} diff --git a/src/libs/kdtools/kdsysinfo.h b/src/libs/kdtools/kdsysinfo.h new file mode 100644 index 000000000..6c8079fcd --- /dev/null +++ b/src/libs/kdtools/kdsysinfo.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDSYSINFO_H +#define KDSYSINFO_H + +#include <kdtoolsglobal.h> + +#include <QtCore/QString> + +namespace KDUpdater { + +class KDTOOLS_EXPORT VolumeInfo +{ +public: + VolumeInfo(); + static VolumeInfo fromPath(const QString &path); + + QString mountPath() const; + void setMountPath(const QString &path); + + QString fileSystemType() const; + void setFileSystemType(const QString &type); + + QString volumeDescriptor() const; + void setVolumeDescriptor(const QString &descriptor); + + quint64 size() const; + void setSize(const quint64 &size); + + quint64 availableSize() const; + void setAvailableSize(const quint64 &available); + + bool operator==(const VolumeInfo &other) const; + +private: + QString m_mountPath; + QString m_fileSystemType; + QString m_volumeDescriptor; + + quint64 m_size; + quint64 m_availableSize; +}; + +struct ProcessInfo +{ + quint32 id; + QString name; +}; + +quint64 installedMemory(); +QList<VolumeInfo> mountedVolumes(); +QList<ProcessInfo> runningProcesses(); + +} // namespace KDUpdater + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +QDebug operator<<(QDebug dbg, KDUpdater::VolumeInfo volume); +QDebug operator<<(QDebug dbg, KDUpdater::ProcessInfo process); + +#endif // KDSYSINFO_H diff --git a/src/libs/kdtools/kdsysinfo_mac.cpp b/src/libs/kdtools/kdsysinfo_mac.cpp new file mode 100644 index 000000000..d039f8975 --- /dev/null +++ b/src/libs/kdtools/kdsysinfo_mac.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdsysinfo.h" + +#include <Carbon/Carbon.h> + +#include <sys/mount.h> +#include <sys/sysctl.h> +#include <sys/types.h> + +#include <QtCore/QList> + +namespace KDUpdater { + +quint64 installedMemory() +{ + SInt32 mb = 0; + Gestalt(gestaltPhysicalRAMSizeInMegabytes, &mb); + return quint64(static_cast<quint64>(mb) * 1024LL * 1024LL); +} + +QList<VolumeInfo> mountedVolumes() +{ + QList<VolumeInfo> result; + FSVolumeRefNum volume; + FSVolumeInfo info; + HFSUniStr255 volName; + FSRef ref; + int i = 0; + + while (FSGetVolumeInfo(kFSInvalidVolumeRefNum, ++i, &volume, kFSVolInfoFSInfo, &info, &volName, &ref) == 0) { + UInt8 path[PATH_MAX + 1]; + if (FSRefMakePath(&ref, path, PATH_MAX) == 0) { + FSGetVolumeInfo(volume, 0, 0, kFSVolInfoSizes, &info, 0, 0); + + VolumeInfo v; + v.setSize(quint64(info.totalBytes)); + v.setAvailableSize(quint64(info.freeBytes)); + v.setMountPath(QString::fromLocal8Bit(reinterpret_cast< char* >(path))); + + struct statfs data; + if (statfs(qPrintable(v.mountPath() + QLatin1String("/.")), &data) == 0) { + v.setFileSystemType(QLatin1String(data.f_fstypename)); + v.setVolumeDescriptor(QLatin1String(data.f_mntfromname)); + } + result.append(v); + } + } + return result; +} + +QList<ProcessInfo> runningProcesses() +{ + int mib[4] = { + CTL_KERN, + KERN_ARGMAX, + 0, + 0 + }; + + int argMax = 0; + size_t argMaxSize = sizeof(argMax); + // fetch the maximum process arguments size + sysctl(mib, 2, &argMax, &argMaxSize, NULL, 0); + char *processArguments = (char*) malloc(argMax); + + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ALL; + size_t processTableSize = 0; + // fetch the kernel process table size + sysctl(mib, 4, NULL, &processTableSize, NULL, 0); + struct kinfo_proc *processTable = (kinfo_proc*) malloc(processTableSize); + + // fetch the process table + sysctl(mib, 4, processTable, &processTableSize, NULL, 0); + + QList<ProcessInfo> processes; + for (size_t i = 0; i < (processTableSize / sizeof(struct kinfo_proc)); ++i) { + struct kinfo_proc *process = processTable + i; + + ProcessInfo processInfo; + processInfo.id = process->kp_proc.p_pid; + + mib[1] = KERN_PROCARGS2; + mib[2] = process->kp_proc.p_pid; + mib[3] = 0; + + size_t size = argMax; + // fetch the process arguments + if (sysctl(mib, 3, processArguments, &size, NULL, 0) != -1) { + /* + * |-----------------| <-- data returned by sysctl() + * | argc | + * |-----------------| + * | executable path | + * |-----------------| + * | arguments | + * ~~~~~~~~~~~~~~~~~~~ + * |-----------------| + */ + processInfo.name = QString::fromLocal8Bit(processArguments + sizeof(int)); + } else { + // if we fail, use the name from the process table + processInfo.name = QString::fromLocal8Bit(process->kp_proc.p_comm); + } + processes.append(processInfo); + } + free(processTable); + free(processArguments); + + return processes; +} + +} // namespace KDUpdater diff --git a/src/libs/kdtools/kdsysinfo_win.cpp b/src/libs/kdtools/kdsysinfo_win.cpp new file mode 100644 index 000000000..45342aee5 --- /dev/null +++ b/src/libs/kdtools/kdsysinfo_win.cpp @@ -0,0 +1,212 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdsysinfo.h" + +#include <windows.h> +#include <Psapi.h> +#include <Tlhelp32.h> + +#include <Winnetwk.h> +#pragma comment(lib, "mpr.lib") + +#include <QtCore/QDir> +#include <QtCore/QLibrary> + +const int KDSYSINFO_PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; + +namespace KDUpdater { + +quint64 installedMemory() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return quint64(status.ullTotalPhys); +} + +VolumeInfo updateVolumeSizeInformation(const VolumeInfo &info) +{ + ULARGE_INTEGER bytesTotal; + ULARGE_INTEGER freeBytesPerUser; + + VolumeInfo update = info; + if (GetDiskFreeSpaceExA(qPrintable(info.volumeDescriptor()), &freeBytesPerUser, &bytesTotal, NULL)) { + update.setSize(bytesTotal.QuadPart); + update.setAvailableSize(freeBytesPerUser.QuadPart); + } + return update; +} + +/*! + Returns a list of volume info objects that are mounted as network drive shares. +*/ +QList<VolumeInfo> networkVolumeInfosFromMountPoints() +{ + QList<VolumeInfo> volumes; + QFileInfoList drives = QDir::drives(); + foreach (const QFileInfo &drive, drives) { + const QString driveLetter = QDir::toNativeSeparators(drive.canonicalPath()); + const uint driveType = GetDriveTypeA(qPrintable(driveLetter)); + switch (driveType) { + case DRIVE_REMOTE: { + char buffer[1024] = ""; + DWORD bufferLength = 1024; + UNIVERSAL_NAME_INFOA *universalNameInfo = (UNIVERSAL_NAME_INFOA*) &buffer; + if (WNetGetUniversalNameA(qPrintable(driveLetter), UNIVERSAL_NAME_INFO_LEVEL, + LPVOID(universalNameInfo), &bufferLength) == NO_ERROR) { + VolumeInfo info; + info.setMountPath(driveLetter); + info.setVolumeDescriptor(QLatin1String(universalNameInfo->lpUniversalName)); + volumes.append(info); + } + } break; + + default: + break; + } + } + return volumes; +} + +/*! + Returns a list of volume info objects based on the given \a volumeGUID. The function also solves mounted + volume folder paths. It does not return any network drive shares. +*/ +QList<VolumeInfo> localVolumeInfosFromMountPoints(const QByteArray &volumeGUID) +{ + QList<VolumeInfo> volumes; + DWORD bufferSize; + char volumeNames[1024] = ""; + if (GetVolumePathNamesForVolumeNameA(volumeGUID, volumeNames, ARRAYSIZE(volumeNames), &bufferSize)) { + QStringList mountedPaths = QString::fromLatin1(volumeNames, bufferSize).split(QLatin1Char(char(0)), + QString::SkipEmptyParts); + foreach (const QString &mountedPath, mountedPaths) { + VolumeInfo info; + info.setMountPath(mountedPath); + info.setVolumeDescriptor(QString::fromLatin1(volumeGUID)); + volumes.append(info); + } + } + return volumes; +} + +QList<VolumeInfo> mountedVolumes() +{ + QList<VolumeInfo> tmp; + char volumeGUID[MAX_PATH] = ""; + HANDLE handle = FindFirstVolumeA(volumeGUID, ARRAYSIZE(volumeGUID)); + if (handle != INVALID_HANDLE_VALUE) { + tmp += localVolumeInfosFromMountPoints(volumeGUID); + while (FindNextVolumeA(handle, volumeGUID, ARRAYSIZE(volumeGUID))) { + tmp += localVolumeInfosFromMountPoints(volumeGUID); + } + FindVolumeClose(handle); + } + tmp += networkVolumeInfosFromMountPoints(); + + QList<VolumeInfo> volumes; + while (!tmp.isEmpty()) // update volume size information + volumes.append(updateVolumeSizeInformation(tmp.takeFirst())); + return volumes; +} + +struct EnumWindowsProcParam +{ + QList<ProcessInfo> processes; + QList<quint32> seenIDs; +}; + +typedef BOOL (WINAPI *QueryFullProcessImageNamePtr)(HANDLE, DWORD, char *, PDWORD); +typedef DWORD (WINAPI *GetProcessImageFileNamePtr)(HANDLE, char *, DWORD); + +QList<ProcessInfo> runningProcesses() +{ + EnumWindowsProcParam param; + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (!snapshot) + return param.processes; + PROCESSENTRY32 processStruct; + processStruct.dwSize = sizeof(PROCESSENTRY32); + bool foundProcess = Process32First(snapshot, &processStruct); + const DWORD bufferSize = 1024; + char driveBuffer[bufferSize]; + QStringList deviceList; + if (QSysInfo::windowsVersion() <= QSysInfo::WV_5_2) { + DWORD size = GetLogicalDriveStringsA(bufferSize, driveBuffer); + deviceList = QString::fromLatin1(driveBuffer, size).split(QLatin1Char(char(0)), QString::SkipEmptyParts); + } + + QLibrary kernel32(QLatin1String("Kernel32.dll")); + kernel32.load(); + void *pQueryFullProcessImageNameA = kernel32.resolve("QueryFullProcessImageNameA"); + + QLibrary psapi(QLatin1String("Psapi.dll")); + psapi.load(); + void *pGetProcessImageFileNamePtr = psapi.resolve("GetProcessImageFileNameA"); + QueryFullProcessImageNamePtr callPtr = (QueryFullProcessImageNamePtr) pQueryFullProcessImageNameA; + GetProcessImageFileNamePtr callPtrXp = (GetProcessImageFileNamePtr) pGetProcessImageFileNamePtr; + + while (foundProcess) { + HANDLE procHandle = OpenProcess(QSysInfo::windowsVersion() > QSysInfo::WV_5_2 + ? KDSYSINFO_PROCESS_QUERY_LIMITED_INFORMATION + : PROCESS_QUERY_INFORMATION, + false, + processStruct.th32ProcessID); + char buffer[1024]; + DWORD bufferSize = 1024; + bool succ = false; + QString executablePath; + ProcessInfo info; + + if (QSysInfo::windowsVersion() > QSysInfo::WV_5_2) { + succ = callPtr(procHandle, 0, buffer, &bufferSize); + executablePath = QString::fromLatin1(buffer); + } else if (pGetProcessImageFileNamePtr) { + succ = callPtrXp(procHandle, buffer, bufferSize); + executablePath = QString::fromLatin1(buffer); + for (int i = 0; i < deviceList.count(); ++i) { + executablePath.replace(QString::fromLatin1( "\\Device\\HarddiskVolume%1\\" ).arg(i + 1), + deviceList.at(i)); + } + } + + if (succ) { + const quint32 pid = processStruct.th32ProcessID; + param.seenIDs.append(pid); + info.id = pid; + info.name = executablePath; + param.processes.append(info); + } + + CloseHandle(procHandle); + foundProcess = Process32Next(snapshot, &processStruct); + + } + if (snapshot) + CloseHandle(snapshot); + + kernel32.unload(); + return param.processes; +} + +} // namespace KDUpdater diff --git a/src/libs/kdtools/kdsysinfo_x11.cpp b/src/libs/kdtools/kdsysinfo_x11.cpp new file mode 100644 index 000000000..f6e2c8ced --- /dev/null +++ b/src/libs/kdtools/kdsysinfo_x11.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdsysinfo.h" + +#include <sys/utsname.h> +#include <sys/statvfs.h> + +#include <QtCore/QFile> +#include <QtCore/QTextStream> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> + +namespace KDUpdater { + +quint64 installedMemory() +{ +#ifdef Q_OS_LINUX + QFile f(QLatin1String("/proc/meminfo")); + f.open(QIODevice::ReadOnly); + QTextStream stream(&f); + while (true) { + const QString s = stream.readLine(); + if( !s.startsWith(QLatin1String("MemTotal:" ))) + continue; + else if (s.isEmpty()) + return quint64(); + + const QStringList parts = s.split(QLatin1Char(' '), QString::SkipEmptyParts); + return quint64(parts.at(1).toInt() * 1024LL); + } +#else + quint64 physmem; + size_t len = sizeof physmem; + static int mib[2] = { CTL_HW, HW_MEMSIZE }; + sysctl(mib, 2, &physmem, &len, 0, 0); + return quint64(physmem); +#endif + return 0; +} + +QList<VolumeInfo> mountedVolumes() +{ + QList<VolumeInfo> result; + + QFile f(QLatin1String("/etc/mtab")); + if (!f.open(QIODevice::ReadOnly)) { + qCritical("%s: Could not open %s: %s", Q_FUNC_INFO, qPrintable(f.fileName()), qPrintable(f.errorString())); + return result; //better error-handling? + } + + QTextStream stream(&f); + while (true) { + const QString s = stream.readLine(); + if (s.isNull()) + return result; + + if (!s.startsWith(QLatin1Char('/'))) + continue; + + const QStringList parts = s.split(QLatin1Char(' '), QString::SkipEmptyParts); + + VolumeInfo v; + v.setMountPath(parts.at(1)); + v.setVolumeDescriptor(parts.at(0)); + v.setFileSystemType(parts.value(2)); + + struct statvfs data; + if (statvfs(qPrintable(v.mountPath() + QLatin1String("/.")), &data) == 0) { + v.setSize(quint64(static_cast<quint64>(data.f_blocks) * data.f_bsize)); + v.setAvailableSize(quint64(static_cast<quint64>(data.f_bavail) * data.f_bsize)); + } + result.append(v); + } + return result; +} + +QList<ProcessInfo> runningProcesses() +{ + QList<ProcessInfo> processes; + QDir procDir(QLatin1String("/proc")); + const QFileInfoList procCont = procDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable); + QRegExp validator(QLatin1String("[0-9]+")); + Q_FOREACH (const QFileInfo &info, procCont) { + if (validator.exactMatch(info.fileName())) { + const QString linkPath = QDir(info.absoluteFilePath()).absoluteFilePath(QLatin1String("exe")); + const QFileInfo linkInfo(linkPath); + if (linkInfo.exists()) { + ProcessInfo processInfo; + processInfo.name = linkInfo.symLinkTarget(); + processInfo.id = info.fileName().toInt(); + processes.append(processInfo); + } + } + } + return processes; +} + +} // namespace KDUpdater diff --git a/src/libs/kdtools/kdtools.pri b/src/libs/kdtools/kdtools.pri new file mode 100644 index 000000000..8b14def9a --- /dev/null +++ b/src/libs/kdtools/kdtools.pri @@ -0,0 +1,60 @@ +DEPENDPATH += $$PWD +INCLUDEPATH += $$PWD + +CONFIG(shared, static|shared) { + DEFINES += BUILD_SHARED_KDTOOLS +} + +HEADERS += $$PWD/kdtoolsglobal.h \ + $$PWD/kdjob.h \ + $$PWD/kdgenericfactory.h \ + $$PWD/kdselfrestarter.h \ + $$PWD/kdsavefile.h \ + $$PWD/kdrunoncechecker.h \ + $$PWD/kdlockfile.h \ + $$PWD/kdsysinfo.h + +SOURCES += $$PWD/kdjob.cpp \ + $$PWD/kdgenericfactory.cpp \ + $$PWD/kdselfrestarter.cpp \ + $$PWD/kdsavefile.cpp \ + $$PWD/kdrunoncechecker.cpp \ + $$PWD/kdlockfile.cpp \ + $$PWD/kdsysinfo.cpp + + +HEADERS += $$PWD/kdupdater.h \ + $$PWD/kdupdaterapplication.h \ + $$PWD/kdupdaterfiledownloader.h \ + $$PWD/kdupdaterfiledownloader_p.h \ + $$PWD/kdupdaterfiledownloaderfactory.h \ + $$PWD/kdupdaterpackagesinfo.h \ + $$PWD/kdupdaterupdate.h \ + $$PWD/kdupdaterupdateoperation.h \ + $$PWD/kdupdaterupdateoperationfactory.h \ + $$PWD/kdupdaterupdateoperations.h \ + $$PWD/kdupdaterupdatesourcesinfo.h \ + $$PWD/kdupdatertask.h \ + $$PWD/kdupdaterupdatefinder.h \ + $$PWD/kdupdaterupdatesinfo_p.h \ + $$PWD/environment.h + +SOURCES += $$PWD/kdupdaterapplication.cpp \ + $$PWD/kdupdaterfiledownloader.cpp \ + $$PWD/kdupdaterfiledownloaderfactory.cpp \ + $$PWD/kdupdaterpackagesinfo.cpp \ + $$PWD/kdupdaterupdate.cpp \ + $$PWD/kdupdaterupdateoperation.cpp \ + $$PWD/kdupdaterupdateoperationfactory.cpp \ + $$PWD/kdupdaterupdateoperations.cpp \ + $$PWD/kdupdaterupdatesourcesinfo.cpp \ + $$PWD/kdupdatertask.cpp \ + $$PWD/kdupdaterupdatefinder.cpp \ + $$PWD/kdupdaterupdatesinfo.cpp \ + $$PWD/environment.cpp + +unix:SOURCES += $$PWD/kdlockfile_unix.cpp +win32:SOURCES += $$PWD/kdlockfile_win.cpp +win32:SOURCES += $$PWD/kdsysinfo_win.cpp +macx:SOURCES += $$PWD/kdsysinfo_mac.cpp +unix:!macx:SOURCES += $$PWD/kdsysinfo_x11.cpp diff --git a/src/libs/kdtools/kdtoolsglobal.h b/src/libs/kdtools/kdtoolsglobal.h new file mode 100644 index 000000000..8199724c4 --- /dev/null +++ b/src/libs/kdtools/kdtoolsglobal.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDTOOLS_KDTOOLSGLOBAL_H +#define KDTOOLS_KDTOOLSGLOBAL_H + +#include <QtCore/QtGlobal> + +#ifdef KDTOOLS_SHARED +# ifdef BUILD_SHARED_KDTOOLS +# define KDTOOLS_EXPORT Q_DECL_EXPORT +# else +# define KDTOOLS_EXPORT Q_DECL_IMPORT +# endif +#else // KDTOOLS_SHARED +# define KDTOOLS_EXPORT +#endif // KDTOOLS_SHARED + +#endif // KDTOOLS_KDTOOLSGLOBAL_H + diff --git a/src/libs/kdtools/kdupdater.h b/src/libs/kdtools/kdupdater.h new file mode 100644 index 000000000..f28461d9b --- /dev/null +++ b/src/libs/kdtools/kdupdater.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_H +#define KD_UPDATER_H + +#include <kdtoolsglobal.h> + +namespace KDUpdater +{ + enum Error + { + ENoError = 0, + ECannotStartTask, + ECannotPauseTask, + ECannotResumeTask, + ECannotStopTask, + EUnknown + }; + + enum UpdateType { + PackageUpdate = 0x1, + CompatUpdate = 0x2, + NewPackage = 0x4, + AllUpdate = PackageUpdate | CompatUpdate + }; + Q_DECLARE_FLAGS( UpdateTypes, UpdateType ) + Q_DECLARE_OPERATORS_FOR_FLAGS( UpdateTypes ) + + KDTOOLS_EXPORT int compareVersion(const QString &v1, const QString &v2); +} + +#endif diff --git a/src/libs/kdtools/kdupdaterapplication.cpp b/src/libs/kdtools/kdupdaterapplication.cpp new file mode 100644 index 000000000..8506eca46 --- /dev/null +++ b/src/libs/kdtools/kdupdaterapplication.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterapplication.h" +#include "kdupdaterpackagesinfo.h" +#include "kdupdaterupdatesourcesinfo.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QDir> +#include <QSettings> + +using namespace KDUpdater; + +/*! + \defgroup kdupdater KD Updater + \since_l 2.1 + + "KD Updater" is a library from KDAB that helps in enabling automatic updates for your applications. + All classes belonging to the "KD Updater" library are defined in the \ref KDUpdater namespace. + + TODO: this comes from the former mainpage: +KD Updater is a tool to automatically detect, retrieve, install and activate updates to software +applications and libraries. It is intended to be used with Qt based applications, and developed +against the Qt 4 series. It is a library that users link to their application. It uses only accepted +standard protocols, and does not require any other 3rd party libraries that are not shipped with +Qt. + +KD Updater is generic in that it is not developed for one specific application. The first version is +experimental. If it proves successful and useful, it will be integrated into KDAB's KD Tools +package. It is part of KDAB's strategy to provide functionality missing in Qt that is required for +medium-to-large scale software systems. +*/ + +/*! + \namespace KDUpdater +*/ + +ConfigurationInterface::~ConfigurationInterface() +{ +} + +namespace { + +class DefaultConfigImpl : public ConfigurationInterface +{ +public: + QVariant value(const QString &key) const + { + QSettings settings; + settings.beginGroup(QLatin1String("KDUpdater")); + return settings.value(key); + } + + void setValue(const QString &key, const QVariant &value) + { + QSettings settings; + settings.beginGroup(QLatin1String("KDUpdater")); + settings.setValue(key, value); + } +}; + +} // namespace anon + +/*! + \class KDUpdater::Application kdupdaterapplication.h KDUpdaterApplication + \ingroup kdupdater + \brief This class represents an application that can be updated. + + A KDUpdater application is an application that needs to interact with one or more update servers and + downloads/installs updates This class helps in describing an application in terms of: + \li application Directory + \li packages XML file name and its corresponding KDUpdater::PackagesInfo object + \li update Sources XML file name and its corresponding KDUpdater::UpdateSourcesInfo object + + User can also retrieve some informations from this class: + \li application name + \li application version + \li compat level +*/ + +struct Application::ApplicationData +{ + explicit ApplicationData(ConfigurationInterface *config) : + packagesInfo(0), + updateSourcesInfo(0), + configurationInterface(config ? config : new DefaultConfigImpl) + { + const QStringList oldFiles = configurationInterface->value(QLatin1String("FilesForDelayedDeletion")).toStringList(); + Q_FOREACH(const QString &i, oldFiles) { //TODO this should happen asnyc and report errors, I guess + QFile f(i); + if (f.exists() && !f.remove()) { + qWarning("Could not delete file %s: %s", qPrintable(i), qPrintable(f.errorString())); + filesForDelayedDeletion << i; // try again next time + } + } + configurationInterface->setValue(QLatin1String("FilesForDelayedDeletion"), filesForDelayedDeletion); + } + + ~ApplicationData() + { + delete packagesInfo; + delete updateSourcesInfo; + delete configurationInterface; + } + + static Application *instance; + + QString applicationDirectory; + PackagesInfo *packagesInfo; + UpdateSourcesInfo *updateSourcesInfo; + QStringList filesForDelayedDeletion; + ConfigurationInterface *configurationInterface; +}; + +Application *Application::ApplicationData::instance = 0; + +/*! + Constructor of the Application class. The class will be constructed and configured to + assume the application directory to be the directory in which the application exists. The + application name is assumed to be QCoreApplication::applicationName() +*/ +Application::Application(ConfigurationInterface* config, QObject* p) : QObject(p) +{ + d = new Application::ApplicationData( config ); + d->packagesInfo = new PackagesInfo(this); + d->updateSourcesInfo = new UpdateSourcesInfo(this); + + setApplicationDirectory( QCoreApplication::applicationDirPath() ); + + ApplicationData::instance = this; +} + +/*! + Destructor +*/ +Application::~Application() +{ + if (this == ApplicationData::instance) + ApplicationData::instance = 0; + delete d; +} + +/*! + Returns a previousle created Application instance. + */ +Application *Application::instance() +{ + return ApplicationData::instance; +} + +/*! + Changes the applicationDirPath directory to \c dir. Packages.xml and UpdateSources.xml found in the new + application directory will be used. +*/ +void Application::setApplicationDirectory(const QString &dir) +{ + if (d->applicationDirectory == dir) + return; + + QDir dirObj(dir); + + // FIXME: Perhaps we should check whether dir exists on the local file system or not + d->applicationDirectory = dirObj.absolutePath(); + setPackagesXMLFileName(QString::fromLatin1("%1/Packages.xml").arg(dir)); + setUpdateSourcesXMLFileName(QString::fromLatin1("%1/UpdateSources.xml").arg(dir)); +} + +/*! + Returns path to the application directory. +*/ +QString Application::applicationDirectory() const +{ + return d->applicationDirectory; +} + +/*! + Returns the application name. +*/ +QString Application::applicationName() const +{ + if (d->packagesInfo->isValid()) + return d->packagesInfo->applicationName(); + + return QCoreApplication::applicationName(); +} + +/*! + Returns the application version. +*/ +QString Application::applicationVersion() const +{ + if (d->packagesInfo->isValid()) + return d->packagesInfo->applicationVersion(); + + return QString(); +} + +/*! + Returns the compat level that this application is in. +*/ +int Application::compatLevel() const +{ + if (d->packagesInfo->isValid()) + return d->packagesInfo->compatLevel(); + + return -1; +} + +void Application::addUpdateSource(const QString &name, const QString &title, + const QString &description, const QUrl &url, int priority) +{ + UpdateSourceInfo info; + info.name = name; + info.title = title; + info.description = description; + info.url = url; + info.priority = priority; + d->updateSourcesInfo->addUpdateSourceInfo(info); +} + + +/*! + Sets the file name of the Package XML file for this application. By default this is assumed to be + Packages.xml in the application directory. + + \sa KDUpdater::PackagesInfo::setFileName() +*/ +void Application::setPackagesXMLFileName(const QString &fileName) +{ + d->packagesInfo->setFileName(fileName); +} + +/*! + Returns the Package XML file name. +*/ +QString Application::packagesXMLFileName() const +{ + return d->packagesInfo->fileName(); +} + +/*! + Returns the \ref PackagesInfo object associated with this application. +*/ +PackagesInfo* Application::packagesInfo() const +{ + return d->packagesInfo; +} + +/*! + Sets the file name of the Package XML file for this application. By default this is assumed to be + Packages.xml in the application directory. + + \sa KDUpdater::UpdateSourcesInfo::setFileName() +*/ +void Application::setUpdateSourcesXMLFileName(const QString &fileName) +{ + d->updateSourcesInfo->setFileName(fileName); +} + +/*! + Returns the Update Sources XML file name. +*/ +QString Application::updateSourcesXMLFileName() const +{ + return d->updateSourcesInfo->fileName(); +} + +/*! + Returns the \ref UpdateSourcesInfo object associated with this application. +*/ +UpdateSourcesInfo* Application::updateSourcesInfo() const +{ + return d->updateSourcesInfo; +} + +void Application::printError(int errorCode, const QString &error) +{ + qDebug() << errorCode << error; +} + +QStringList Application::filesForDelayedDeletion() const +{ + return d->filesForDelayedDeletion; +} + +void Application::addFilesForDelayedDeletion(const QStringList &files) +{ + d->filesForDelayedDeletion << files; + d->configurationInterface->setValue(QLatin1String("FilesForDelayedDeletion"), d->filesForDelayedDeletion); +} diff --git a/src/libs/kdtools/kdupdaterapplication.h b/src/libs/kdtools/kdupdaterapplication.h new file mode 100644 index 000000000..f1ca9d612 --- /dev/null +++ b/src/libs/kdtools/kdupdaterapplication.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_APPLICATION_H +#define KD_UPDATER_APPLICATION_H + +#include "kdupdater.h" +#include <QObject> + +QT_BEGIN_NAMESPACE +class QUrl; +QT_END_NAMESPACE + +namespace KDUpdater { + +class PackagesInfo; +class UpdateSourcesInfo; + +class ConfigurationInterface +{ +public: + virtual ~ConfigurationInterface(); + virtual QVariant value(const QString &key ) const = 0; + virtual void setValue(const QString &key, const QVariant &value) = 0; +}; + +class KDTOOLS_EXPORT Application : public QObject +{ + Q_OBJECT + +public: + explicit Application(ConfigurationInterface *config = 0, QObject *parent = 0); + ~Application(); + + static Application *instance(); + + void setApplicationDirectory(const QString &dir); + QString applicationDirectory() const; + + QString applicationName() const; + QString applicationVersion() const; + int compatLevel() const; + + void setPackagesXMLFileName(const QString &fileName); + QString packagesXMLFileName() const; + PackagesInfo *packagesInfo() const; + + void addUpdateSource(const QString &name, const QString &title, + const QString &description, const QUrl &url, int priority = -1); + + void setUpdateSourcesXMLFileName(const QString &fileName); + QString updateSourcesXMLFileName() const; + UpdateSourcesInfo *updateSourcesInfo() const; + + QStringList filesForDelayedDeletion() const; + void addFilesForDelayedDeletion(const QStringList &files); + +public Q_SLOTS: + void printError(int errorCode, const QString &error); + +private: + struct ApplicationData; + ApplicationData *d; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_APPLICATION_H diff --git a/src/libs/kdtools/kdupdaterfiledownloader.cpp b/src/libs/kdtools/kdupdaterfiledownloader.cpp new file mode 100644 index 000000000..c36b78402 --- /dev/null +++ b/src/libs/kdtools/kdupdaterfiledownloader.cpp @@ -0,0 +1,1294 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterfiledownloader_p.h" +#include "kdupdaterfiledownloaderfactory.h" + +#include <QFile> +#include <QFtp> +#include <QNetworkAccessManager> +#include <QNetworkProxyFactory> +#include <QPointer> +#include <QUrl> +#include <QTemporaryFile> +#include <QFileInfo> +#include <QCryptographicHash> +#include <QThreadPool> +#include <QDebug> + +#include <QBasicTimer> +#include <QTimerEvent> + +using namespace KDUpdater; + +static double calcProgress(qint32 done, qint32 total) +{ + return total ? (double(done) / double(total)) : 0; +} + +static QString format(double data) +{ + if (data < 1024.0) + return KDUpdater::FileDownloader::tr("%L1 B").arg(data); + data /= 1024.0; + if (data < 1024.0) + return KDUpdater::FileDownloader::tr("%L1 KB").arg(data, 0, 'f', 2); + data /= 1024.0; + if (data < 1024.0) + return KDUpdater::FileDownloader::tr("%L1 MB").arg(data, 0, 'f', 2); + data /= 1024.0; + return KDUpdater::FileDownloader::tr("%L1 GB").arg(data, 0, 'f', 2); +} + +QByteArray KDUpdater::calculateHash(QIODevice* device, QCryptographicHash::Algorithm algo) +{ + Q_ASSERT(device); + QCryptographicHash hash(algo); + QByteArray buffer; + buffer.resize(512 * 1024); + while (true) { + const qint64 numRead = device->read(buffer.data(), buffer.size()); + if (numRead <= 0) + return hash.result(); + hash.addData(buffer.constData(), numRead); + } + return QByteArray(); // never reached +} + +QByteArray KDUpdater::calculateHash(const QString &path, QCryptographicHash::Algorithm algo) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) + return QByteArray(); + return calculateHash(&file, algo); +} + + +// -- HashVerificationJob + +class HashVerificationJob::Private +{ +public: + Private() + : hash(QCryptographicHash::Sha1) + , error(HashVerificationJob::ReadError) + , timerId(-1) { } + + QPointer<QIODevice> device; + QByteArray sha1Sum; + QCryptographicHash hash; + HashVerificationJob::Error error; + int timerId; +}; + +HashVerificationJob::HashVerificationJob(QObject* parent) + : QObject(parent) + , d(new Private) +{ +} + +HashVerificationJob::~HashVerificationJob() +{ + delete d; +} + +void HashVerificationJob::setDevice(QIODevice* dev) +{ + d->device = dev; +} + +void HashVerificationJob::setSha1Sum(const QByteArray &sum) +{ + d->sha1Sum = sum; +} + +int HashVerificationJob::error() const +{ + return d->error; +} + +bool HashVerificationJob::hasError() const +{ + return d->error != NoError; +} + +void HashVerificationJob::start() +{ + Q_ASSERT(d->device); + d->timerId = startTimer(0); +} + +void HashVerificationJob::emitFinished() +{ + emit finished(this); + deleteLater(); +} + +void HashVerificationJob::timerEvent(QTimerEvent*) +{ + Q_ASSERT(d->timerId >= 0); + if (d->sha1Sum.isEmpty()) { + killTimer(d->timerId); + d->timerId = -1; + d->error = NoError; + d->device->close(); + emitFinished(); + return; + } + + QByteArray buf; + buf.resize(128 * 1024); + const qint64 read = d->device->read(buf.data(), buf.size()); + if (read > 0) { + d->hash.addData(buf.constData(), read); + return; + } + + d->error = d->hash.result() == d->sha1Sum ? NoError : SumsDifferError; + killTimer(d->timerId); + d->timerId = -1; + emitFinished(); +} + + +// -- KDUpdater::FileDownloader + +/*! + \internal + \ingroup kdupdater + \class KDUpdater::FileDownloader kdupdaterfiledownloader.h + + Base class for file downloaders used in KDUpdater. File downloaders are used by + the KDUpdater::Update class to download update files. Each subclass of FileDownloader + can download file from a specific category of sources (e.g. local, ftp, http etc). + + This is an internal class, not a part of the public API. Currently we have three + subclasses of FileDownloader + \li LocalFileDownloader - downloads from the local file system + \li FtpDownloader - downloads from a FTP site + \li HttpDownloader - downloads from a HTTP site + + Usage + + \code + KDUpdater::FileDownloader* downloader = new KDUpdater::(some subclass name) + + downloader->setUrl(url); + downloader->download(); + + // wait for downloadCompleted() signal + + QString downloadedFile = downloader->downloadedFileName(); + \endcode +*/ + +struct KDUpdater::FileDownloader::Private +{ + Private() + : autoRemove(true) + , m_speedTimerInterval(100) + , m_bytesReceived(0) + , m_bytesToReceive(0) + , m_currentSpeedBin(0) + , m_sampleIndex(0) + , m_downloadSpeed(0) + , m_factory(0) + { + memset(m_samples, 0, sizeof(m_samples)); + } + + ~Private() + { + delete m_factory; + } + + QUrl url; + QString scheme; + QByteArray sha1Sum; + QString errorString; + bool autoRemove; + bool followRedirect; + + QBasicTimer m_timer; + int m_speedTimerInterval; + + qint64 m_bytesReceived; + qint64 m_bytesToReceive; + + mutable qint64 m_samples[50]; + mutable qint64 m_currentSpeedBin; + mutable quint32 m_sampleIndex; + mutable qint64 m_downloadSpeed; + + QAuthenticator m_authenticator; + FileDownloaderProxyFactory *m_factory; +}; + +KDUpdater::FileDownloader::FileDownloader(const QString &scheme, QObject *parent) + : QObject(parent) + , d(new Private) +{ + d->scheme = scheme; + d->followRedirect = false; +} + +KDUpdater::FileDownloader::~FileDownloader() +{ + delete d; +} + +void KDUpdater::FileDownloader::setUrl(const QUrl &url) +{ + d->url = url; +} + +QUrl KDUpdater::FileDownloader::url() const +{ + return d->url; +} + +void KDUpdater::FileDownloader::setSha1Sum(const QByteArray &sum) +{ + d->sha1Sum = sum; +} + +QByteArray KDUpdater::FileDownloader::sha1Sum() const +{ + return d->sha1Sum; +} + +QString FileDownloader::errorString() const +{ + return d->errorString; +} + +void FileDownloader::setDownloadAborted(const QString &error) +{ + d->errorString = error; + emit downloadStatus(error); + emit downloadAborted(error); +} + +void KDUpdater::FileDownloader::setDownloadCompleted(const QString &path) +{ + HashVerificationJob *job = new HashVerificationJob; + QFile *file = new QFile(path, job); + if (!file->open(QIODevice::ReadOnly)) { + emit downloadProgress(1); + onError(); + setDownloadAborted(tr("Could not reopen downloaded file %1 for reading: %2").arg(path, + file->errorString())); + delete job; + return; + } + + job->setDevice(file); + job->setSha1Sum(d->sha1Sum); + connect(job, SIGNAL(finished(KDUpdater::HashVerificationJob*)), this, + SLOT(sha1SumVerified(KDUpdater::HashVerificationJob*))); + job->start(); +} + +void KDUpdater::FileDownloader::setDownloadCanceled() +{ + emit downloadCanceled(); + emit downloadStatus(tr("Download canceled.")); +} + +void KDUpdater::FileDownloader::sha1SumVerified(KDUpdater::HashVerificationJob *job) +{ + if (job->hasError()) { + onError(); + setDownloadAborted(tr("Cryptographic hashes do not match.")); + } else { + onSuccess(); + emit downloadCompleted(); + emit downloadStatus(tr("Download finished.")); + } +} + +QString KDUpdater::FileDownloader::scheme() const +{ + return d->scheme; +} + +void KDUpdater::FileDownloader::setAutoRemoveDownloadedFile(bool val) +{ + d->autoRemove = val; +} + +void KDUpdater::FileDownloader::setFollowRedirects(bool val) +{ + d->followRedirect = val; +} + +bool KDUpdater::FileDownloader::followRedirects() const +{ + return d->followRedirect; +} + +bool KDUpdater::FileDownloader::isAutoRemoveDownloadedFile() const +{ + return d->autoRemove; +} + +void KDUpdater::FileDownloader::download() +{ + QMetaObject::invokeMethod(this, "doDownload", Qt::QueuedConnection); +} + +void KDUpdater::FileDownloader::cancelDownload() +{ + // Do nothing +} + +void KDUpdater::FileDownloader::runDownloadSpeedTimer() +{ + if (!d->m_timer.isActive()) + d->m_timer.start(d->m_speedTimerInterval, this); +} + +void KDUpdater::FileDownloader::stopDownloadSpeedTimer() +{ + d->m_timer.stop(); +} + +void KDUpdater::FileDownloader::addSample(qint64 sample) +{ + d->m_currentSpeedBin += sample; +} + +int KDUpdater::FileDownloader::downloadSpeedTimerId() const +{ + return d->m_timer.timerId(); +} + +void KDUpdater::FileDownloader::setProgress(qint64 bytesReceived, qint64 bytesToReceive) +{ + d->m_bytesReceived = bytesReceived; + d->m_bytesToReceive = bytesToReceive; +} + +void KDUpdater::FileDownloader::emitDownloadSpeed() +{ + unsigned int windowSize = sizeof(d->m_samples) / sizeof(qint64); + + // add speed of last time bin to the window + d->m_samples[d->m_sampleIndex % windowSize] = d->m_currentSpeedBin; + d->m_currentSpeedBin = 0; // reset bin for next time interval + + // advance the sample index + d->m_sampleIndex++; + d->m_downloadSpeed = 0; + + // dynamic window size until the window is completely filled + if (d->m_sampleIndex < windowSize) + windowSize = d->m_sampleIndex; + + for (unsigned int i = 0; i < windowSize; ++i) + d->m_downloadSpeed += d->m_samples[i]; + + d->m_downloadSpeed /= windowSize; // computer average + d->m_downloadSpeed *= 1000.0 / d->m_speedTimerInterval; // rescale to bytes/second + + emit downloadSpeed(d->m_downloadSpeed); +} + +void KDUpdater::FileDownloader::emitDownloadStatus() +{ + QString status; + if (d->m_bytesToReceive > 0) { + QString bytesReceived = format(d->m_bytesReceived); + const QString bytesToReceive = format(d->m_bytesToReceive); + + const QString tmp = bytesToReceive.mid(bytesToReceive.indexOf(QLatin1Char(' '))); + if (bytesReceived.endsWith(tmp)) + bytesReceived.chop(tmp.length()); + + status = bytesReceived + tr(" of ") + bytesToReceive; + } else { + if (d->m_bytesReceived > 0) + status = format(d->m_bytesReceived) + tr(" downloaded."); + } + + status += QLatin1String(" (") + format(d->m_downloadSpeed) + tr("/sec") + QLatin1Char(')'); + if (d->m_bytesToReceive > 0 && d->m_downloadSpeed > 0) { + const qint64 time = (d->m_bytesToReceive - d->m_bytesReceived) / d->m_downloadSpeed; + + int s = time % 60; + const int d = time / 86400; + const int h = (time / 3600) - (d * 24); + const int m = (time / 60) - (d * 1440) - (h * 60); + + QString days; + if (d > 0) + days = QString::number(d) + (d < 2 ? tr(" day") : tr(" days")) + QLatin1String(", "); + + QString hours; + if (h > 0) + hours = QString::number(h) + (h < 2 ? tr(" hour") : tr(" hours")) + QLatin1String(", "); + + QString minutes; + if (m > 0) + minutes = QString::number(m) + (m < 2 ? tr(" minute") : tr(" minutes")); + + QString seconds; + if (s >= 0 && minutes.isEmpty()) { + s = (s <= 0 ? 1 : s); + seconds = QString::number(s) + (s < 2 ? tr(" second") : tr(" seconds")); + } + status += tr(" - ") + days + hours + minutes + seconds + tr(" remaining."); + } else { + status += tr(" - unknown time remaining."); + } + + emit downloadStatus(status); +} + +void KDUpdater::FileDownloader::emitDownloadProgress() +{ + emit downloadProgress(d->m_bytesReceived, d->m_bytesToReceive); +} + +void KDUpdater::FileDownloader::emitEstimatedDownloadTime() +{ + if (d->m_bytesToReceive <= 0 || d->m_downloadSpeed <= 0) { + emit estimatedDownloadTime(-1); + return; + } + emit estimatedDownloadTime((d->m_bytesToReceive - d->m_bytesReceived) / d->m_downloadSpeed); +} + +/*! + Returns a copy of the proxy factory that this FileDownloader object is using to determine the proxies to + be used for requests. +*/ +FileDownloaderProxyFactory *KDUpdater::FileDownloader::proxyFactory() const +{ + if (d->m_factory) + return d->m_factory->clone(); + return 0; +} + +/*! + Sets the proxy factory for this class to be \a factory. A proxy factory is used to determine a more + specific list of proxies to be used for a given request, instead of trying to use the same proxy value + for all requests. This might only be of use for http or ftp requests. +*/ +void KDUpdater::FileDownloader::setProxyFactory(FileDownloaderProxyFactory *factory) +{ + delete d->m_factory; + d->m_factory = factory; +} + +/*! + Returns a copy of the authenticator that this FileDownloader object is using to set the username and + password for download request. +*/ +QAuthenticator KDUpdater::FileDownloader::authenticator() const +{ + return d->m_authenticator; +} + +/*! + Sets the authenticator object for this class to be \a authenticator. A authenticator is used to + pass on the required authentication information. This might only be of use for http or ftp requests. +*/ +void KDUpdater::FileDownloader::setAuthenticator(const QAuthenticator &authenticator) +{ + d->m_authenticator = authenticator; +} + +// -- KDUpdater::LocalFileDownloader + +/* + Even though QFile::copy() does the task of copying local files from one place + to another, I prefer to use the timer and copy one block of data per unit time. + + This is because, it is possible that the user of KDUpdater is simultaneously + downloading several files. Sometimes in tandem with other file downloaders. + If the local file that is being downloaded takes a long time; then that will + hang the other downloads. + + On the other hand, local downloads need not actually download the file. It can + simply pass on the source file as destination file. At this moment however, + I think the user of LocalFileDownloader will assume that the downloaded file + can be fiddled around with without worrying about whether it would mess up + the original source or not. +*/ + +struct KDUpdater::LocalFileDownloader::Private +{ + Private() + : source(0) + , destination(0) + , downloaded(false) + , timerId(-1) + {} + + QFile *source; + QFile *destination; + QString destFileName; + bool downloaded; + int timerId; +}; + +KDUpdater::LocalFileDownloader::LocalFileDownloader(QObject *parent) + : KDUpdater::FileDownloader(QLatin1String("file"), parent) + , d (new Private) +{ +} + +KDUpdater::LocalFileDownloader::~LocalFileDownloader() +{ + if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty()) + QFile::remove(d->destFileName); + + delete d; +} + +bool KDUpdater::LocalFileDownloader::canDownload() const +{ + QFileInfo fi(url().toLocalFile()); + return fi.exists() && fi.isReadable(); +} + +bool KDUpdater::LocalFileDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::LocalFileDownloader::doDownload() +{ + // Already downloaded + if (d->downloaded) + return; + + // Already started downloading + if (d->timerId >= 0) + return; + + // Open source and destination files + QString localFile = this->url().toLocalFile(); + d->source = new QFile(localFile, this); + if (!d->source->open(QFile::ReadOnly)) { + onError(); + setDownloadAborted(tr("Cannot open source file '%1' for reading.").arg(QFileInfo(localFile) + .fileName())); + return; + } + + if (d->destFileName.isEmpty()) { + QTemporaryFile *file = new QTemporaryFile(this); + file->open(); + d->destination = file; + } else { + d->destination = new QFile(d->destFileName, this); + d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); + } + + if (!d->destination->isOpen()) { + onError(); + setDownloadAborted(tr("Cannot open destination file '%1' for writing.") + .arg(QFileInfo(d->destination->fileName()).fileName())); + return; + } + + runDownloadSpeedTimer(); + // Start a timer and kickoff the copy process + d->timerId = startTimer(0); // as fast as possible + + emit downloadStarted(); + emit downloadProgress(0); +} + +QString KDUpdater::LocalFileDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +void KDUpdater::LocalFileDownloader::setDownloadedFileName(const QString &name) +{ + d->destFileName = name; +} + +KDUpdater::LocalFileDownloader *KDUpdater::LocalFileDownloader::clone(QObject *parent) const +{ + return new LocalFileDownloader(parent); +} + +void KDUpdater::LocalFileDownloader::cancelDownload() +{ + if (d->timerId < 0) + return; + + killTimer(d->timerId); + d->timerId = -1; + + onError(); + setDownloadCanceled(); +} + +void KDUpdater::LocalFileDownloader::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == d->timerId) { + if (!d->source || !d->destination) + return; + + const qint64 blockSize = 32768; + QByteArray buffer; + buffer.resize(blockSize); + const qint64 numRead = d->source->read(buffer.data(), buffer.size()); + qint64 toWrite = numRead; + while (toWrite > 0) { + const qint64 numWritten = d->destination->write(buffer.constData() + numRead - toWrite, toWrite); + if (numWritten < 0) { + killTimer(d->timerId); + d->timerId = -1; + onError(); + setDownloadAborted(tr("Writing to %1 failed: %2").arg(d->destination->fileName(), + d->destination->errorString())); + return; + } + toWrite -= numWritten; + } + addSample(numRead); + + if (numRead > 0) { + setProgress(d->source->pos(), d->source->size()); + emit downloadProgress(calcProgress(d->source->pos(), d->source->size())); + return; + } + + d->destination->flush(); + + killTimer(d->timerId); + d->timerId = -1; + + setDownloadCompleted(d->destination->fileName()); + } else if (event->timerId() == downloadSpeedTimerId()) { + emitDownloadSpeed(); + emitDownloadStatus(); + emitDownloadProgress(); + emitEstimatedDownloadTime(); + } +} + +void LocalFileDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination)) + file->setAutoRemove(false); + d->destination->close(); + delete d->destination; + d->destination = 0; + delete d->source; + d->source = 0; + stopDownloadSpeedTimer(); +} + +void LocalFileDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; + delete d->source; + d->source = 0; + stopDownloadSpeedTimer(); +} + + +// -- ResourceFileDownloader + +struct KDUpdater::ResourceFileDownloader::Private +{ + Private() + : downloaded(false), + timerId(-1) + {} + + QString destFileName; + bool downloaded; + int timerId; +}; + +KDUpdater::ResourceFileDownloader::ResourceFileDownloader(QObject *parent) + : KDUpdater::FileDownloader(QLatin1String("resource"), parent) + , d(new Private) +{ +} + +KDUpdater::ResourceFileDownloader::~ResourceFileDownloader() +{ + delete d; +} + +bool KDUpdater::ResourceFileDownloader::canDownload() const +{ + QUrl url = this->url(); + url.setScheme(QString::fromLatin1("file")); + QString localFile = QString::fromLatin1(":%1").arg(url.toLocalFile()); + QFileInfo fi(localFile); + return fi.exists() && fi.isReadable(); +} + +bool KDUpdater::ResourceFileDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::ResourceFileDownloader::doDownload() +{ + // Already downloaded + if (d->downloaded) + return; + + // Already started downloading + if (d->timerId >= 0) + return; + + // Open source and destination files + QUrl url = this->url(); + url.setScheme(QString::fromLatin1("file")); + d->destFileName = QString::fromLatin1(":%1").arg(url.toLocalFile()); + + // Start a timer and kickoff the copy process + d->timerId = startTimer(0); // as fast as possible + emit downloadStarted(); + emit downloadProgress(0); +} + +QString KDUpdater::ResourceFileDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +void KDUpdater::ResourceFileDownloader::setDownloadedFileName(const QString &/*name*/) +{ + Q_ASSERT_X(false, "KDUpdater::ResourceFileDownloader::setDownloadedFileName", "Not supported!"); +} + +KDUpdater::ResourceFileDownloader *KDUpdater::ResourceFileDownloader::clone(QObject *parent) const +{ + return new ResourceFileDownloader(parent); +} + +void KDUpdater::ResourceFileDownloader::cancelDownload() +{ + if (d->timerId < 0) + return; + + killTimer(d->timerId); + d->timerId = -1; + + setDownloadCanceled(); +} + +void KDUpdater::ResourceFileDownloader::timerEvent(QTimerEvent *) +{ + killTimer(d->timerId); + d->timerId = -1; + setDownloadCompleted(d->destFileName); +} + +void KDUpdater::ResourceFileDownloader::onSuccess() +{ + d->downloaded = true; +} + +void KDUpdater::ResourceFileDownloader::onError() +{ + d->downloaded = false; +} + + +// -- KDUpdater::FtpFileDownloader + +struct KDUpdater::FtpDownloader::Private +{ + Private() + : ftp(0) + , destination(0) + , downloaded(false) + , ftpCmdId(-1) + , aborted(false) + {} + + QFtp *ftp; + QFile *destination; + QString destFileName; + bool downloaded; + int ftpCmdId; + bool aborted; +}; + +KDUpdater::FtpDownloader::FtpDownloader(QObject *parent) + : KDUpdater::FileDownloader(QLatin1String("ftp"), parent) + , d(new Private) +{ +} + +KDUpdater::FtpDownloader::~FtpDownloader() +{ + if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty()) + QFile::remove(d->destFileName); + + delete d; +} + +bool KDUpdater::FtpDownloader::canDownload() const +{ + // TODO: Check whether the ftp file actually exists or not. + return true; +} + +bool KDUpdater::FtpDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::FtpDownloader::doDownload() +{ + if (d->downloaded) + return; + + if (d->ftp) + return; + + d->ftp = new QFtp(this); + connect(d->ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool))); + connect(d->ftp, SIGNAL(commandStarted(int)), this, SLOT(ftpCmdStarted(int))); + connect(d->ftp, SIGNAL(commandFinished(int, bool)), this, SLOT(ftpCmdFinished(int, bool))); + connect(d->ftp, SIGNAL(stateChanged(int)), this, SLOT(ftpStateChanged(int))); + connect(d->ftp, SIGNAL(dataTransferProgress(qint64, qint64)), this, + SLOT(ftpDataTransferProgress(qint64, qint64))); + connect(d->ftp, SIGNAL(readyRead()), this, SLOT(ftpReadyRead())); + + if (FileDownloaderProxyFactory *factory = proxyFactory()) { + const QList<QNetworkProxy> proxies = factory->queryProxy(QNetworkProxyQuery(url())); + if (!proxies.isEmpty()) + d->ftp->setProxy(proxies.at(0).hostName(), proxies.at(0).port()); + delete factory; + } + + d->ftp->connectToHost(url().host(), url().port(21)); + d->ftp->login(authenticator().user(), authenticator().password()); +} + +QString KDUpdater::FtpDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +void KDUpdater::FtpDownloader::setDownloadedFileName(const QString &name) +{ + d->destFileName = name; +} + +KDUpdater::FtpDownloader *KDUpdater::FtpDownloader::clone(QObject *parent) const +{ + return new FtpDownloader(parent); +} + +void KDUpdater::FtpDownloader::cancelDownload() +{ + if (d->ftp) { + d->aborted = true; + d->ftp->abort(); + } +} + +void KDUpdater::FtpDownloader::ftpDone(bool error) +{ + if (error) { + QString errorString; + if (d->ftp) { + errorString = d->ftp->errorString(); + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + } + + onError(); + + if (d->aborted) { + d->aborted = false; + setDownloadCanceled(); + } else { + setDownloadAborted(errorString); + } + } + //PENDING what about the non-error case?? +} + +void KDUpdater::FtpDownloader::ftpCmdStarted(int id) +{ + if (id != d->ftpCmdId) + return; + + emit downloadStarted(); + emit downloadProgress(0); +} + +void KDUpdater::FtpDownloader::ftpCmdFinished(int id, bool error) +{ + if (id != d->ftpCmdId || error) // PENDING why error -> return?? + return; + + disconnect(d->ftp, 0, this, 0); + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + d->destination->flush(); + + setDownloadCompleted(d->destination->fileName()); +} + +void FtpDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination)) + file->setAutoRemove(false); + delete d->destination; + d->destination = 0; + stopDownloadSpeedTimer(); + +} + +void FtpDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; + stopDownloadSpeedTimer(); +} + +void KDUpdater::FtpDownloader::ftpStateChanged(int state) +{ + switch(state) { + case QFtp::Connected: { + // begin the download + if (d->destFileName.isEmpty()) { + QTemporaryFile *file = new QTemporaryFile(this); + file->open(); //PENDING handle error + d->destination = file; + } else { + d->destination = new QFile(d->destFileName, this); + d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); + } + runDownloadSpeedTimer(); + d->ftpCmdId = d->ftp->get(url().path()); + } break; + + case QFtp::Unconnected: { + // download was unconditionally aborted + disconnect(d->ftp, 0, this, 0); + d->ftp->deleteLater(); + d->ftp = 0; + d->ftpCmdId = -1; + onError(); + setDownloadAborted(tr("Download was aborted due to network errors.")); + } break; + } +} + +void KDUpdater::FtpDownloader::ftpDataTransferProgress(qint64 done, qint64 total) +{ + setProgress(done, total); + emit downloadProgress(calcProgress(done, total)); +} + +void KDUpdater::FtpDownloader::ftpReadyRead() +{ + static QByteArray buffer(16384, '\0'); + while (d->ftp->bytesAvailable()) { + const qint64 read = d->ftp->read(buffer.data(), buffer.size()); + qint64 written = 0; + while (written < read) { + const qint64 numWritten = d->destination->write(buffer.data() + written, read - written); + if (numWritten < 0) { + onError(); + setDownloadAborted(tr("Cannot download %1: Writing to temporary file failed: %2") + .arg(url().toString(), d->destination->errorString())); + return; + } + written += numWritten; + } + addSample(written); + } +} + +void KDUpdater::FtpDownloader::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == downloadSpeedTimerId()) { + emitDownloadSpeed(); + emitDownloadStatus(); + emitDownloadProgress(); + emitEstimatedDownloadTime(); + } +} + + +// -- KDUpdater::HttpDownloader + +struct KDUpdater::HttpDownloader::Private +{ + explicit Private(HttpDownloader *qq) + : q(qq) + , http(0) + , destination(0) + , downloaded(false) + , aborted(false) + , retrying(false) + , m_authenticationDone(false) + {} + + HttpDownloader *const q; + QNetworkAccessManager manager; + QNetworkReply *http; + QFile *destination; + QString destFileName; + bool downloaded; + bool aborted; + bool retrying; + bool m_authenticationDone; + + void shutDown() + { + disconnect(http, SIGNAL(finished()), q, SLOT(httpReqFinished())); + http->deleteLater(); + http = 0; + destination->close(); + destination->deleteLater(); + destination = 0; + } +}; + +KDUpdater::HttpDownloader::HttpDownloader(QObject *parent) + : KDUpdater::FileDownloader(QLatin1String("http"), parent) + , d(new Private(this)) +{ + connect(&d->manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, + SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*))); +} + +KDUpdater::HttpDownloader::~HttpDownloader() +{ + if (this->isAutoRemoveDownloadedFile() && !d->destFileName.isEmpty()) + QFile::remove(d->destFileName); + delete d; +} + +bool KDUpdater::HttpDownloader::canDownload() const +{ + // TODO: Check whether the http file actually exists or not. + return true; +} + +bool KDUpdater::HttpDownloader::isDownloaded() const +{ + return d->downloaded; +} + +void KDUpdater::HttpDownloader::doDownload() +{ + if (d->downloaded) + return; + + if (d->http) + return; + + startDownload(url()); + runDownloadSpeedTimer(); +} + +QString KDUpdater::HttpDownloader::downloadedFileName() const +{ + return d->destFileName; +} + +void KDUpdater::HttpDownloader::setDownloadedFileName(const QString &name) +{ + d->destFileName = name; +} + +KDUpdater::HttpDownloader *KDUpdater::HttpDownloader::clone(QObject *parent) const +{ + return new HttpDownloader(parent); +} + +void KDUpdater::HttpDownloader::httpReadyRead() +{ + static QByteArray buffer(16384, '\0'); + while (d->http->bytesAvailable()) { + const qint64 read = d->http->read(buffer.data(), buffer.size()); + qint64 written = 0; + while (written < read) { + const qint64 numWritten = d->destination->write(buffer.data() + written, read - written); + if (numWritten < 0) { + const QString err = d->destination->errorString(); + d->shutDown(); + setDownloadAborted(tr("Cannot download %1: Writing to temporary file failed: %2") + .arg(url().toString(), err)); + return; + } + written += numWritten; + } + addSample(written); + } +} + +void KDUpdater::HttpDownloader::httpError(QNetworkReply::NetworkError) +{ + if (!d->aborted) + httpDone(true); +} + +void KDUpdater::HttpDownloader::cancelDownload() +{ + d->aborted = true; + if (d->http) { + d->http->abort(); + httpDone(true); + } +} + +void KDUpdater::HttpDownloader::httpDone(bool error) +{ + if (error) { + QString err; + if (d->http) { + err = d->http->errorString(); + d->http->deleteLater(); + d->http = 0; + onError(); + } + + if (d->aborted) { + d->aborted = false; + setDownloadCanceled(); + } else { + setDownloadAborted(err); + } + } + //PENDING: what about the non-error case?? +} + +void KDUpdater::HttpDownloader::onError() +{ + d->downloaded = false; + d->destFileName.clear(); + delete d->destination; + d->destination = 0; + stopDownloadSpeedTimer(); +} + +void KDUpdater::HttpDownloader::onSuccess() +{ + d->downloaded = true; + d->destFileName = d->destination->fileName(); + if (QTemporaryFile *file = dynamic_cast<QTemporaryFile *>(d->destination)) + file->setAutoRemove(false); + delete d->destination; + d->destination = 0; + stopDownloadSpeedTimer(); +} + +void KDUpdater::HttpDownloader::httpReqFinished() +{ + const QVariant redirect = d->http == 0 ? QVariant() + : d->http->attribute(QNetworkRequest::RedirectionTargetAttribute); + + const QUrl redirectUrl = redirect.toUrl(); + if (followRedirects() && redirectUrl.isValid()) { + d->shutDown(); // clean the previous download + startDownload(redirectUrl); + } else { + if (d->http == 0) + return; + + httpReadyRead(); + d->destination->flush(); + setDownloadCompleted(d->destination->fileName()); + d->http->deleteLater(); + d->http = 0; + } +} + +void KDUpdater::HttpDownloader::httpReadProgress(qint64 done, qint64 total) +{ + setProgress(done, total); + emit downloadProgress(calcProgress(done, total)); +} + +void KDUpdater::HttpDownloader::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == downloadSpeedTimerId()) { + emitDownloadSpeed(); + emitDownloadStatus(); + emitDownloadProgress(); + emitEstimatedDownloadTime(); + } +} + +void KDUpdater::HttpDownloader::startDownload(const QUrl &url) +{ + d->m_authenticationDone = false; + d->manager.setProxyFactory(proxyFactory()); + d->http = d->manager.get(QNetworkRequest(url)); + + connect(d->http, SIGNAL(readyRead()), this, SLOT(httpReadyRead())); + connect(d->http, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(httpReadProgress(qint64, qint64))); + connect(d->http, SIGNAL(finished()), this, SLOT(httpReqFinished())); + connect(d->http, SIGNAL(error(QNetworkReply::NetworkError)), this, + SLOT(httpError(QNetworkReply::NetworkError))); + + if (d->destFileName.isEmpty()) { + QTemporaryFile *file = new QTemporaryFile(this); + file->open(); + d->destination = file; + } else { + d->destination = new QFile(d->destFileName, this); + d->destination->open(QIODevice::ReadWrite | QIODevice::Truncate); + } + + if (!d->destination->isOpen()) { + d->shutDown(); + setDownloadAborted(tr("Cannot download %1: Could not create temporary file: %2").arg(url.toString(), + d->destination->errorString())); + } +} + +void KDUpdater::HttpDownloader::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) +{ + qDebug() << reply->readAll(); + if (!d->m_authenticationDone) { + d->m_authenticationDone = true; + authenticator->setUser(this->authenticator().user()); + authenticator->setPassword(this->authenticator().password()); + } +} diff --git a/src/libs/kdtools/kdupdaterfiledownloader.h b/src/libs/kdtools/kdupdaterfiledownloader.h new file mode 100644 index 000000000..bdba0a859 --- /dev/null +++ b/src/libs/kdtools/kdupdaterfiledownloader.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_FILE_DOWNLOADER_H +#define KD_UPDATER_FILE_DOWNLOADER_H + +#include "kdupdater.h" +#include "kdtoolsglobal.h" + +#include <QtCore/QObject> +#include <QtCore/QUrl> +#include <QtCore/QCryptographicHash> + +#include <QtNetwork/QAuthenticator> + +namespace KDUpdater { + +KDTOOLS_EXPORT QByteArray calculateHash(QIODevice *device, QCryptographicHash::Algorithm algo); +KDTOOLS_EXPORT QByteArray calculateHash(const QString &path, QCryptographicHash::Algorithm algo); + +class HashVerificationJob; +class FileDownloaderProxyFactory; + +class KDTOOLS_EXPORT FileDownloader : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool autoRemoveDownloadedFile READ isAutoRemoveDownloadedFile WRITE setAutoRemoveDownloadedFile) + Q_PROPERTY(QUrl url READ url WRITE setUrl) + Q_PROPERTY(QString scheme READ scheme) + +public: + explicit FileDownloader(const QString &scheme, QObject *parent = 0); + ~FileDownloader(); + + void setUrl(const QUrl &url); + QUrl url() const; + + void setSha1Sum(const QByteArray &sha1); + QByteArray sha1Sum() const; + + QString errorString() const; + QString scheme() const; + + virtual bool canDownload() const = 0; + virtual bool isDownloaded() const = 0; + virtual QString downloadedFileName() const = 0; + virtual void setDownloadedFileName(const QString &name) = 0; + virtual FileDownloader *clone(QObject *parent=0) const = 0; + + void download(); + + void setAutoRemoveDownloadedFile(bool val); + bool isAutoRemoveDownloadedFile() const; + + void setFollowRedirects(bool val); + bool followRedirects() const; + + FileDownloaderProxyFactory *proxyFactory() const; + void setProxyFactory(FileDownloaderProxyFactory *factory); + + QAuthenticator authenticator() const; + void setAuthenticator(const QAuthenticator &authenticator); + +public Q_SLOTS: + virtual void cancelDownload(); + void sha1SumVerified(KDUpdater::HashVerificationJob *job); + +protected: + virtual void onError() = 0; + virtual void onSuccess() = 0; + +Q_SIGNALS: + void downloadStarted(); + void downloadCanceled(); + + void downloadProgress(double progress); + void estimatedDownloadTime(int seconds); + void downloadSpeed(qint64 bytesPerSecond); + void downloadStatus(const QString &status); + void downloadProgress(qint64 bytesReceived, qint64 bytesToReceive); + +#ifndef Q_MOC_RUN +private: +#endif + void downloadCompleted(); + void downloadAborted(const QString &errorMessage); + +protected: + void setDownloadCanceled(); + void setDownloadCompleted(const QString &filepath); + void setDownloadAborted(const QString &error); + + void runDownloadSpeedTimer(); + void stopDownloadSpeedTimer(); + + void addSample(qint64 sample); + int downloadSpeedTimerId() const; + void setProgress(qint64 bytesReceived, qint64 bytesToReceive); + + void emitDownloadSpeed(); + void emitDownloadStatus(); + void emitDownloadProgress(); + void emitEstimatedDownloadTime(); + +private Q_SLOTS: + virtual void doDownload() = 0; + +private: + struct Private; + Private *d; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_FILE_DOWNLOADER_H diff --git a/src/libs/kdtools/kdupdaterfiledownloader_p.h b/src/libs/kdtools/kdupdaterfiledownloader_p.h new file mode 100644 index 000000000..127c3f5d9 --- /dev/null +++ b/src/libs/kdtools/kdupdaterfiledownloader_p.h @@ -0,0 +1,209 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_FILE_DOWNLOADER_P_H +#define KD_UPDATER_FILE_DOWNLOADER_P_H + +#include "kdupdaterfiledownloader.h" + +#include <QtCore/QCryptographicHash> +#include <QtNetwork/QNetworkReply> + +// these classes are not a part of the public API + +namespace KDUpdater { + +//TODO make it a KDJob once merged +class HashVerificationJob : public QObject +{ + Q_OBJECT + +public: + enum Error { + NoError = 0, + ReadError = 128, + SumsDifferError + }; + + explicit HashVerificationJob(QObject *parent = 0); + ~HashVerificationJob(); + + void setDevice(QIODevice *dev); + void setSha1Sum(const QByteArray &data); + + bool hasError() const; + int error() const; + + void start(); + +Q_SIGNALS: + void finished(KDUpdater::HashVerificationJob *); + +private: + void emitFinished(); + void timerEvent(QTimerEvent *te); + +private: + class Private; + Private *d; +}; + +class LocalFileDownloader : public FileDownloader +{ + Q_OBJECT + +public: + explicit LocalFileDownloader(QObject *parent = 0); + ~LocalFileDownloader(); + + bool canDownload() const; + bool isDownloaded() const; + QString downloadedFileName() const; + void setDownloadedFileName(const QString &name); + LocalFileDownloader *clone(QObject *parent = 0) const; + +public Q_SLOTS: + void cancelDownload(); + +protected: + void timerEvent(QTimerEvent *te); + void onError(); + void onSuccess(); + +private Q_SLOTS: + void doDownload(); + +private: + struct Private; + Private *d; +}; + +class ResourceFileDownloader : public FileDownloader +{ + Q_OBJECT + +public: + explicit ResourceFileDownloader(QObject *parent = 0); + ~ResourceFileDownloader(); + + bool canDownload() const; + bool isDownloaded() const; + QString downloadedFileName() const; + void setDownloadedFileName(const QString &name); + ResourceFileDownloader *clone(QObject *parent = 0) const; + +public Q_SLOTS: + void cancelDownload(); + +protected: + void timerEvent(QTimerEvent *te); + void onError(); + void onSuccess(); + +private Q_SLOTS: + void doDownload(); + +private: + struct Private; + Private *d; +}; + +class FtpDownloader : public FileDownloader +{ + Q_OBJECT + +public: + explicit FtpDownloader(QObject *parent = 0); + ~FtpDownloader(); + + bool canDownload() const; + bool isDownloaded() const; + QString downloadedFileName() const; + void setDownloadedFileName(const QString &name); + FtpDownloader *clone(QObject *parent = 0) const; + +public Q_SLOTS: + void cancelDownload(); + +protected: + void onError(); + void onSuccess(); + void timerEvent(QTimerEvent *event); + +private Q_SLOTS: + void doDownload(); + + void ftpDone(bool error); + void ftpCmdStarted(int id); + void ftpCmdFinished(int id, bool error); + void ftpStateChanged(int state); + void ftpDataTransferProgress(qint64 done, qint64 total); + void ftpReadyRead(); + +private: + struct Private; + Private *d; +}; + +class HttpDownloader : public FileDownloader +{ + Q_OBJECT + +public: + explicit HttpDownloader(QObject *parent = 0); + ~HttpDownloader(); + + bool canDownload() const; + bool isDownloaded() const; + QString downloadedFileName() const; + void setDownloadedFileName(const QString &name); + HttpDownloader *clone(QObject *parent = 0) const; + +public Q_SLOTS: + void cancelDownload(); + +protected: + void onError(); + void onSuccess(); + void timerEvent(QTimerEvent *event); + +private Q_SLOTS: + void doDownload(); + + void httpReadyRead(); + void httpReadProgress(qint64 done, qint64 total); + void httpError(QNetworkReply::NetworkError); + void httpDone(bool error); + void httpReqFinished(); + void onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator); + +private: + void startDownload(const QUrl &url); + +private: + struct Private; + Private *d; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_FILE_DOWNLOADER_P_H diff --git a/src/libs/kdtools/kdupdaterfiledownloaderfactory.cpp b/src/libs/kdtools/kdupdaterfiledownloaderfactory.cpp new file mode 100644 index 000000000..8be508cc7 --- /dev/null +++ b/src/libs/kdtools/kdupdaterfiledownloaderfactory.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterfiledownloaderfactory.h" +#include "kdupdaterfiledownloader_p.h" + +/*! + \internal + \ingroup kdupdater + \class KDUpdater::FileDownloaderFactory kdupdaterfiledownloaderfactory.h + \brief Factory for \ref KDUpdater::FileDownloader + + This class acts as a factory for \ref KDUpdater::FileDownloader. You can register + one or more file downloaders with this factory and query them based on their scheme. + + This class follows the singleton design pattern. Only one instance of this class can + be created and its reference can be fetched from the \ref instance() method. +*/ + +using namespace KDUpdater; + +struct FileDownloaderFactory::FileDownloaderFactoryData +{ + FileDownloaderFactoryData() : m_factory(0) {} + ~FileDownloaderFactoryData() { delete m_factory; } + + bool m_followRedirects; + FileDownloaderProxyFactory *m_factory; +}; + +FileDownloaderFactory& FileDownloaderFactory::instance() +{ + static KDUpdater::FileDownloaderFactory theFactory; + return theFactory; +} + +/*! + Constructor +*/ +FileDownloaderFactory::FileDownloaderFactory() + : d (new FileDownloaderFactoryData) +{ + // Register the default file downloader set + registerFileDownloader<LocalFileDownloader>( QLatin1String("file")); + registerFileDownloader<FtpDownloader>(QLatin1String("ftp")); + registerFileDownloader<HttpDownloader>(QLatin1String("http")); + registerFileDownloader<ResourceFileDownloader >(QLatin1String("resource")); + d->m_followRedirects = false; +} + +bool FileDownloaderFactory::followRedirects() +{ + return FileDownloaderFactory::instance().d->m_followRedirects; +} + +void FileDownloaderFactory::setFollowRedirects(bool val) +{ + FileDownloaderFactory::instance().d->m_followRedirects = val; +} + +void FileDownloaderFactory::setProxyFactory(FileDownloaderProxyFactory *factory) +{ + delete FileDownloaderFactory::instance().d->m_factory; + FileDownloaderFactory::instance().d->m_factory = factory; +} + +FileDownloaderFactory::~FileDownloaderFactory() +{ + delete d; +} + +/*! + Returns a new instance to the \ref KDUpdater::FileDownloader based whose scheme is equal to the string + passed as parameter to this function. + \note Ownership of this object remains to the programmer. +*/ +FileDownloader *FileDownloaderFactory::create(const QString &scheme, QObject *parent) const +{ + FileDownloader *downloader = KDGenericFactory<FileDownloader>::create(scheme); + if (downloader != 0) { + downloader->setParent(parent); + downloader->setFollowRedirects(d->m_followRedirects); + if (d->m_factory) + downloader->setProxyFactory(d->m_factory->clone()); + } + return downloader; +} + +/*! + KDUpdater::FileDownloaderFactory::registerFileDownlooader + Registers a new file downloader with the factory. If there is already a downloader with the same scheme, + the downloader is replaced. The ownership of the downloader is transfered to the factory. +*/ diff --git a/src/libs/kdtools/kdupdaterfiledownloaderfactory.h b/src/libs/kdtools/kdupdaterfiledownloaderfactory.h new file mode 100644 index 000000000..e27cb6f7e --- /dev/null +++ b/src/libs/kdtools/kdupdaterfiledownloaderfactory.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_FILE_DOWNLOADER_FACTORY_H +#define KD_UPDATER_FILE_DOWNLOADER_FACTORY_H + +#include "kdupdater.h" +#include <kdgenericfactory.h> + +#include <QtCore/QStringList> +#include <QtCore/QUrl> + +#include <QtNetwork/QNetworkProxyFactory> + +QT_BEGIN_NAMESPACE +class QObject; +QT_END_NAMESPACE + +namespace KDUpdater { + +class FileDownloader; + +class KDTOOLS_EXPORT FileDownloaderProxyFactory : public QNetworkProxyFactory +{ + public: + virtual ~FileDownloaderProxyFactory() {} + virtual FileDownloaderProxyFactory *clone() const = 0; +}; + +class KDTOOLS_EXPORT FileDownloaderFactory : public KDGenericFactory<FileDownloader> +{ + Q_DISABLE_COPY(FileDownloaderFactory) + +public: + static FileDownloaderFactory &instance(); + ~FileDownloaderFactory(); + + template<typename T> + void registerFileDownloader(const QString &scheme) + { + registerProduct<T>(scheme); + } + FileDownloader *create(const QString &scheme, QObject *parent = 0) const; + + static bool followRedirects(); + static void setFollowRedirects(bool val); + + static void setProxyFactory(FileDownloaderProxyFactory *factory); + +private: + FileDownloaderFactory(); + +private: + struct FileDownloaderFactoryData; + FileDownloaderFactoryData *d; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_FILE_DOWNLOADER_FACTORY_H diff --git a/src/libs/kdtools/kdupdaterpackagesinfo.cpp b/src/libs/kdtools/kdupdaterpackagesinfo.cpp new file mode 100644 index 000000000..5a23c7d1a --- /dev/null +++ b/src/libs/kdtools/kdupdaterpackagesinfo.cpp @@ -0,0 +1,581 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterpackagesinfo.h" +#include "kdupdaterapplication.h" + +#include <QFileInfo> +#include <QDomDocument> +#include <QDomElement> +#include <QVector> + +using namespace KDUpdater; + +/*! + \ingroup kdupdater + \class KDUpdater::PackagesInfo kdupdaterpackagesinfo.h KDUpdaterPackagesInfo + \brief Provides access to information about packages installed on the application side. + + This class parses the XML package file specified via the setFileName() method and + provides access to the the information defined within the package file through an + easy to use API. You can: + \li get application name via the \ref applicationName() method + \li get application version via the \ref applicationVersion() method + \li get information about the number of packages installed and their meta-data via the + \ref packageInfoCount() and \ref packageInfo() methods. + + Instances of this class cannot be created. Each instance of \ref KDUpdater::Application + has one instance of this class associated with it. You can fetch a pointer to an instance + of this class for an application via the \ref KDUpdater::Application::packagesInfo() + method. +*/ + +/*! \enum UpdatePackagesInfo::Error + * Error codes related to retrieving update sources + */ + +/*! \var UpdatePackagesInfo::Error UpdatePackagesInfo::NoError + * No error occurred + */ + +/*! \var UpdatePackagesInfo::Error UpdatePackagesInfo::NotYetReadError + * The package information was not parsed yet from the XML file + */ + +/*! \var UpdatePackagesInfo::Error UpdatePackagesInfo::CouldNotReadPackageFileError + * the specified update source file could not be read (does not exist or not readable) + */ + +/*! \var UpdatePackagesInfo::Error UpdatePackagesInfo::InvalidXmlError + * The source file contains invalid XML. + */ + +/*! \var UpdatePackagesInfo::Error UpdatePackagesInfo::InvalidContentError + * The source file contains valid XML, but does not match the expected format for package descriptions + */ + +struct PackagesInfo::PackagesInfoData +{ + PackagesInfoData() : + application(0), + error(PackagesInfo::NotYetReadError), + compatLevel(-1), + modified(false) + {} + Application *application; + QString errorMessage; + PackagesInfo::Error error; + QString fileName; + QString applicationName; + QString applicationVersion; + int compatLevel; + bool modified; + + QVector<PackageInfo> packageInfoList; + + void addPackageFrom(const QDomElement &packageE); + void setInvalidContentError(const QString &detail); +}; + +void PackagesInfo::PackagesInfoData::setInvalidContentError(const QString &detail) +{ + error = PackagesInfo::InvalidContentError; + errorMessage = tr("%1 contains invalid content: %2").arg(fileName, detail); +} + +/*! + \internal +*/ +PackagesInfo::PackagesInfo(Application *application) + : QObject(application), + d(new PackagesInfoData()) +{ + d->application = application; +} + +/*! + \internal +*/ +PackagesInfo::~PackagesInfo() +{ + writeToDisk(); + delete d; +} + +/*! + Returns a pointer to the application, whose package information this class provides + access to. +*/ +Application *PackagesInfo::application() const +{ + return d->application; +} + +/*! + Returns true if the PackagesInfo are valid else false is returned in which case + the \a errorString() method can be used to receive a describing error message. +*/ +bool PackagesInfo::isValid() const +{ + if (!d->fileName.isEmpty()) + return d->error <= NotYetReadError; + return d->error == NoError; +} + +/*! + Returns a human-readable error message. +*/ +QString PackagesInfo::errorString() const +{ + return d->errorMessage; +} + +PackagesInfo::Error PackagesInfo::error() const +{ + return d->error; +} + +/*! + Sets the complete file name of the Packages.xml file. The function also issues a call to + \ref refresh() to reload package information from the XML file. + + \sa KDUpdater::Application::setPackagesXMLFileName() +*/ +void PackagesInfo::setFileName(const QString &fileName) +{ + if (d->fileName == fileName) + return; + + d->fileName = fileName; + refresh(); +} + +/*! + Returns the name of the Packages.xml file that this class referred to. +*/ +QString PackagesInfo::fileName() const +{ + return d->fileName; +} + +/*! + Sets the application name. By default this is the name specified in + the ApplicationName XML element of the Packages.xml file. +*/ +void PackagesInfo::setApplicationName(const QString &name) +{ + d->applicationName = name; + d->modified = true; +} + +/*! + Returns the application name. +*/ +QString PackagesInfo::applicationName() const +{ + return d->applicationName; +} + +/*! + Sets the application version. By default this is the version specified + in the ApplicationVersion XML element of Packages.xml. +*/ +void PackagesInfo::setApplicationVersion(const QString &version) +{ + d->applicationVersion = version; + d->modified = true; +} + +/*! + Returns the application version. +*/ +QString PackagesInfo::applicationVersion() const +{ + return d->applicationVersion; +} + +/*! + Returns the number of \ref KDUpdater::PackageInfo objects contained in this class. +*/ +int PackagesInfo::packageInfoCount() const +{ + return d->packageInfoList.count(); +} + +/*! + Returns the package info structure (\ref KDUpdater::PackageInfo) at index. If index is + out of range then an empty package info structure is returned. +*/ +PackageInfo PackagesInfo::packageInfo(int index) const +{ + if (index < 0 || index >= d->packageInfoList.count()) + return PackageInfo(); + + return d->packageInfoList.at(index); +} + +/*! + Returns the compat level of the application. +*/ +int PackagesInfo::compatLevel() const +{ + return d->compatLevel; +} + +/*! + This function returns the index of the package whose name is \c pkgName. If no such + package was found, this function returns -1. +*/ +int PackagesInfo::findPackageInfo(const QString &pkgName) const +{ + for (int i = 0; i < d->packageInfoList.count(); i++) { + if (d->packageInfoList[i].name == pkgName) + return i; + } + + return -1; +} + +/*! + Returns all package info structures. +*/ +QVector<PackageInfo> PackagesInfo::packageInfos() const +{ + return d->packageInfoList; +} + +/*! + This function re-reads the Packages.xml file and updates itself. Changes to \ref applicationName() + and \ref applicationVersion() are lost after this function returns. The function emits a reset() + signal after completion. +*/ +void PackagesInfo::refresh() +{ + // First clear internal variables + d->applicationName.clear(); + d->applicationVersion.clear(); + d->packageInfoList.clear(); + d->modified = false; + + QFile file(d->fileName); + + // if the file does not exist then we just skip the reading + if (!file.exists()) { + d->error = NotYetReadError; + d->errorMessage = tr("The file %1 does not exist.").arg(d->fileName); + emit reset(); + return; + } + + // Open Packages.xml + if (!file.open(QFile::ReadOnly)) { + d->error = CouldNotReadPackageFileError; + d->errorMessage = tr("Could not open %1.").arg(d->fileName); + emit reset(); + return; + } + + // Parse the XML document + QDomDocument doc; + QString parseErrorMessage; + int parseErrorLine; + int parseErrorColumn; + if (!doc.setContent(&file, &parseErrorMessage, &parseErrorLine, &parseErrorColumn)) { + d->error = InvalidXmlError; + d->errorMessage = tr("Parse error in %1 at %2, %3: %4") + .arg(d->fileName, + QString::number(parseErrorLine), + QString::number(parseErrorColumn), + parseErrorMessage); + emit reset(); + return; + } + file.close(); + + // Now populate information from the XML file. + QDomElement rootE = doc.documentElement(); + if (rootE.tagName() != QLatin1String("Packages")) { + d->setInvalidContentError(tr("Root element %1 unexpected, should be 'Packages'.").arg(rootE.tagName())); + emit reset(); + return; + } + + QDomNodeList childNodes = rootE.childNodes(); + for (int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.item(i); + QDomElement childNodeE = childNode.toElement(); + if (childNodeE.isNull()) + continue; + + if (childNodeE.tagName() == QLatin1String("ApplicationName")) + d->applicationName = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("ApplicationVersion")) + d->applicationVersion = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Package")) + d->addPackageFrom(childNodeE); + else if (childNodeE.tagName() == QLatin1String("CompatLevel")) + d->compatLevel = childNodeE.text().toInt(); + } + + d->error = NoError; + d->errorMessage.clear(); + emit reset(); +} + +/*! + Sets the application compat level. +*/ +void PackagesInfo::setCompatLevel(int level) +{ + d->compatLevel = level; + d->modified = true; +} + +/*! + Marks the package with \a name as installed in \a version. + */ +bool PackagesInfo::installPackage(const QString &name, const QString &version, + const QString &title, const QString &description, + const QStringList &dependencies, bool forcedInstallation, + bool virtualComp, quint64 uncompressedSize, + const QString &inheritVersionFrom) +{ + if (findPackageInfo(name) != -1) + return updatePackage(name, version, QDate::currentDate()); + + PackageInfo info; + info.name = name; + info.version = version; + info.inheritVersionFrom = inheritVersionFrom; + info.installDate = QDate::currentDate(); + info.title = title; + info.description = description; + info.dependencies = dependencies; + info.forcedInstallation = forcedInstallation; + info.virtualComp = virtualComp; + info.uncompressedSize = uncompressedSize; + d->packageInfoList.push_back(info); + d->modified = true; + return true; +} + +/*! + Update the package. +*/ +bool PackagesInfo::updatePackage(const QString &name, + const QString &version, + const QDate &date) +{ + int index = findPackageInfo(name); + + if (index == -1) + return false; + + d->packageInfoList[index].version = version; + d->packageInfoList[index].lastUpdateDate = date; + d->modified = true; + return true; +} + +/*! + Remove the package with \a name. + */ +bool PackagesInfo::removePackage(const QString &name) +{ + const int index = findPackageInfo(name); + if (index == -1) + return false; + + d->packageInfoList.remove(index); + d->modified = true; + return true; +} + +static void addTextChildHelper(QDomNode *node, + const QString &tag, + const QString &text, + const QString &attributeName = QString(), + const QString &attributeValue = QString()) +{ + QDomElement domElement = node->ownerDocument().createElement(tag); + QDomText domText = node->ownerDocument().createTextNode(text); + + domElement.appendChild(domText); + if (!attributeName.isEmpty()) + domElement.setAttribute(attributeName, attributeValue); + node->appendChild(domElement); +} + +void PackagesInfo::writeToDisk() +{ + if (d->modified && (!d->packageInfoList.isEmpty() || QFile::exists(d->fileName))) { + QDomDocument doc; + QDomElement root = doc.createElement(QLatin1String("Packages")) ; + doc.appendChild(root); + + addTextChildHelper(&root, QLatin1String("ApplicationName"), d->applicationName); + addTextChildHelper(&root, QLatin1String("ApplicationVersion"), d->applicationVersion); + if (d->compatLevel != -1) + addTextChildHelper(&root, QLatin1String( "CompatLevel" ), QString::number(d->compatLevel)); + + Q_FOREACH (const PackageInfo &info, d->packageInfoList) { + QDomElement package = doc.createElement(QLatin1String("Package")); + + addTextChildHelper(&package, QLatin1String("Name"), info.name); + addTextChildHelper(&package, QLatin1String("Pixmap"), info.pixmap); + addTextChildHelper(&package, QLatin1String("Title"), info.title); + addTextChildHelper(&package, QLatin1String("Description"), info.description); + if (info.inheritVersionFrom.isEmpty()) + addTextChildHelper(&package, QLatin1String("Version"), info.version); + else + addTextChildHelper(&package, QLatin1String("Version"), info.version, + QLatin1String("inheritVersionFrom"), info.inheritVersionFrom); + addTextChildHelper(&package, QLatin1String("LastUpdateDate"), info.lastUpdateDate.toString(Qt::ISODate)); + addTextChildHelper(&package, QLatin1String("InstallDate"), info.installDate.toString(Qt::ISODate)); + addTextChildHelper(&package, QLatin1String("Size"), QString::number(info.uncompressedSize)); + QString assembledDependencies = QLatin1String(""); + Q_FOREACH (const QString & val, info.dependencies) { + assembledDependencies += val + QLatin1String(","); + } + if (info.dependencies.count() > 0) + assembledDependencies.chop(1); + addTextChildHelper(&package, QLatin1String("Dependencies"), assembledDependencies); + if (info.forcedInstallation) + addTextChildHelper(&package, QLatin1String("ForcedInstallation"), QLatin1String("true")); + if (info.virtualComp) + addTextChildHelper(&package, QLatin1String("Virtual"), QLatin1String("true")); + + root.appendChild(package); + } + + // Open Packages.xml + QFile file(d->fileName); + if (!file.open(QFile::WriteOnly)) + return; + + file.write(doc.toByteArray(4)); + file.close(); + d->modified = false; + } +} + +void PackagesInfo::PackagesInfoData::addPackageFrom(const QDomElement &packageE) +{ + if (packageE.isNull()) + return; + + QDomNodeList childNodes = packageE.childNodes(); + if (childNodes.count() == 0) + return; + + PackageInfo info; + info.forcedInstallation = false; + info.virtualComp = false; + for (int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.item(i); + QDomElement childNodeE = childNode.toElement(); + if (childNodeE.isNull()) + continue; + + if (childNodeE.tagName() == QLatin1String("Name")) + info.name = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Pixmap")) + info.pixmap = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Title")) + info.title = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Description")) + info.description = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Version")) { + info.version = childNodeE.text(); + info.inheritVersionFrom = childNodeE.attribute(QLatin1String("inheritVersionFrom")); + } + else if (childNodeE.tagName() == QLatin1String("Virtual")) + info.virtualComp = childNodeE.text().toLower() == QLatin1String("true") ? true : false; + else if (childNodeE.tagName() == QLatin1String("Size")) + info.uncompressedSize = childNodeE.text().toULongLong(); + else if (childNodeE.tagName() == QLatin1String("Dependencies")) + info.dependencies = childNodeE.text().split(QRegExp(QLatin1String("\\b(,|, )\\b")), QString::SkipEmptyParts); + else if (childNodeE.tagName() == QLatin1String("ForcedInstallation")) + info.forcedInstallation = childNodeE.text().toLower() == QLatin1String( "true" ) ? true : false; + else if (childNodeE.tagName() == QLatin1String("LastUpdateDate")) + info.lastUpdateDate = QDate::fromString(childNodeE.text(), Qt::ISODate); + else if (childNodeE.tagName() == QLatin1String("InstallDate")) + info.installDate = QDate::fromString(childNodeE.text(), Qt::ISODate); + } + + this->packageInfoList.append(info); +} + +/*! + Clears the installed package list. +*/ +void PackagesInfo::clearPackageInfoList() +{ + d->packageInfoList.clear(); + d->modified = true; + emit reset(); +} + +/*! + \fn void KDUpdater::PackagesInfo::reset() + + This signal is emitted whenever the contents of this class is refreshed, usually from within + the \ref refresh() slot. +*/ + +/*! + \ingroup kdupdater + \struct KDUpdater::PackageInfo kdupdaterpackagesinfo.h KDUpdaterPackageInfo + \brief Describes a single installed package in the application. + + This structure contains information about a single installed package in the application. + The information contained in this structure corresponds to the information described + by the Package XML element in Packages.xml +*/ + +/*! + \var QString KDUpdater::PackageInfo::name +*/ + +/*! + \var QString KDUpdater::PackageInfo::pixmap +*/ + +/*! + \var QString KDUpdater::PackageInfo::title +*/ + +/*! + \var QString KDUpdater::PackageInfo::description +*/ + +/*! + \var QString KDUpdater::PackageInfo::version +*/ + +/*! + \var QDate KDUpdater::PackageInfo::lastUpdateDate +*/ + +/*! + \var QDate KDUpdater::PackageInfo::installDate +*/ diff --git a/src/libs/kdtools/kdupdaterpackagesinfo.h b/src/libs/kdtools/kdupdaterpackagesinfo.h new file mode 100644 index 000000000..a70a70568 --- /dev/null +++ b/src/libs/kdtools/kdupdaterpackagesinfo.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_PACKAGES_INFO_H +#define KD_UPDATER_PACKAGES_INFO_H + +#include "kdupdater.h" + +#include <QObject> +#include <QDate> +#include <QString> +#include <QStringList> +#include <QVariant> + +namespace KDUpdater { + +class Application; +class UpdateInstaller; + +struct KDTOOLS_EXPORT PackageInfo +{ + QString name; + QString pixmap; + QString title; + QString description; + QString version; + QString inheritVersionFrom; + QStringList dependencies; + QStringList translations; + QDate lastUpdateDate; + QDate installDate; + bool forcedInstallation; + bool virtualComp; + quint64 uncompressedSize; +}; + +class KDTOOLS_EXPORT PackagesInfo : public QObject +{ + Q_OBJECT + +public: + ~PackagesInfo(); + + enum Error + { + NoError = 0, + NotYetReadError, + CouldNotReadPackageFileError, + InvalidXmlError, + InvalidContentError + }; + + Application *application() const; + + bool isValid() const; + QString errorString() const; + Error error() const; + void clearPackageInfoList(); + + void setFileName(const QString &fileName); + QString fileName() const; + + void setApplicationName(const QString &name); + QString applicationName() const; + + void setApplicationVersion(const QString &version); + QString applicationVersion() const; + + int packageInfoCount() const; + PackageInfo packageInfo(int index) const; + int findPackageInfo(const QString &pkgName) const; + QVector<KDUpdater::PackageInfo> packageInfos() const; + void writeToDisk(); + + int compatLevel() const; + void setCompatLevel(int level); + + bool installPackage(const QString &pkgName, const QString &version, const QString &title = QString(), + const QString &description = QString(), const QStringList &dependencies = QStringList(), + bool forcedInstallation = false, bool virtualComp = false, quint64 uncompressedSize = 0, + const QString &inheritVersionFrom = QString()); + + bool updatePackage(const QString &pkgName, const QString &version, const QDate &date); + bool removePackage(const QString &pkgName); + +public Q_SLOTS: + void refresh(); + +Q_SIGNALS: + void reset(); + +protected: + explicit PackagesInfo(Application *application = 0); + +private: + friend class Application; + friend class UpdateInstaller; + struct PackagesInfoData; + PackagesInfoData *d; +}; + +} // KDUpdater + +#endif // KD_UPDATER_PACKAGES_INFO_H diff --git a/src/libs/kdtools/kdupdatersignatureverificationrunnable.cpp b/src/libs/kdtools/kdupdatersignatureverificationrunnable.cpp new file mode 100644 index 000000000..1e00ca43f --- /dev/null +++ b/src/libs/kdtools/kdupdatersignatureverificationrunnable.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdatersignatureverificationrunnable.h" +#include "kdupdatersignatureverifier.h" +#include "kdupdatersignatureverificationresult.h" + +#include <QByteArray> +#include <QIODevice> +#include <QMetaObject> +#include <QObject> +#include <QPointer> +#include <QThreadPool> +#include <QVariant> +#include <QVector> + +#include <cassert> + +using namespace KDUpdater; + +class Runnable::Private { +public: + QVector<QObject*> receivers; + QVector<QByteArray> methods; +}; + +Runnable::Runnable() : QRunnable(), d( new Private ) { +} + +Runnable::~Runnable() { + delete d; +} + + +void Runnable::addResultListener( QObject* receiver, const char* method ) { + d->receivers.push_back( receiver ); + d->methods.push_back( QByteArray( method ) ); +} + +void Runnable::emitResult( const QGenericArgument& arg0, + const QGenericArgument& arg1, + const QGenericArgument& arg2, + const QGenericArgument& arg3, + const QGenericArgument& arg4, + const QGenericArgument& arg5, + const QGenericArgument& arg6, + const QGenericArgument& arg7, + const QGenericArgument& arg8, + const QGenericArgument& arg9 ) { + assert( d->receivers.size() == d->methods.size() ); + for ( int i = 0; i < d->receivers.size(); ++i ) { + QMetaObject::invokeMethod( d->receivers[i], + d->methods[i].constData(), + Qt::QueuedConnection, + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9 ); + } +} + +class SignatureVerificationRunnable::Private { +public: + Private() : verifier( 0 ) {} + const SignatureVerifier* verifier; + QPointer<QIODevice> device; + QByteArray signature; +}; + +SignatureVerificationRunnable::SignatureVerificationRunnable() : Runnable(), d( new Private ) { +} + +SignatureVerificationRunnable::~SignatureVerificationRunnable() { + delete d; +} + +const SignatureVerifier* SignatureVerificationRunnable::verifier() const { + return d->verifier; +} + +void SignatureVerificationRunnable::setVerifier( const SignatureVerifier* verifier ) { + delete d->verifier; + d->verifier = verifier ? verifier->clone() : 0; +} + +QByteArray SignatureVerificationRunnable::signature() const { + return d->signature; +} + +void SignatureVerificationRunnable::setSignature( const QByteArray& sig ) { + d->signature = sig; +} + +QIODevice* SignatureVerificationRunnable::data() const { + return d->device; +} + +void SignatureVerificationRunnable::setData( QIODevice* device ) { + d->device = device; +} + + +void SignatureVerificationRunnable::run() { + QThreadPool::globalInstance()->releaseThread(); + const SignatureVerificationResult result = d->verifier->verify( d->device->readAll(), d->signature ); + QThreadPool::globalInstance()->reserveThread(); + delete d->verifier; + delete d->device; + emitResult( Q_ARG( KDUpdater::SignatureVerificationResult, result ) ); +} + + diff --git a/src/libs/kdtools/kdupdatersignatureverificationrunnable.h b/src/libs/kdtools/kdupdatersignatureverificationrunnable.h new file mode 100644 index 000000000..901689253 --- /dev/null +++ b/src/libs/kdtools/kdupdatersignatureverificationrunnable.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KDUPDATERSIGNATUREVERIFICATIONJOB_H +#define KDUPDATERSIGNATUREVERIFICATIONJOB_H + +#include <kdtoolsglobal.h> + +#include <QtCore/QGenericArgument> +#include <QtCore/QRunnable> + +QT_BEGIN_NAMESPACE +class QByteArray; +class QIODevice; +class QObject; +template <typename T> class QVector; +QT_END_NAMESPACE + +namespace KDUpdater { + +class SignatureVerifier; +class SignatureVerificationResult; + +class Runnable : public QRunnable +{ +public: + Runnable(); + ~Runnable(); + + void addResultListener(QObject *receiver, const char *method); + +protected: + void emitResult(const QGenericArgument &arg0 = QGenericArgument(0), + const QGenericArgument &arg1 = QGenericArgument(), + const QGenericArgument &arg2 = QGenericArgument(), + const QGenericArgument &arg3 = QGenericArgument(), + const QGenericArgument &arg4 = QGenericArgument(), + const QGenericArgument &arg5 = QGenericArgument(), + const QGenericArgument &arg6 = QGenericArgument(), + const QGenericArgument &arg7 = QGenericArgument(), + const QGenericArgument &arg8 = QGenericArgument(), + const QGenericArgument &arg9 = QGenericArgument()); + +private: + class Private; + Private *d; +}; + +class SignatureVerificationRunnable : public Runnable +{ +public: + explicit SignatureVerificationRunnable(); + ~SignatureVerificationRunnable(); + + const SignatureVerifier *verifier() const; + void setVerifier(const SignatureVerifier *verifier); + + QByteArray signature() const; + void setSignature(const QByteArray &sig); + + QIODevice *data() const; + void setData(QIODevice *device); + + void run(); + +private: + class Private; + Private *d; +}; + +} // namespace KDUpdater + +#endif // KDUPDATERSIGNATUREVERIFICATIONJOB_H diff --git a/src/libs/kdtools/kdupdatertask.cpp b/src/libs/kdtools/kdupdatertask.cpp new file mode 100644 index 000000000..fb8118fbd --- /dev/null +++ b/src/libs/kdtools/kdupdatertask.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdatertask.h" + +using namespace KDUpdater; + +/*! + \ingroup kdupdater + \class KDUpdater::Task kdupdatertask.h KDUpdaterTask + \brief Base class for all task classes in KDUpdater + + This class is the base class for all task classes in KDUpdater. Task is an activity that + occupies certain amount of execution time. It can be started, stopped (or canceled), paused and + resumed. Tasks can report progress and error messages which an application can show in any + sort of UI. The KDUpdater::Task class provides a common interface for dealing with all kinds of + tasks in KDUpdater. The class diagram show in this class documentation will help in pointing out + the task classes in KDUpdater. + + User should be carefull of these points: + \li Instances of this class cannot be created. Only instance of the subclasses can be created + \li Task classes can be started only once. +*/ + +/*! + \internal +*/ +KDUpdater::Task::Task(const QString &name, int caps, QObject *parent) + : QObject(parent) + , m_caps(caps) + , m_name(name) + , m_errorCode(0) + , m_started(false) + , m_finished(false) + , m_paused(false) + , m_stopped(false) + , m_progressPc(0) + , m_autoDelete(true) +{ +} + +/*! + \internal +*/ +Task::~Task() +{} + +/*! + Returns the name of the task. +*/ +QString Task::name() const +{ + return m_name; +} + +/*! + Returns the capabilities of the task. It is a combination of one or more + Capability flags. Defined as follows + \code + enum Task::Capability + { + NoCapability = 0, + Pausable = 1, + Stoppable = 2 + }; + \endcode +*/ +int Task::capabilities() const +{ + return m_caps; +} + +/*! + Returns the last reported error code. +*/ +int Task::error() const +{ + return m_errorCode; +} + +/*! + Returns the last reported error message text. +*/ +QString Task::errorString() const +{ + return m_errorText; +} + +/*! + Returns whether the task has started and is running or not. +*/ +bool Task::isRunning() const +{ + return m_started; +} + +/*! + Returns whether the task has finished or not. + + \note Stopped (or canceled) tasks are not finished tasks. +*/ +bool Task::isFinished() const +{ + return m_finished; +} + +/*! + Returns whether the task is paused or not. +*/ +bool Task::isPaused() const +{ + return m_paused; +} + +/*! + Returns whether the task is stopped or not. + + \note Finished tasks are not stopped classes. +*/ +bool Task::isStopped() const +{ + return m_stopped; +} + +/*! + Returns the progress in percentage made by this task. +*/ +int Task::progressPercent() const +{ + return m_progressPc; +} + +/*! + Returns a string that describes the progress made by this task as a string. +*/ +QString Task::progressText() const +{ + return m_progressText; +} + +/*! + Starts the task. +*/ +void Task::run() +{ + if (m_started) { + qDebug("Trying to start an already started task"); + return; + } + + if (m_finished || m_stopped) { + qDebug("Trying to start a finished or canceled task"); + return; + } + + m_stopped = false; + m_finished = false; // for the sake of completeness + m_started = true; + emit started(); + reportProgress(0, tr("%1 started").arg(m_name)); + + doRun(); +} + +/*! + Stops the task, provided the task has \ref Stoppable capability. + + \note Once the task is stopped, it cannot be restarted. +*/ +void Task::stop() +{ + if (!(m_caps & Stoppable)) { + const QString errorMsg = tr("%1 cannot be stopped").arg(m_name); + reportError(ECannotStopTask, errorMsg); + return; + } + + if (!m_started) { + qDebug("Trying to stop an unstarted task"); + return; + } + + if(m_finished || m_stopped) + { + qDebug("Trying to stop a finished or canceled task"); + return; + } + + m_stopped = doStop(); + if (!m_stopped) { + const QString errorMsg = tr("Cannot stop task %1").arg(m_name); + reportError(ECannotStopTask, errorMsg); + return; + } + + m_started = false; // the task is not running + m_finished = false; // the task is not finished, but was canceled half-way through + + emit stopped(); + if (m_autoDelete) + deleteLater(); +} + +/*! + Paused the task, provided the task has \ref Pausable capability. +*/ +void Task::pause() +{ + if (!(m_caps & Pausable)) { + const QString errorMsg = tr("%1 cannot be paused").arg(m_name); + reportError(ECannotPauseTask, errorMsg); + return; + } + + if (!m_started) { + qDebug("Trying to pause an unstarted task"); + return; + } + + if (m_finished || m_stopped) { + qDebug("Trying to pause a finished or canceled task"); + return; + } + + m_paused = doPause(); + + if (!m_paused) { + const QString errorMsg = tr("Cannot pause task %1").arg(m_name); + reportError(ECannotPauseTask, errorMsg); + return; + } + + // The task state has to be started, paused but not finished or stopped. + // We need not set the flags below, but just in case. + // Perhaps we should do Q_ASSERT() ??? + m_started = true; + m_finished = false; + m_stopped = false; + + emit paused(); +} + +/*! + Resumes the task if it was paused. +*/ +void Task::resume() +{ + if (!m_paused) { + qDebug("Trying to resume an unpaused task"); + return; + } + + const bool val = doResume(); + + if (!val) { + const QString errorMsg = tr("Cannot resume task %1").arg(m_name); + reportError(ECannotResumeTask, errorMsg); + return; + } + + // The task state should be started, but not paused, finished or stopped. + // We need not set the flags below, but just in case. + // Perhaps we should do Q_ASSERT() ??? + m_started = true; + m_paused = false; + m_finished = false; + m_stopped = false; + + emit resumed(); +} + +/*! + \internal +*/ +void Task::reportProgress(int percent, const QString &text) +{ + if (m_progressPc == percent) + return; + + m_progressPc = percent; + m_progressText = text; + emit progressValue(m_progressPc); + emit progressText(m_progressText); +} + +/*! + \internal +*/ +void Task::reportError(int errorCode, const QString &errorText) +{ + m_errorCode = errorCode; + m_errorText = errorText; + + emit error(m_errorCode, m_errorText); + if (m_autoDelete) + deleteLater(); +} + +/*! + \internal +*/ +void Task::reportError(const QString &errorText) +{ + reportError(EUnknown, errorText); +} + +/*! + \internal +*/ +void Task::reportDone() +{ + QString msg = tr("%1 done"); + reportProgress(100, msg); + + // State should be finished, but not started, paused or stopped. + m_finished = true; + m_started = false; + m_paused = false; + m_stopped = false; + m_errorCode = 0; + m_errorText.clear(); + + emit finished(); + if (m_autoDelete) + deleteLater(); +} + +bool Task::autoDelete() const +{ + return m_autoDelete; +} + +void Task::setAutoDelete(bool autoDelete) +{ + m_autoDelete = autoDelete; +} + +/*! + \fn virtual bool KDUpdater::Task::doStart() = 0; +*/ + +/*! + \fn virtual bool KDUpdater::Task::doStop() = 0; +*/ + +/*! + \fn virtual bool KDUpdater::Task::doPause() = 0; +*/ + +/*! + \fn virtual bool KDUpdater::Task::doResume() = 0; +*/ + +/*! + \signal void KDUpdater::Task::error(int code, const QString& errorText) + + This signal is emitted to notify an error during the execution of this task. + \param code Error code + \param errorText A string describing the error. + + Error codes are just integers, there are however built in errors represented + by the KDUpdater::Error enumeration + \code + enum Error + { + ECannotStartTask, + ECannotPauseTask, + ECannotResumeTask, + ECannotStopTask, + EUnknown + }; + \endcode +*/ + +/*! + \signal void KDUpdater::Task::progress(int percent, const QString& progressText) + + This signal is emitted to nofity progress made by the task. + + \param percent Percentage of progress made + \param progressText A string describing the progress made +*/ + +/*! + \signal void KDUpdater::Task::started() + + This signal is emitted when the task has started. +*/ + +/*! + \signal void KDUpdater::Task::paused() + + This signal is emitted when the task has paused. +*/ + +/*! + \signal void KDUpdater::Task::resumed() + + This signal is emitted when the task has resumed. +*/ + +/*! + \signal void KDUpdater::Task::stopped() + + This signal is emitted when the task has stopped (or canceled). +*/ + +/*! + \signal void KDUpdater::Task::finished() + + This signal is emitted when the task has finished. +*/ diff --git a/src/libs/kdtools/kdupdatertask.h b/src/libs/kdtools/kdupdatertask.h new file mode 100644 index 000000000..4b2b2e4f5 --- /dev/null +++ b/src/libs/kdtools/kdupdatertask.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_TASK_H +#define KD_UPDATER_TASK_H + +#include "kdupdater.h" + +#include <QObject> + +namespace KDUpdater { + +class KDTOOLS_EXPORT Task : public QObject +{ + Q_OBJECT + +public: + enum Capability + { + NoCapability = 0, + Pausable = 1, + Stoppable = 2 + }; + + virtual ~Task(); + + QString name() const; + int capabilities() const; + + int error() const; + QString errorString() const; + + bool isRunning() const; + bool isFinished() const; + bool isPaused() const; + bool isStopped() const; + + int progressPercent() const; + QString progressText() const; + + bool autoDelete() const; + void setAutoDelete(bool autoDelete); + +public Q_SLOTS: + void run(); + void stop(); + void pause(); + void resume(); + +Q_SIGNALS: + void error(int code, const QString &errorText); + void progressValue(int percent); + void progressText(const QString &progressText); + void started(); + void paused(); + void resumed(); + void stopped(); + void finished(); + +protected: + explicit Task(const QString &name, int caps = NoCapability, QObject *parent = 0); + void reportProgress(int percent, const QString &progressText); + void reportError(int errorCode, const QString &errorText); + void reportError(const QString &errorText); + void reportDone(); + + // Task interface + virtual void doRun() = 0; + virtual bool doStop() = 0; + virtual bool doPause() = 0; + virtual bool doResume() = 0; + +private: + int m_caps; + QString m_name; + int m_errorCode; + QString m_errorText; + bool m_started; + bool m_finished; + bool m_paused; + bool m_stopped; + int m_progressPc; + QString m_progressText; + bool m_autoDelete; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_TASK_H diff --git a/src/libs/kdtools/kdupdaterupdate.cpp b/src/libs/kdtools/kdupdaterupdate.cpp new file mode 100644 index 000000000..02d7fbc9a --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdate.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdate.h" +#include "kdupdaterapplication.h" +#include "kdupdaterupdatesourcesinfo.h" +#include "kdupdaterfiledownloader_p.h" +#include "kdupdaterfiledownloaderfactory.h" +#include "kdupdaterupdateoperations.h" +#include "kdupdaterupdateoperationfactory.h" + +#include <QFile> + +using namespace KDUpdater; + +/*! + \ingroup kdupdater + \class KDUpdater::Update kdupdaterupdate.h KDUpdaterUpdate + \brief Represents a single update + + The KDUpdater::Update class contains information and mechanisms to download one update. It is + created by KDUpdater::UpdateFinder and is used by KDUpdater::UpdateInstaller to download the UpdateFile + corresponding to the update. + + The class makes use of appropriate network protocols (HTTP, HTTPS, FTP, or Local File Copy) to + download the UpdateFile. + + The constructor of the KDUpdater::Update class is made protected, because it can be instantiated only by + KDUpdater::UpdateFinder (which is a friend class). The destructor however is public. +*/ + +struct Update::UpdateData +{ + UpdateData(Update *qq) : + q(qq), + application(0), + compressedSize(0), + uncompressedSize(0) + {} + + Update *q; + Application *application; + UpdateSourceInfo sourceInfo; + QMap<QString, QVariant> data; + QUrl updateUrl; + UpdateType type; + QList<UpdateOperation *> operations; + QByteArray sha1sum; + + quint64 compressedSize; + quint64 uncompressedSize; + + FileDownloader *fileDownloader; +}; + + +/*! + \internal +*/ +Update::Update(Application *application, const UpdateSourceInfo &sourceInfo, + UpdateType type, const QUrl &updateUrl, const QMap<QString, QVariant> &data, + quint64 compressedSize, quint64 uncompressedSize, const QByteArray &sha1sum) + : Task(QLatin1String("Update"), Stoppable, application), + d(new UpdateData(this)) +{ + d->application = application; + d->sourceInfo = sourceInfo; + d->data = data; + d->updateUrl = updateUrl; + d->type = type; + + d->compressedSize = compressedSize; + d->uncompressedSize = uncompressedSize; + d->sha1sum = sha1sum; + + d->fileDownloader = FileDownloaderFactory::instance().create(updateUrl.scheme(), this); + if (d->fileDownloader) { + d->fileDownloader->setUrl(d->updateUrl); + d->fileDownloader->setSha1Sum(d->sha1sum); + connect(d->fileDownloader, SIGNAL(downloadProgress(double)), this, SLOT(downloadProgress(double))); + connect(d->fileDownloader, SIGNAL(downloadCanceled()), this, SIGNAL(stopped())); + connect(d->fileDownloader, SIGNAL(downloadCompleted()), this, SIGNAL(finished())); + } + + switch (type) { + case NewPackage: + case PackageUpdate: { + UpdateOperation *packageOperation = UpdateOperationFactory::instance().create(QLatin1String("UpdatePackage")); + QStringList args; + args << data.value(QLatin1String("Name")).toString() + << data.value(QLatin1String("Version")).toString() + << data.value(QLatin1String("ReleaseDate")).toString(); + packageOperation->setArguments(args); + packageOperation->setApplication(application); + d->operations.append(packageOperation); + break; + } + case CompatUpdate: { + UpdateOperation *compatOperation = UpdateOperationFactory::instance().create(QLatin1String("UpdateCompatLevel")); + QStringList args; + args << data.value(QLatin1String("CompatLevel")).toString(); + compatOperation->setArguments(args); + compatOperation->setApplication(application); + d->operations.append(compatOperation); + break; + } + default: + break; + } +} + +/*! + Destructor +*/ +Update::~Update() +{ + const QString fileName = this->downloadedFileName(); + if (!fileName.isEmpty()) + QFile::remove(fileName); + qDeleteAll(d->operations); + d->operations.clear(); + delete d; +} + +/*! + Returns the application for which this class is downloading the UpdateFile +*/ +Application *Update::application() const +{ + return d->application; +} + +/*! + Returns the release date of the update downloaded by this class +*/ +QDate Update::releaseDate() const +{ + return d->data.value(QLatin1String("ReleaseDate")).toDate(); +} + +/*! + Returns data whose name is given in parameter, or an invalid QVariant if the data doesn't exist. +*/ +QVariant Update::data(const QString &name, const QVariant &defaultValue) const +{ + if (d->data.contains(name)) + return d->data.value(name); + return defaultValue; +} + +/*! + Returns the complete URL of the UpdateFile downloaded by this class. +*/ +QUrl Update::updateUrl() const +{ + return d->updateUrl; +} + +/*! + Returns the update source info on which this update was created. +*/ +UpdateSourceInfo Update::sourceInfo() const +{ + return d->sourceInfo; +} + +/*! + * Returns the type of update + */ +UpdateType Update::type() const +{ + return d->type; +} + +/*! + Returns true of the update can be downloaded, false otherwise. The function + returns false if the URL scheme is not supported by this class. +*/ +bool Update::canDownload() const +{ + return d->fileDownloader && d->fileDownloader->canDownload(); +} + +/*! + Returns true of the update has been downloaded. If this function returns true + the you can use the \ref downloadedFileName() method to get the complete name + of the downloaded UpdateFile. + + \note: The downloaded UpdateFile will be deleted when this class is destroyed +*/ +bool Update::isDownloaded() const +{ + return d->fileDownloader && d->fileDownloader->isDownloaded(); +} + +/*! + Returns the name of the downloaded UpdateFile after the download is complete, ie + when \ref isDownloaded() returns true. +*/ +QString Update::downloadedFileName() const +{ + if (d->fileDownloader) + return d->fileDownloader->downloadedFileName(); + + return QString(); +} + +/*! + \internal +*/ +void Update::downloadProgress(double value) +{ + Q_ASSERT(value <= 1); + reportProgress(value * 100, tr("Downloading update...")); +} + +/*! + \internal +*/ +void Update::downloadCompleted() +{ + reportProgress(100, tr("Update downloaded")); + reportDone(); +} + +/*! + \internal +*/ +void Update::downloadAborted(const QString &msg) +{ + reportError(msg); +} + +/*! + \internal +*/ +void Update::doRun() +{ + if (d->fileDownloader) + d->fileDownloader->download(); +} + +/*! + \internal +*/ +bool Update::doStop() +{ + if (d->fileDownloader) + d->fileDownloader->cancelDownload(); + return true; +} + +/*! + \internal +*/ +bool Update::doPause() +{ + return false; +} + +/*! + \internal +*/ +bool Update::doResume() +{ + return false; +} + +/*! + Returns a list of operations needed by this update. For example, package update needs to change + the package version, compat update needs to change the compat level... + */ +QList<UpdateOperation *> Update::operations() const +{ + return d->operations; +} + +/*! + * Returns the compressed size of this update's data file. + */ +quint64 Update::compressedSize() const +{ + return d->compressedSize; +} + +/*! + * Returns the uncompressed size of this update's data file. + */ +quint64 Update::uncompressedSize() const +{ + return d->uncompressedSize; +} diff --git a/src/libs/kdtools/kdupdaterupdate.h b/src/libs/kdtools/kdupdaterupdate.h new file mode 100644 index 000000000..0cf614f56 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdate.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_H +#define KD_UPDATER_UPDATE_H + +#include "kdupdater.h" +#include "kdupdatertask.h" + +#include <QUrl> +#include <QDate> +#include <QMap> +#include <QVariant> +#include <QList> + +namespace KDUpdater { + +class Application; +struct UpdateSourceInfo; +class UpdateFinder; +class UpdateOperation; + +class KDTOOLS_EXPORT Update : public Task +{ + Q_OBJECT + +public: + ~Update(); + + Application *application() const; + + UpdateType type() const; + QUrl updateUrl() const; + QDate releaseDate() const; + QVariant data(const QString &m_name, const QVariant &defaultValue = QVariant()) const; + UpdateSourceInfo sourceInfo() const; + + bool canDownload() const; + bool isDownloaded() const; + void download() { run(); } + QString downloadedFileName() const; + + QList<UpdateOperation *> operations() const; + + quint64 compressedSize() const; + quint64 uncompressedSize() const; + +private Q_SLOTS: + void downloadProgress(double); + void downloadAborted(const QString &msg); + void downloadCompleted(); + +private: + friend class UpdateFinder; + struct UpdateData; + UpdateData *d; + + void doRun(); + bool doStop(); + bool doPause(); + bool doResume(); + + Update(Application *application, const UpdateSourceInfo &sourceInfo, + UpdateType type, const QUrl &updateUrl, const QMap<QString, QVariant> &data, + quint64 compressedSize, quint64 uncompressedSize, const QByteArray &sha1sum); +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_H diff --git a/src/libs/kdtools/kdupdaterupdatefinder.cpp b/src/libs/kdtools/kdupdaterupdatefinder.cpp new file mode 100644 index 000000000..63631a8e5 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatefinder.cpp @@ -0,0 +1,842 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdatefinder.h" +#include "kdupdaterapplication.h" +#include "kdupdaterupdatesourcesinfo.h" +#include "kdupdaterpackagesinfo.h" +#include "kdupdaterupdate.h" +#include "kdupdaterfiledownloader_p.h" +#include "kdupdaterfiledownloaderfactory.h" +#include "kdupdaterupdatesinfo_p.h" + +#include <QCoreApplication> +#include <QDebug> + +using namespace KDUpdater; + +/*! + \ingroup kdupdater + \class KDUpdater::UpdateFinder kdupdaterupdatefinder KDUpdaterUpdateFinder + \brief Finds updates applicable for a \ref KDUpdater::Application + + The KDUpdater::UpdateFinder class helps in searching for updates and installing them on the application. The + class basically processes the application's \ref KDUpdater::PackagesInfo and the UpdateXMLs it aggregates + from all the update sources described in KDUpdater::UpdateSourcesInfo and populates a list of + \ref KDUpdater::Update objects. This list can then be passed to \ref KDUpdater::UpdateInstaller for + actually downloading and installing the updates. + + + Usage: + \code + KDUpdater::UpdateFinder updateFinder( application ); + QProgressDialog finderProgressDlg; + + QObject::connect( &updateFinder, SIGNAL(progressValue(int)), + &finderProgressDlg, SLOT(setValue(int))); + QObject::connect( &updateFinder, SIGNAL(computeUpdatesCompleted()), + &finderProgressDlg, SLOT(accept())); + QObject::connect( &updateFinder, SIGNAL(computeUpdatesCanceled()), + &finderProgressDlg, SLOT(reject())); + + QObject::connect( &finderProgressDlg, SIGNAL(canceled()), + &updateFinder, SLOT(cancelComputeUpdates())); + + updateFinder.run(); + finderProgressDlg.exec(); + + // Control comes here after update finding is done or canceled. + + QList<KDUpdater::Update*> updates = updateFinder.updates(); + KDUpdater::UpdateInstaller updateInstaller; + updateInstaller.installUpdates( updates ); + +\endcode +*/ + + +// +// Private +// +class UpdateFinder::Private +{ +public: + Private(UpdateFinder *qq) : + q(qq), + application(0), + updateType(PackageUpdate) + {} + + ~Private() + { + qDeleteAll(updates); + qDeleteAll(updatesInfoList); + qDeleteAll(updateXmlFDList); + } + + UpdateFinder *q; + Application *application; + QList<Update *> updates; + UpdateTypes updateType; + + // Temporary structure that notes down information about updates. + bool cancel; + int downloadCompleteCount; + QList<UpdateSourceInfo> updateSourceInfoList; + QList<UpdatesInfo *> updatesInfoList; + QList<FileDownloader *> updateXmlFDList; + + void clear(); + void computeUpdates(); + void cancelComputeUpdates(); + bool downloadUpdateXMLFiles(); + bool computeApplicableUpdates(); + + QList<UpdateInfo> applicableUpdates(UpdatesInfo *updatesInfo, + bool addNewPackages = false); + void createUpdateObjects(const UpdateSourceInfo &sourceInfo, + const QList<UpdateInfo> &updateInfoList); + bool checkForUpdatePriority(const UpdateSourceInfo &sourceInfo, + const UpdateInfo &updateInfo); + int pickUpdateFileInfo(const QList<UpdateFileInfo> &updateFiles); + void slotDownloadDone(); +}; + + +static int computeProgressPercentage(int min, int max, int percent) +{ + return min + qint64(max-min) * percent / 100; +} + +static int computePercent(int done, int total) +{ + return total ? done * Q_INT64_C(100) / total : 0 ; +} + +/*! + \internal + + Releases all internal resources consumed while downloading and computing updates. +*/ +void UpdateFinder::Private::clear() +{ + qDeleteAll(updates); + updates.clear(); + qDeleteAll(updatesInfoList); + updatesInfoList.clear(); + qDeleteAll(updateXmlFDList); + updateXmlFDList.clear(); + updateSourceInfoList.clear(); + downloadCompleteCount = 0; +} + +/*! + \internal + + This method computes the updates that can be applied on the application by + studying the application's \ref KDUpdater::PackagesInfo object and the UpdateXML files + from each of the update sources described in \ref KDUpdater::UpdateSourcesInfo. + + This function can take a long time to complete. The following signals are emitted + during the execution of this function + + The function creates \ref KDUpdater::Update objects on the stack. All KDUpdater::Update objects + are made children of the application associated with this finder. + + The update sources are fetched from the \ref KDUpdater::UpdateSourcesInfo object associated with + the application. Package information is extracted from the \ref KDUpdater::PackagesInfo object + associated with the application. + + \note Each time this function is called, all the previously computed updates are discarded + and its resources are freed. +*/ +void UpdateFinder::Private::computeUpdates() +{ + // Computing updates is done in two stages + // 1. Downloading Update XML files from all the update sources + // 2. Matching updates with Package XML and figuring out available updates + + cancel = false; + clear(); + + // First do some quick sanity checks on the packages info + PackagesInfo *packages = application->packagesInfo(); + if (!packages) { + q->reportError(tr("Could not access the package information of this application.")); + return; + } + if (!packages->isValid()) { + q->reportError(packages->errorString()); + return; + } + + // Now do some quick sanity checks on the update sources info + UpdateSourcesInfo *sources = application->updateSourcesInfo(); + if (!sources) { + q->reportError(tr("Could not access the update sources information of this application.")); + return; + } + if (!sources->isValid()) { + q->reportError(sources->errorString()); + return; + } + + // Now we can start... + + // Step 1: 0 - 49 percent + if (!downloadUpdateXMLFiles() || cancel) { + clear(); + return; + } + + // Step 2: 50 - 100 percent + if (!computeApplicableUpdates() || cancel) { + clear(); + return; + } + + // All done + q->reportProgress(100, tr("%1 updates found.").arg(updates.count())); + q->reportDone(); +} + +/*! + \internal + + Cancels the computation of updates. + + \sa \ref computeUpdates() +*/ +void UpdateFinder::Private::cancelComputeUpdates() +{ + cancel = true; +} + +/*! + \internal + + This function downloads Updates.xml from all the update sources. A single application can potentially + have several update sources, hence we need to be asynchronous in downloading updates from different + sources. + + The function basically does this for each update source: + a) Create a KDUpdater::FileDownloader and KDUpdater::UpdatesInfo for each update + b) Triggers the download of Updates.xml from each file downloader. + c) The downloadCompleted(), downloadCanceled() and downloadAborted() signals are connected + in each of the downloaders. Once all the downloads are complete and/or aborted, the next stage + would be done. + + The function gets into an event loop until all the downloads are complete. +*/ +bool UpdateFinder::Private::downloadUpdateXMLFiles() +{ + if (!application) + return false; + + UpdateSourcesInfo *updateSources = application->updateSourcesInfo(); + if (!updateSources ) + return false; + + // Create FileDownloader and UpdatesInfo for each update + for (int i = 0; i < updateSources->updateSourceInfoCount(); i++) { + UpdateSourceInfo info = updateSources->updateSourceInfo(i); + QUrl updateXmlUrl = QString::fromLatin1("%1/Updates.xml").arg(info.url.toString()); + + FileDownloader *downloader = FileDownloaderFactory::instance().create(updateXmlUrl.scheme(), q); + if (!downloader) + continue; + + downloader->setUrl(updateXmlUrl); + downloader->setAutoRemoveDownloadedFile(true); + + UpdatesInfo *updatesInfo = new UpdatesInfo; + updateSourceInfoList.append(info); + updateXmlFDList.append(downloader); + updatesInfoList.append(updatesInfo); + + connect(downloader, SIGNAL(downloadCompleted()), + q, SLOT(slotDownloadDone())); + connect(downloader, SIGNAL(downloadCanceled()), + q, SLOT(slotDownloadDone())); + connect(downloader, SIGNAL(downloadAborted(QString)), + q, SLOT(slotDownloadDone())); + } + + // Trigger download of Updates.xml file + downloadCompleteCount = 0; + for (int i = 0; i < updateXmlFDList.count(); i++) { + FileDownloader *downloader = updateXmlFDList.at(i); + downloader->download(); + } + + // Wait until all downloaders have completed their downloads. + while (true) { + QCoreApplication::processEvents(); + if (cancel) + return false; + if (downloadCompleteCount == updateXmlFDList.count()) + break; + + int pc = computePercent(downloadCompleteCount, updateXmlFDList.count()); + q->reportProgress(pc, tr("Downloading Updates.xml from update sources.")); + } + + // All the downloaders have now either downloaded or aborted the + // download of update XML files. + + // Let's now get rid of update sources whose Updates.xml could not be downloaded + for (int i = 0; i < updateXmlFDList.count(); i++) { + FileDownloader *downloader = updateXmlFDList.at(i); + if (downloader->isDownloaded()) + continue; + + UpdateSourceInfo info = updateSourceInfoList.at(i); + QString msg = tr("Could not download updates from %1 ('%2')").arg(info.name, info.url.toString()); + q->reportError(msg); + + delete updatesInfoList[i]; + delete downloader; + updateXmlFDList.removeAt(i); + updatesInfoList.removeAt(i); + updateSourceInfoList.removeAt(i); + --i; + } + + if (updatesInfoList.isEmpty()) + return false; + + // Lets parse the downloaded update XML files and get rid of the downloaders. + for (int i = 0; i < updateXmlFDList.count(); i++) { + FileDownloader *downloader = updateXmlFDList.at(i); + UpdatesInfo *updatesInfo = updatesInfoList.at(i); + + updatesInfo->setFileName(downloader->downloadedFileName()); + + if (!updatesInfo->isValid()) { + QString msg = updatesInfo->errorString(); + q->reportError(msg); + + delete updatesInfoList[i]; + delete downloader; + updateXmlFDList.removeAt(i); + updatesInfoList.removeAt(i); + --i; + } + } + qDeleteAll(updateXmlFDList); + updateXmlFDList.clear(); + + if (updatesInfoList.isEmpty()) + return false; + + q->reportProgress(49, tr("Updates.xml file(s) downloaded from update sources.")); + return true; +} + +/*! + \internal + + This function runs through all the KDUpdater::UpdatesInfo objects created during + the downloadUpdateXMLFiles() method and compares it with the data contained in + KDUpdater::PackagesInfo. Thereby figures out whether an update is applicable for + this application or not. +*/ +bool UpdateFinder::Private::computeApplicableUpdates() +{ + if (updateType & CompatUpdate) { + UpdateInfo compatUpdateInfo; + UpdateSourceInfo compatUpdateSourceInfo; + + // Required compat level + int reqCompatLevel = application->compatLevel() + 1; + + q->reportProgress(60, tr("Looking for compatibility update...")); + + // We are only interested in compat updates. + for (int i = 0; i < updatesInfoList.count(); i++) { + UpdatesInfo *info = updatesInfoList.at(i); + UpdateSourceInfo updateSource = updateSourceInfoList.at(i); + + // If we already have a compat update, just check if the source currently being + // considered has a higher priority or not. + if (compatUpdateInfo.data.contains(QLatin1String("CompatLevel")) && updateSource.priority < compatUpdateSourceInfo.priority) + continue; + + // Let's look for compat updates that provide compat level one-higher than + // the application's current compat level. + QList<UpdateInfo> updatesInfo = info->updatesInfo(CompatUpdate, reqCompatLevel); + + if (updatesInfo.count() == 0) + continue; + + compatUpdateInfo = updatesInfo.at(0); + compatUpdateSourceInfo = updateSource; + } + + bool found = compatUpdateInfo.data.contains(QLatin1String("CompatLevel")); + if (found) { + q->reportProgress(80, tr("Found compatibility update.")); + + // Create an update for this compat update. + // Pick a update file based on arch and OS. + int pickUpdateFileIndex = pickUpdateFileInfo(compatUpdateInfo.updateFiles); + if (pickUpdateFileIndex < 0) { + q->reportError(tr("Compatibility update for the required architecture and hardware configuration was " + "not found.")); + q->reportProgress(100, tr("Compatibility update not found.")); + return false; + } + + UpdateFileInfo fileInfo = compatUpdateInfo.updateFiles.at(pickUpdateFileIndex); + + // Create an update for this entry + QUrl url = QString::fromLatin1("%1/%2").arg( compatUpdateSourceInfo.url.toString(), fileInfo.fileName); + Update *update = q->constructUpdate(application, compatUpdateSourceInfo, CompatUpdate, + url, compatUpdateInfo.data, fileInfo.compressedSize, + fileInfo.uncompressedSize, fileInfo.sha1sum); + + // Register the update + updates.append(update); + + // Done + q->reportProgress(100, tr("Compatibility update found.")); + } else { + q->reportProgress(100, tr("No compatibility updates found.")); + } + } + if (updateType & PackageUpdate) { + // We are looking for normal updates, not compat ones. + for (int i = 0; i < updatesInfoList.count(); i++) { + // Fetch updates applicable to this application. + UpdatesInfo *info = updatesInfoList.at(i); + QList<UpdateInfo> updates = applicableUpdates(info , updateType & NewPackage); + if (!updates.count()) + continue; + + if (cancel) + return false; + UpdateSourceInfo updateSource = updateSourceInfoList.at(i); + + // Create Update objects for updates that have a valid + // UpdateFile + createUpdateObjects(updateSource, updates); + if (cancel) + return false; + + // Report progress + int pc = computePercent(i, updatesInfoList.count()); + pc = computeProgressPercentage(51, 100, pc); + q->reportProgress(pc, tr("Computing applicable updates.")); + } + } + + q->reportProgress(99, tr("Application updates computed.")); + return true; +} + +QList<UpdateInfo> UpdateFinder::Private::applicableUpdates(UpdatesInfo *updatesInfo, bool addNewPackages) +{ + QList<UpdateInfo> retList; + + if (!updatesInfo || updatesInfo->updateInfoCount( PackageUpdate ) == 0) + return retList; + + PackagesInfo *packages = this->application->packagesInfo(); + if (!packages) + return retList; + + // Check to see if the updates info contains updates for any application + bool anyApp = updatesInfo->applicationName() == QLatin1String("{AnyApplication}"); + int appNameIndex = -1; + + if (!anyApp) { + // updatesInfo->applicationName() describes one application or a series of + // application names separated by commas. + QString appName = updatesInfo->applicationName(); + appName = appName.replace(QLatin1String( ", " ), QLatin1String( "," )); + appName = appName.replace(QLatin1String( " ," ), QLatin1String( "," )); + + // Catch hold of app names contained updatesInfo->applicationName() + QStringList apps = appName.split(QRegExp(QLatin1String("\\b(,|, )\\b")), QString::SkipEmptyParts); + appNameIndex = apps.indexOf(this->application->applicationName()); + + // If the application appName isn't one of the app names, then + // the updates are not applicable. + if (appNameIndex < 0) + return retList; + } + + // Check to see if version numbers match. This means that the version + // number of the update should be greater than the version number of + // the package that is currently installed. + QList<UpdateInfo> updateList = updatesInfo->updatesInfo(PackageUpdate); + for (int i = 0; i < updatesInfo->updateInfoCount(PackageUpdate); i++) { + UpdateInfo updateInfo = updateList.at(i); + if (!addNewPackages) { + int pkgInfoIdx = packages->findPackageInfo( updateInfo.data.value(QLatin1String("Name")).toString()); + if (pkgInfoIdx < 0) + continue; + + PackageInfo pkgInfo = packages->packageInfo(pkgInfoIdx); + + // First check to see if the update version is higher than package version + QString updateVersion = updateInfo.data.value(QLatin1String("Version")).toString(); + QString pkgVersion = pkgInfo.version; + if (KDUpdater::compareVersion(updateVersion, pkgVersion) <= 0) + continue; + + // It is quite possible that we may have already installed the update. + // Lets check the last update date of the package and the release date + // of the update. This way we can compare and figure out if the update + // has been installed or not. + QDate pkgDate = pkgInfo.lastUpdateDate; + QDate updateDate = updateInfo.data.value(QLatin1String("ReleaseDate")).toDate(); + if (pkgDate > updateDate) + continue; + } + + // Bingo, we found an update :-) + retList.append(updateInfo); + } + + return retList; +} + +void UpdateFinder::Private::createUpdateObjects(const UpdateSourceInfo &sourceInfo, const QList<UpdateInfo> &updateInfoList) +{ + for (int i = 0; i < updateInfoList.count(); i++) { + UpdateInfo info = updateInfoList.at(i); + // Compat level checks + if (info.data.contains(QLatin1String("RequiredCompatLevel")) && + info.data.value(QLatin1String("RequiredCompatLevel")).toInt() != application->compatLevel()) + { + qDebug().nospace() << "Update \"" << info.data.value( QLatin1String( "Name" ) ).toString() + << "\" at \"" << sourceInfo.name << "\"(\"" << sourceInfo.url.toString() + << "\") requires a different compat level"; + continue; // Compatibility level mismatch + } + + // If another update of the same name exists, then use the update coming from + // a higher priority. + if (!checkForUpdatePriority(sourceInfo, info)) { + qDebug().nospace() << "Skipping Update \"" + << info.data.value(QLatin1String("Name")).toString() + << "\" from \"" + << sourceInfo.name + << "\"(\"" + << sourceInfo.url.toString() + << "\") because an update with the same name was found from a higher priority location"; + + continue; + } + + // Pick a update file based on arch and OS. + int pickUpdateFileIndex = this->pickUpdateFileInfo(info.updateFiles); + if (pickUpdateFileIndex < 0) + continue; + + UpdateFileInfo fileInfo = info.updateFiles.at(pickUpdateFileIndex); + + // Create an update for this entry + QUrl url(QString::fromLatin1("%1/%2").arg( sourceInfo.url.toString(), fileInfo.fileName)); + Update *update = q->constructUpdate(application, sourceInfo, PackageUpdate, url, info.data, fileInfo.compressedSize, fileInfo.uncompressedSize, fileInfo.sha1sum); + + // Register the update + this->updates.append(update); + } +} + +bool UpdateFinder::Private::checkForUpdatePriority(const UpdateSourceInfo &sourceInfo, const UpdateInfo &updateInfo) +{ + for (int i = 0; i < this->updates.count(); i++){ + Update *update = this->updates.at(i); + if (update->data(QLatin1String("Name")).toString() != updateInfo.data.value(QLatin1String("Name")).toString()) + continue; + + // Bingo, update was previously found elsewhere. + + // If the existing update comes from a higher priority server, then cool :) + if (update->sourceInfo().priority > sourceInfo.priority) + return false; + + // If the existing update has a higher version number, keep it + if (KDUpdater::compareVersion(update->data(QLatin1String("Version")).toString(), + updateInfo.data.value(QLatin1String("Version")).toString()) > 0) + return false; + + // Otherwise the old update must be deleted. + this->updates.removeAll(update); + delete update; + + return true; + } + + // No update by that name was found, so what we have is a priority update. + return true; +} + +int UpdateFinder::Private::pickUpdateFileInfo(const QList<UpdateFileInfo> &updateFiles) +{ +#ifdef Q_WS_MAC + QString os = QLatin1String( "MacOSX" ); +#endif +#ifdef Q_WS_WIN + QString os = QLatin1String( "Windows" ); +#endif +#ifdef Q_WS_X11 + QString os = QLatin1String( "Linux" ); +#endif + + QString arch = QLatin1String( "i386" ); // only one architecture considered for now. + + for (int i = 0; i < updateFiles.count(); i++) { + UpdateFileInfo fileInfo = updateFiles.at(i); + + if (fileInfo.arch != arch) + continue; + + if (fileInfo.os != QLatin1String("Any") && fileInfo.os != os) + continue; + + return i; + } + + return -1; +} + + + +// +// UpdateFinder +// + +/*! + Constructs a update finder for a given \ref KDUpdater::Application. +*/ +UpdateFinder::UpdateFinder(Application *application) + : Task(QLatin1String("UpdateFinder"), Stoppable, application), + d(new Private(this)) +{ + d->application = application; +} + +/*! + Destructor +*/ +UpdateFinder::~UpdateFinder() +{ + delete d; +} + +/*! + Returns a pointer to the update application for which this function computes all + the updates. +*/ +Application *UpdateFinder::application() const +{ + return d->application; +} + +/*! + Returns a list of KDUpdater::Update objects. The update objects returned in this list + are made children of the \ref KDUpdater::Application object associated with this class. +*/ +QList<Update *> UpdateFinder::updates() const +{ + return d->updates; +} + +/*! + Looks only for a certain type of update. By default, only package update +*/ +void UpdateFinder::setUpdateType(UpdateTypes type) +{ + d->updateType = type; +} + +/*! + Returns the type of updates searched +*/ +UpdateTypes UpdateFinder::updateType() const +{ + return d->updateType; +} + +/*! + \internal + + Implemented from \ref KDUpdater::Task::doStart(). +*/ +void UpdateFinder::doRun() +{ + d->computeUpdates(); +} + +/*! + \internal + + Implemented form \ref KDUpdater::Task::doStop() +*/ +bool UpdateFinder::doStop() +{ + d->cancelComputeUpdates(); + + // Wait until the cancel has actually happened, and then return. + // Thinking of using QMutex for this. Frank/Till any suggestions? + + return true; +} + +/*! + \internal + + Implemented form \ref KDUpdater::Task::doStop() +*/ +bool UpdateFinder::doPause() +{ + // Not a pausable task + return false; +} + +/*! + \internal + + Implemented form \ref KDUpdater::Task::doStop() +*/ +bool UpdateFinder::doResume() +{ + // Not a pausable task, hence it is not resumable as well + return false; +} + +/*! + \internal +*/ +void UpdateFinder::Private::slotDownloadDone() +{ + ++downloadCompleteCount; + + int pc = computePercent(downloadCompleteCount, updateXmlFDList.count()); + pc = computeProgressPercentage(0, 45, pc); + q->reportProgress( pc, tr("Downloading Updates.xml from update sources.") ); +} + +/*! + \internal + */ +Update *UpdateFinder::constructUpdate(Application *application, const UpdateSourceInfo &sourceInfo, + UpdateType type, const QUrl &updateUrl, const QMap<QString, QVariant> &data, + quint64 compressedSize, quint64 uncompressedSize, const QByteArray &sha1sum ) +{ + return new Update(application, sourceInfo, type, updateUrl, data, compressedSize, uncompressedSize, sha1sum); +} + + +/*! + \ingroup kdupdater + + This function compares two version strings \c v1 and \c v2 and returns + -1, 0 or +1 based on the following rule + + \li Returns 0 if v1 == v2 + \li Returns -1 if v1 < v2 + \li Returns +1 if v1 > v2 + + The function is very similar to \c strcmp(), except that it works on version strings. + + Example: + \code + + KDUpdater::compareVersion("2.0", "2.1"); // Returns -1 + KDUpdater::compareVersion("2.1", "2.0"); // Returns +1 + KDUpdater::compareVersion("2.0", "2.0"); // Returns 0 + KDUpdater::compareVersion("2.1", "2.1"); // Returns 0 + + KDUpdater::compareVersion("2.0", "2.x"); // Returns 0 + KDUpdater::compareVersion("2.x", "2.0"); // Returns 0 + + KDUpdater::compareVersion("2.0.12.4", "2.1.10.4"); // Returns -1 + KDUpdater::compareVersion("2.0.12.x", "2.0.x"); // Returns 0 + KDUpdater::compareVersion("2.1.12.x", "2.0.x"); // Returns +1 + KDUpdater::compareVersion("2.1.12.x", "2.x"); // Returns 0 + KDUpdater::compareVersion("2.x", "2.1.12.x"); // Returns 0 + + \endcode +*/ +int KDUpdater::compareVersion(const QString &v1, const QString &v2) +{ + // For tests refer VersionCompareFnTest testcase. + + // Check for equality + if (v1 == v2) + return 0; + + // Split version numbers across "." + const QStringList v1_comps = v1.split(QRegExp(QLatin1String( "\\.|-"))); + const QStringList v2_comps = v2.split(QRegExp(QLatin1String( "\\.|-"))); + + // Check each component of the version + int index = 0; + while (true) { + if (index == v1_comps.count() && index < v2_comps.count()) + return -1; + if (index < v1_comps.count() && index == v2_comps.count()) + return +1; + if (index >= v1_comps.count() || index >= v2_comps.count()) + break; + + bool v1_ok, v2_ok; + int v1_comp = v1_comps[index].toInt(&v1_ok); + int v2_comp = v2_comps[index].toInt(&v2_ok); + + if (!v1_ok) { + if (v1_comps[index] == QLatin1String("x")) + return 0; + } + if (!v2_ok) { + if (v2_comps[index] == QLatin1String("x")) + return 0; + } + if (!v1_ok && !v2_ok) + return v1_comps[index].compare(v2_comps[index]); + + if (v1_comp < v2_comp) + return -1; + + if (v1_comp > v2_comp) + return +1; + + // v1_comp == v2_comp + ++index; + } + + if (index < v2_comps.count()) + return +1; + + if (index < v1_comps.count()) + return -1; + + // Controversial return. I hope this never happens. + return 0; +} + +#include "moc_kdupdaterupdatefinder.cpp" diff --git a/src/libs/kdtools/kdupdaterupdatefinder.h b/src/libs/kdtools/kdupdaterupdatefinder.h new file mode 100644 index 000000000..4faa97dab --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatefinder.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_FINDER_H +#define KD_UPDATER_UPDATE_FINDER_H + +#include "kdupdater.h" +#include "kdupdatertask.h" + +#include <QList> +#include <QMap> + +QT_BEGIN_NAMESPACE +class QUrl; +QT_END_NAMESPACE + +namespace KDUpdater { + +class Application; +class Update; +struct UpdateSourceInfo; + +class KDTOOLS_EXPORT UpdateFinder : public Task +{ + Q_OBJECT + +public: + explicit UpdateFinder(Application *application); + ~UpdateFinder(); + + Application *application() const; + QList<Update *> updates() const; + + void setUpdateType(UpdateTypes type); + UpdateTypes updateType() const; + +private: + void doRun(); + bool doStop(); + bool doPause(); + bool doResume(); + + Update *constructUpdate(Application *application, const UpdateSourceInfo &sourceInfo, + UpdateType type, const QUrl &updateUrl, const QMap<QString, QVariant> &data, + quint64 compressedSize, quint64 uncompressedSize, const QByteArray &sha1sum); + +private: + class Private; + Private *d; + Q_PRIVATE_SLOT(d, void slotDownloadDone()) +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_FINDER_H diff --git a/src/libs/kdtools/kdupdaterupdateoperation.cpp b/src/libs/kdtools/kdupdaterupdateoperation.cpp new file mode 100644 index 000000000..2c4af4b03 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperation.cpp @@ -0,0 +1,388 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdateoperation.h" + +#include "kdupdaterapplication.h" + +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QTemporaryFile> + +/*! + \ingroup kdupdater + \class KDUpdater::UpdateOperation kdupdaterupdateoperation.h KDUpdaterUpdateOperation + \brief Abstract base class for update operations. + + The \ref KDUpdater::UpdateOperation is an abstract class that specifies an interface for + update operations. Concrete implementations of this class must perform a single update + operation like copy, move, delete etc. + + \note Two seperate threads cannot be using a single instance of KDUpdater::UpdateOperation + at the same time. +*/ + +/* + * \internal + * Returns a filename for a temporary file based on \a templateName + */ +static QString backupFileName(const QString &templateName) +{ + const QFileInfo templ(templateName); + QTemporaryFile file( QDir::temp().absoluteFilePath(templ.fileName())); + file.open(); + const QString name = file.fileName(); + file.close(); + file.remove(); + return name; +} + +using namespace KDUpdater; + +/*! + Constructor +*/ +UpdateOperation::UpdateOperation() + : m_error(0), m_application(0) +{} + +/*! + Destructor +*/ +UpdateOperation::~UpdateOperation() +{ + if (Application *app = Application::instance()) + app->addFilesForDelayedDeletion(filesForDelayedDeletion()); +} + +/*! + Returns the update operation name. + + \sa setName() +*/ +QString UpdateOperation::name() const +{ + return m_name; +} + +/*! + Returns a command line string that describes the update operation. The returned + string would be of the form + + <name> <arg1> <arg2> <arg3> .... +*/ +QString UpdateOperation::operationCommand() const +{ + QString argsStr = m_arguments.join(QLatin1String( " " )); + return QString::fromLatin1( "%1 %2" ).arg(m_name, argsStr); +} + +/*! + Returns true if there exists a setting called \a name. Otherwise returns false. +*/ +bool UpdateOperation::hasValue(const QString &name) const +{ + return m_values.contains(name); +} + +/*! + Clears the value of setting \a name and removes it. + \post hasValue( \a name ) returns false. +*/ +void UpdateOperation::clearValue(const QString &name) +{ + m_values.remove(name); +} + +/*! + Returns the value of setting \a name. If the setting does not exists, + this returns an empty QVariant. +*/ +QVariant UpdateOperation::value(const QString &name) const +{ + return hasValue(name) ? m_values[name] : QVariant(); +} + +/*! + Sets the value of setting \a name to \a value. +*/ +void UpdateOperation::setValue(const QString &name, const QVariant &value) +{ + m_values[name] = value; +} + +/*! + Sets a update operation name. Subclasses will have to provide a unique + name to describe this operation. +*/ +void UpdateOperation::setName(const QString &name) +{ + m_name = name; +} + +/*! + Through this function, arguments to the update operation can be specified + to the update operation. +*/ +void UpdateOperation::setArguments(const QStringList &args) +{ + m_arguments = args; +} + +/*! + Sets the Application for this operation. + This may be used by some operations +*/ +void UpdateOperation::setApplication(Application *application) +{ + m_application = application; +} + +/*! + Returns the last set function arguments. +*/ +QStringList UpdateOperation::arguments() const +{ + return m_arguments; +} + +/*! + Returns error details in case performOperation() failed. +*/ +QString UpdateOperation::errorString() const +{ + return m_errorString; +} + +/*! + * Can be used by subclasses to report more detailed error codes (optional). + * To check if an operation was successful, use the return value of performOperation(). + */ +int UpdateOperation::error() const +{ + return m_error; +} + +/*! + * Used by subclasses to set the error string. + */ +void UpdateOperation::setErrorString(const QString &str) +{ + m_errorString = str; +} + +/*! + * Used by subclasses to set the error code. + */ +void UpdateOperation::setError(int error, const QString &errorString) +{ + m_error = error; + if (!errorString.isNull()) + m_errorString = errorString; +} + +/*! + Clears the previously set argument list and application +*/ +void UpdateOperation::clear() +{ + m_arguments.clear(); + m_application = 0; +} + +QStringList UpdateOperation::filesForDelayedDeletion() const +{ + return m_delayedDeletionFiles; +} + +/*! + Registers a file to be deleted later, once the application was restarted + (and the file isn't used anymore for sure). + @param files the files to be registered +*/ +void UpdateOperation::registerForDelayedDeletion(const QStringList &files) +{ + m_delayedDeletionFiles << files; +} + +/*! + Tries to delete \a file. If \a file can't be deleted, it gets registered for delayed deletion. +*/ +bool UpdateOperation::deleteFileNowOrLater(const QString &file, QString *errorString) +{ + if (file.isEmpty() || QFile::remove(file)) + return true; + + if (!QFile::exists(file)) + return true; + + const QString backup = backupFileName(file); + QFile f(file); + if (!f.rename(backup)) { + if (errorString) + *errorString = f.errorString(); + return false; + } + registerForDelayedDeletion(QStringList(backup)); + return true; +} + +/*! + Returns a pointer to the current Application +*/ +Application *UpdateOperation::application() const +{ + return m_application; +} + +/*! + \fn virtual void KDUpdater::UpdateOperation::backup() = 0; + + Subclasses must implement this function to backup any data before performing the action. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::performOperation() = 0; + + Subclasses must implement this function to perform the update operation +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::undoOperation() = 0; + + Subclasses must implement this function to perform the reverse of the operation. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::testOperation() = 0; + + Subclasses must implement this function to perform the test operation. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::clone() = 0; + + Subclasses must implement this function to clone the current operation. +*/ + +/*! + Saves this UpdateOperation in XML. You can override this method to store your own extra-data. + The default implementation is taking care of arguments and values set via setValue. +*/ +QDomDocument UpdateOperation::toXml() const +{ + QDomDocument doc; + QDomElement root = doc.createElement(QLatin1String("operation")); + doc.appendChild(root); + QDomElement args = doc.createElement(QLatin1String("arguments")); + Q_FOREACH (const QString &s, arguments()) { + QDomElement arg = doc.createElement(QLatin1String("argument")); + arg.appendChild(doc.createTextNode(s)); + args.appendChild(arg); + } + root.appendChild(args); + if (m_values.isEmpty()) + return doc; + + // append all values set with setValue + QDomElement values = doc.createElement(QLatin1String("values")); + for (QVariantMap::const_iterator it = m_values.begin(); it != m_values.end(); ++it) { + QDomElement value = doc.createElement(QLatin1String("value")); + const QVariant& variant = it.value(); + value.setAttribute(QLatin1String("name"), it.key()); + value.setAttribute(QLatin1String("type"), QLatin1String( QVariant::typeToName( variant.type()))); + + if (variant.type() != QVariant::List && variant.type() != QVariant::StringList && qVariantCanConvert<QString>(variant)) { + // it can convert to string? great! + value.appendChild( doc.createTextNode(variant.toString())); + } else { + // no? then we have to go the hard way... + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << variant; + value.appendChild(doc.createTextNode(QLatin1String( data.toBase64().data()))); + } + values.appendChild(value); + } + root.appendChild(values); + return doc; +} + +/*! + Restores UpdateOperation's arguments and values from the XML document \a doc. + Returns true on success, otherwise false. +*/ +bool UpdateOperation::fromXml(const QDomDocument &doc) +{ + QStringList args; + const QDomElement root = doc.documentElement(); + const QDomElement argsElem = root.firstChildElement(QLatin1String("arguments")); + Q_ASSERT(! argsElem.isNull()); + for (QDomNode n = argsElem.firstChild(); ! n.isNull(); n = n.nextSibling()) { + const QDomElement e = n.toElement(); + if (!e.isNull() && e.tagName() == QLatin1String("argument")) + args << e.text(); + } + setArguments(args); + + m_values.clear(); + const QDomElement values = root.firstChildElement(QLatin1String("values")); + for (QDomNode n = values.firstChild(); !n.isNull(); n = n.nextSibling()) { + const QDomElement v = n.toElement(); + if (v.isNull() || v.tagName() != QLatin1String("value")) + continue; + + const QString name = v.attribute(QLatin1String("name")); + const QString type = v.attribute(QLatin1String("type")); + const QString value = v.text(); + + const QVariant::Type t = QVariant::nameToType(type.toLatin1().data()); + QVariant var = qVariantFromValue(value); + if (t == QVariant::List || t == QVariant::StringList || !var.convert(t)) { + QDataStream stream(QByteArray::fromBase64( value.toLatin1())); + stream >> var; + } + + m_values[name] = var; + } + + return true; +} + +/*! + Restores UpdateOperation's arguments and values from the XML document at path \a xml. + Returns true on success, otherwise false. + \overload +*/ +bool UpdateOperation::fromXml(const QString &xml) +{ + QDomDocument doc; + QString errorMsg; + int errorLine; + int errorColumn; + if (!doc.setContent( xml, &errorMsg, &errorLine, &errorColumn)) { + qWarning() << "Error parsing xml error=" << errorMsg << "line=" << errorLine << "column=" << errorColumn; + return false; + } + return fromXml(doc); +} diff --git a/src/libs/kdtools/kdupdaterupdateoperation.h b/src/libs/kdtools/kdupdaterupdateoperation.h new file mode 100644 index 000000000..04f1c4858 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperation.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_OPERATION_H +#define KD_UPDATER_UPDATE_OPERATION_H + +#include "kdupdater.h" + +#include <QCoreApplication> +#include <QStringList> +#include <QVariant> +#include <QtXml/QDomDocument> + +namespace KDUpdater { + +class Application; + +class KDTOOLS_EXPORT UpdateOperation +{ + Q_DECLARE_TR_FUNCTIONS(UpdateOperation) + +public: + enum Error { + NoError = 0, + InvalidArguments = 1, + UserDefinedError = 128 + }; + + UpdateOperation(); + virtual ~UpdateOperation(); + + QString name() const; + QString operationCommand() const; + + bool hasValue(const QString &name) const; + void clearValue(const QString &name); + QVariant value(const QString &name) const; + void setValue(const QString &name, const QVariant &value); + + void setArguments(const QStringList &args); + void setApplication(Application *application); + QStringList arguments() const; + void clear(); + QString errorString() const; + int error() const; + QStringList filesForDelayedDeletion() const; + + virtual void backup() = 0; + virtual bool performOperation() = 0; + virtual bool undoOperation() = 0; + virtual bool testOperation() = 0; + virtual UpdateOperation *clone() const = 0; + + virtual QDomDocument toXml() const; + virtual bool fromXml(const QString &xml); + virtual bool fromXml(const QDomDocument &doc); + +protected: + void setName(const QString &name); + Application *application() const; + void setErrorString(const QString &errorString); + void setError(int error, const QString &errorString = QString()); + void registerForDelayedDeletion(const QStringList &files); + bool deleteFileNowOrLater(const QString &file, QString *errorString = 0); + +private: + QString m_name; + QStringList m_arguments; + QString m_errorString; + int m_error; + Application *m_application; + QVariantMap m_values; + QStringList m_delayedDeletionFiles; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_OPERATION_H diff --git a/src/libs/kdtools/kdupdaterupdateoperationfactory.cpp b/src/libs/kdtools/kdupdaterupdateoperationfactory.cpp new file mode 100644 index 000000000..aab8f6c2d --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperationfactory.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdateoperationfactory.h" +#include "kdupdaterupdateoperations.h" + +#include <QHash> + +/*! + \ingroup kdupdater + \class KDUpdater::UpdateOperationFactory kdupdaterupdateoperationfactory.h KDUpdaterUpdateOperationFactory + \brief Factory for \ref KDUpdater::UpdateOperation + + This class acts as a factory for \ref KDUpdater::UpdateOperation. You can register + one or more update operations with this factory and query operations based on their name. + + This class follows the singleton design pattern. Only one instance of this class can + be created and its reference can be fetched from the \ref instance() method. +*/ + +/*! + \fn KDUpdater::UpdateOperationFactory::registerUpdateOperation( const QString& name ) + + Registers T as new UpdateOperation with \a name. When create() is called with that \a name, + T is constructed using its default constructor. +*/ + +using namespace KDUpdater; + +/*! + Returns the UpdateOperationFactory instance. The instance is created if needed. +*/ +UpdateOperationFactory &UpdateOperationFactory::instance() +{ + static UpdateOperationFactory theFactory; + return theFactory; +} + +/*! + Constructor +*/ +UpdateOperationFactory::UpdateOperationFactory() +{ + // Register the default update operation set + registerUpdateOperation<CopyOperation>(QLatin1String("Copy")); + registerUpdateOperation<MoveOperation>(QLatin1String("Move")); + registerUpdateOperation<DeleteOperation>(QLatin1String("Delete")); + registerUpdateOperation<MkdirOperation>(QLatin1String("Mkdir")); + registerUpdateOperation<RmdirOperation>(QLatin1String("Rmdir")); + registerUpdateOperation<AppendFileOperation>(QLatin1String("AppendFile")); + registerUpdateOperation<PrependFileOperation>(QLatin1String("PrependFile")); + registerUpdateOperation<ExecuteOperation>(QLatin1String("Execute")); + registerUpdateOperation<UpdatePackageOperation>(QLatin1String("UpdatePackage")); + registerUpdateOperation<UpdateCompatOperation>(QLatin1String("UpdateCompat")); +} diff --git a/src/libs/kdtools/kdupdaterupdateoperationfactory.h b/src/libs/kdtools/kdupdaterupdateoperationfactory.h new file mode 100644 index 000000000..c21727f78 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperationfactory.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_OPERATION_FACTORY_H +#define KD_UPDATER_UPDATE_OPERATION_FACTORY_H + +#include <kdgenericfactory.h> + +#include "kdupdater.h" + +namespace KDUpdater { + +class UpdateOperation; + +typedef KDGenericFactory<UpdateOperation>::FactoryFunction UpdateOperationFactoryFunction; + +class KDTOOLS_EXPORT UpdateOperationFactory : public KDGenericFactory<UpdateOperation> +{ + Q_DISABLE_COPY(UpdateOperationFactory) + +public: + static UpdateOperationFactory &instance(); + + template <class T> + void registerUpdateOperation(const QString &name) + { + registerProduct<T>(name); + } + +protected: + UpdateOperationFactory(); +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_OPERATION_FACTORY_H diff --git a/src/libs/kdtools/kdupdaterupdateoperations.cpp b/src/libs/kdtools/kdupdaterupdateoperations.cpp new file mode 100644 index 000000000..681631542 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperations.cpp @@ -0,0 +1,1058 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdateoperations.h" +#include "kdupdaterapplication.h" +#include "kdupdaterpackagesinfo.h" +#include "environment.h" + +#include <QFile> +#include <QDir> +#include <QDirIterator> +#include <QProcess> +#include <QTextStream> +#include <QDebug> +#include <QTemporaryFile> + + +#include <cerrno> + +#define SUPPORT_DETACHED_PROCESS_EXECUTION + +#ifdef SUPPORT_DETACHED_PROCESS_EXECUTION +#ifdef Q_WS_WIN +#include <windows.h> +#endif +#endif + +using namespace KDUpdater; + +static bool removeDirectory(const QString &path, QString *errorString) +{ + Q_ASSERT(errorString); + const QFileInfoList entries = QDir(path).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden); + for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) { + if (it->isDir() && !it->isSymLink()) { + removeDirectory(it->filePath(), errorString); + } else { + QFile f(it->filePath()); + if (!f.remove()) + return false; + } + } + + errno = 0; + const bool success = QDir().rmdir(path); + if (errno) + *errorString = QLatin1String(strerror(errno)); + return success; +} +/* + * \internal + * Returns a filename for a temporary file based on \a templateName + */ +static QString backupFileName(const QString &templateName) +{ + QTemporaryFile file(templateName); + file.open(); + const QString name = file.fileName(); + file.close(); + file.remove(); + return name; +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::CopyOperation +//////////////////////////////////////////////////////////////////////////// + +CopyOperation::CopyOperation() +{ + setName(QLatin1String("Copy")); +} + +CopyOperation::~CopyOperation() +{ + deleteFileNowOrLater(value(QLatin1String("backupOfExistingDestination")).toString()); +} + +void CopyOperation::backup() +{ + const QString dest = arguments().last(); + if (!QFile::exists(dest)) { + clearValue(QLatin1String("backupOfExistingDestination")); + return; + } + + setValue(QLatin1String("backupOfExistingDestination"), backupFileName(dest)); + + // race condition: The backup file could get created + // by another process right now. But this is the same + // in QFile::copy... + const bool success = QFile::rename(dest, value(QLatin1String("backupOfExistingDestination")).toString()); + if (!success) + setError(UserDefinedError, tr("Could not backup file %1.").arg(dest)); +} + +bool CopyOperation::performOperation() +{ + // We need two args to complete the copy operation. + // First arg provides the complete file name of source + // Second arg provides the complete file name of dest + QStringList args = this->arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count())); + return false; + } + QString source = args.first(); + QString dest = args.last(); + + // If destination file exists, then we cannot use QFile::copy() + // because it does not overwrite an existing file. So we remove + // the destination file. + if (QFile::exists(dest)) { + QFile file(dest); + if (!file.remove()) { + setError(UserDefinedError); + setErrorString(tr("Could not remove destination file %1: %2").arg(dest, file.errorString())); + return false; + } + } + + QFile file(source); + const bool copied = file.copy(dest); + if (!copied) { + setError(UserDefinedError); + setErrorString(tr("Could not copy %1 to %2: %3").arg(source, dest, file.errorString())); + } + return copied; +} + +bool CopyOperation::undoOperation() +{ + const QString dest = arguments().last(); + + QFile destF(dest); + // first remove the dest + if (!destF.remove()) { + setError(UserDefinedError, tr("Could not delete file %1: %2").arg(dest, destF.errorString())); + return false; + } + + // no backup was done: + // the copy destination file wasn't existing yet - that's no error + if (!hasValue(QLatin1String("backupOfExistingDestination"))) + return true; + + QFile backupF(value(QLatin1String("backupOfExistingDestination")).toString()); + // otherwise we have to copy the backup back: + const bool success = backupF.rename(dest); + if (!success) + setError(UserDefinedError, tr("Could not restore backup file into %1: %2").arg(dest, backupF.errorString())); + return success; +} + +/*! + \reimp + */ +QDomDocument CopyOperation::toXml() const +{ + // we don't want to save the backupOfExistingDestination + if (!hasValue(QLatin1String("backupOfExistingDestination"))) + return UpdateOperation::toXml(); + + CopyOperation *const me = const_cast<CopyOperation *>(this); + + const QVariant v = value(QLatin1String("backupOfExistingDestination")); + me->clearValue(QLatin1String("backupOfExistingDestination")); + const QDomDocument xml = UpdateOperation::toXml(); + me->setValue(QLatin1String("backupOfExistingDestination"), v); + return xml; +} + +bool CopyOperation::testOperation() +{ + // TODO + return true; +} + +CopyOperation *CopyOperation::clone() const +{ + return new CopyOperation(); +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::MoveOperation +//////////////////////////////////////////////////////////////////////////// + +MoveOperation::MoveOperation() +{ + setName(QLatin1String("Move")); +} + +MoveOperation::~MoveOperation() +{ + deleteFileNowOrLater(value(QLatin1String("backupOfExistingDestination")).toString()); +} + +void MoveOperation::backup() +{ + const QString dest = arguments().last(); + if (!QFile::exists(dest)) { + clearValue(QLatin1String("backupOfExistingDestination")); + return; + } + + setValue(QLatin1String("backupOfExistingDestination"), backupFileName(dest)); + + // race condition: The backup file could get created + // by another process right now. But this is the same + // in QFile::copy... + const bool success = QFile::rename(dest, value(QLatin1String("backupOfExistingDestination")).toString()); + if (!success) + setError(UserDefinedError, tr("Could not backup file %1.").arg(dest)); +} + +bool MoveOperation::performOperation() +{ + // We need two args to complete the copy operation. + // First arg provides the complete file name of source + // Second arg provides the complete file name of dest + QStringList args = this->arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count())); + return false; + } + + QString source = args.first(); + QString dest = args.last(); + + // If destination file exists, then we cannot use QFile::copy() + // because it does not overwrite an existing file. So we remove + // the destination file. + if (QFile::exists(dest)) { + QFile file(dest); + if (!file.remove(dest)) { + setError(UserDefinedError); + setErrorString(tr("Could not remove destination file %1: %2").arg(dest, file.errorString())); + return false; + } + } + + // Copy source to destination. + QFile file(source); + const bool copied = file.copy(source, dest); + if (!copied) { + setError(UserDefinedError); + setErrorString(tr("Could not copy %1 to %2: %3").arg(source, dest, file.errorString())); + return false; + } + + return deleteFileNowOrLater(source); +} + +bool MoveOperation::undoOperation() +{ + const QStringList args = arguments(); + const QString& source = args.first(); + const QString& dest = args.last(); + + // first: copy back the destination to source + QFile destF(dest); + if (!destF.copy(source)) { + setError(UserDefinedError, tr("Cannot copy %1 to %2: %3").arg(dest, source, destF.errorString())); + return false; + } + + // second: delete the move destination + if (!deleteFileNowOrLater(dest)) { + setError(UserDefinedError, tr("Cannot remove file %1.")); + return false; + } + + // no backup was done: + // the move destination file wasn't existing yet - that's no error + if (!hasValue(QLatin1String("backupOfExistingDestination"))) + return true; + + // otherwise we have to copy the backup back: + QFile backupF(value(QLatin1String("backupOfExistingDestination")).toString()); + const bool success = backupF.rename(dest); + if (!success) + setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(dest, backupF.errorString())); + + return success; +} + +bool MoveOperation::testOperation() +{ + // TODO + return true; +} + +MoveOperation *MoveOperation::clone() const +{ + return new MoveOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::DeleteOperation +//////////////////////////////////////////////////////////////////////////// + +DeleteOperation::DeleteOperation() +{ + setName(QLatin1String("Delete")); +} + +DeleteOperation::~DeleteOperation() +{ + deleteFileNowOrLater(value(QLatin1String("backupOfExistingFile")).toString()); +} + +void DeleteOperation::backup() +{ + const QString fileName = arguments().first(); + setValue(QLatin1String("backupOfExistingFile"), backupFileName(fileName)); + QFile file(fileName); + const bool success = file.copy(value(QLatin1String("backupOfExistingFile")).toString()); + if (!success) + setError(UserDefinedError, tr("Cannot create backup of %1: %2").arg(fileName, file.errorString())); +} + +bool DeleteOperation::performOperation() +{ + // Requires only one parameter. That is the name of + // the file to remove. + QStringList args = this->arguments(); + if (args.count() != 1) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count())); + return false; + } + + const QString fName = args.first(); + return deleteFileNowOrLater(fName); +} + +bool DeleteOperation::undoOperation() +{ + if (!hasValue(QLatin1String("backupOfExistingFile"))) + return true; + + const QString fileName = arguments().first(); + QFile backupF(value(QLatin1String("backupOfExistingFile")).toString()); + const bool success = backupF.copy(fileName) && deleteFileNowOrLater(backupF.fileName()); + if (!success) + setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(fileName, backupF.errorString())); + + return success; +} + +bool DeleteOperation::testOperation() +{ + // TODO + return true; +} + +DeleteOperation *DeleteOperation::clone() const +{ + return new DeleteOperation; +} + +/*! + \reimp + */ +QDomDocument DeleteOperation::toXml() const +{ + // we don't want to save the backupOfExistingFile + if (!hasValue(QLatin1String("backupOfExistingFile"))) + return UpdateOperation::toXml(); + + DeleteOperation *const me = const_cast<DeleteOperation *>(this); + + const QVariant v = value(QLatin1String("backupOfExistingFile")); + me->clearValue(QLatin1String("backupOfExistingFile")); + const QDomDocument xml = UpdateOperation::toXml(); + me->setValue(QLatin1String("backupOfExistingFile"), v); + return xml; +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::MkdirOperation +//////////////////////////////////////////////////////////////////////////// + +MkdirOperation::MkdirOperation() +{ + setName(QLatin1String("Mkdir")); +} + +void MkdirOperation::backup() +{ + static const QRegExp re(QLatin1String("\\\\|/")); + static const QLatin1String sep("/"); + + QString path = arguments().first(); + path.replace(re, sep); + + QDir createdDir = QDir::root(); + + // find out, which part of the path is the first one we actually need to create + int end = 0; + while (true) { + QString p = path.section(sep, 0, ++end); + createdDir = QDir(p); + if (!createdDir.exists()) + break; + if (p == path) { + // everything did already exist -> nothing to do for us (nothing to revert then, either) + createdDir = QDir::root(); + break; + } + } + + setValue(QLatin1String("createddir"), createdDir.absolutePath()); +} + +bool MkdirOperation::performOperation() +{ + // Requires only one parameter. That is the name of + // the file to remove. + QStringList args = this->arguments(); + if (args.count() != 1) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count())); + return false; + } + QString dirName = args.first(); + const bool created = QDir::root().mkpath(dirName); + if (!created) { + setError(UserDefinedError); + setErrorString(tr("Could not create folder %1: Unknown error.").arg(dirName)); + } + return created; +} + +bool MkdirOperation::undoOperation() +{ + Q_ASSERT(arguments().count() == 1); + + QDir createdDir = QDir(value(QLatin1String("createddir")).toString()); + const bool forceremoval = QVariant(value(QLatin1String("forceremoval"))).toBool(); + + // Since refactoring we know the mkdir operation which is creating the target path. If we do a full + // uninstall prevent removing the full path including target, instead remove the target only. (QTIFW-46) + if (hasValue(QLatin1String("uninstall-only")) && value(QLatin1String("uninstall-only")).toBool()) + createdDir = QDir(arguments().first()); + + if (createdDir == QDir::root()) + return true; + + if (!createdDir.exists()) + return true; + + QString errorString; + if (forceremoval) + return removeDirectory(createdDir.path(), &errorString); + + // even remove some hidden, OS-created files in there +#if defined Q_WS_MAC + QFile::remove(createdDir.path() + QLatin1String("/.DS_Store")); +#elif defined Q_WS_WIN + QFile::remove(createdDir.path() + QLatin1String("/Thumbs.db")); +#endif + + errno = 0; + const bool result = QDir::root().rmdir(createdDir.path()); + if (!result) { + if (errorString.isEmpty()) + setError(UserDefinedError, tr("Cannot remove directory %1: %2").arg(createdDir.path(), errorString)); + else + setError(UserDefinedError, tr("Cannot remove directory %1: %2").arg(createdDir.path(), QLatin1String(strerror(errno)))); + } + return result; +} + +bool KDUpdater::MkdirOperation::testOperation() +{ + // TODO + return true; +} + +MkdirOperation *MkdirOperation::clone() const +{ + return new MkdirOperation; +} + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::RmdirOperation +//////////////////////////////////////////////////////////////////////////// + +RmdirOperation::RmdirOperation() +{ + setValue(QLatin1String("removed"), false); + setName(QLatin1String("Rmdir")); +} + +void RmdirOperation::backup() +{ + // nothing to backup - rollback will just create the directory +} + +bool RmdirOperation::performOperation() +{ + // Requires only one parameter. That is the name of + // the file to remove. + QStringList args = this->arguments(); + if (args.count() != 1) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count())); + return false; + } + + QString dirName = args.first(); + QDir dir(dirName); + if (!dir.exists()) { + setError(UserDefinedError); + setErrorString(tr("Could not remove folder %1: The folder does not exist.").arg(dirName)); + return false; + } + + errno = 0; + const bool removed = dir.rmdir(dirName); + setValue(QLatin1String("removed"), removed); + if (!removed) { + setError(UserDefinedError); + setErrorString(tr("Could not remove folder %1: %2").arg(dirName, QLatin1String(strerror(errno)))); + } + return removed; +} + +bool RmdirOperation::undoOperation() +{ + if (!value(QLatin1String("removed")).toBool()) + return true; + + const QFileInfo fi(arguments().first()); + errno = 0; + const bool success = fi.dir().mkdir(fi.fileName()); + if( !success) + setError(UserDefinedError, tr("Cannot recreate directory %1: %2").arg(fi.fileName(), QLatin1String(strerror(errno)))); + + return success; +} + +bool RmdirOperation::testOperation() +{ + // TODO + return true; +} + +RmdirOperation *RmdirOperation::clone() const +{ + return new RmdirOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::AppendFileOperation +//////////////////////////////////////////////////////////////////////////// + +AppendFileOperation::AppendFileOperation() +{ + setName(QLatin1String("AppendFile")); +} + +void AppendFileOperation::backup() +{ + const QString filename = arguments().first(); + + QFile file(filename); + if (!file.exists()) + return; // nothing to backup + + setValue(QLatin1String("backupOfFile"), backupFileName(filename)); + if (!file.copy(value(QLatin1String("backupOfFile")).toString())) { + setError(UserDefinedError, tr("Cannot backup file %1: %2").arg(filename, file.errorString())); + clearValue(QLatin1String("backupOfFile")); + } +} + +bool AppendFileOperation::performOperation() +{ + // This operation takes two arguments. First argument is the name + // of the file into which a text has to be appended. Second argument + // is the text to append. + QStringList args = this->arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count())); + return false; + } + + QString fName = args.first(); + QString text = args.last(); + + QFile file(fName); + if (!file.open(QFile::Append)) { + // first we rename the file, then we copy it to the real target and open the copy - the renamed original is then marked for deletion + const QString newName = backupFileName(fName); + if (!QFile::rename(fName, newName) && QFile::copy(newName, fName) && file.open(QFile::Append)) { + QFile::rename(newName, fName); + setError(UserDefinedError); + setErrorString(tr("Could not open file %1 for writing: %2").arg(file.fileName(), file.errorString())); + return false; + } + deleteFileNowOrLater(newName); + } + + QTextStream ts(&file); + ts << text; + file.close(); + + return true; +} + +bool AppendFileOperation::undoOperation() +{ + // backupOfFile being empty -> file didn't exist before -> no error + const QString filename = arguments().first(); + const QString backupOfFile = value(QLatin1String("backupOfFile")).toString(); + if (!backupOfFile.isEmpty() && !QFile::exists(backupOfFile)) { + setError(UserDefinedError, tr("Cannot find backup file for %1.").arg(filename)); + return false; + } + + const bool removed = deleteFileNowOrLater(filename); + if (!removed) { + setError(UserDefinedError, tr("Could not restore backup file for %1.").arg(filename)); + return false; + } + + // got deleted? We might be done, if it didn't exist before + if (backupOfFile.isEmpty()) + return true; + + QFile backupFile(backupOfFile); + const bool success = backupFile.rename(filename); + if (!success) + setError(UserDefinedError, tr("Could not restore backup file for %1: %2").arg(filename, backupFile.errorString())); + return success; +} + +bool AppendFileOperation::testOperation() +{ + // TODO + return true; +} + +AppendFileOperation *AppendFileOperation::clone() const +{ + return new AppendFileOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::PrependFileOperation +//////////////////////////////////////////////////////////////////////////// + +PrependFileOperation::PrependFileOperation() +{ + setName(QLatin1String("PrependFile")); +} + +void PrependFileOperation::backup() +{ + const QString filename = arguments().first(); + + QFile file(filename); + if (!file.exists()) + return; // nothing to backup + + setValue(QLatin1String("backupOfFile"), backupFileName(filename)); + if (!file.copy(value(QLatin1String("backupOfFile")).toString())) { + setError(UserDefinedError, tr("Cannot backup file %1: %2").arg(filename, file.errorString())); + clearValue(QLatin1String("backupOfFile")); + } +} + +bool PrependFileOperation::performOperation() +{ + // This operation takes two arguments. First argument is the name + // of the file into which a text has to be appended. Second argument + // is the text to append. + QStringList args = this->arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count())); + return false; + } + + QString fName = args.first(); + QString text = args.last(); + + // Load the file first. + QFile file(fName); + if (!file.open(QFile::ReadOnly)) { + setError(UserDefinedError); + setErrorString(tr("Could not open file %1 for reading: %2").arg(file.fileName(), file.errorString())); + return false; + } + QString fContents(QLatin1String(file.readAll())); + file.close(); + + // Prepend text to the file text + fContents = text + fContents; + + // Now re-open the file in write only mode. + if (!file.open(QFile::WriteOnly)) { + // first we rename the file, then we copy it to the real target and open the copy - the renamed original is then marked for deletion + const QString newName = backupFileName(fName); + if (!QFile::rename(fName, newName) && QFile::copy(newName, fName) && file.open(QFile::WriteOnly)) { + QFile::rename(newName, fName); + setError(UserDefinedError); + setErrorString(tr("Could not open file %1 for writing: %2").arg(file.fileName(), file.errorString())); + return false; + } + deleteFileNowOrLater(newName); + } + QTextStream ts(&file); + ts << fContents; + file.close(); + + return true; +} + +bool PrependFileOperation::undoOperation() +{ + // bockupOfFile being empty -> file didn't exist before -> no error + const QString filename = arguments().first(); + const QString backupOfFile = value(QLatin1String("backupOfFile")).toString(); + if (!backupOfFile.isEmpty() && !QFile::exists(backupOfFile)) { + setError(UserDefinedError, tr("Cannot find backup file for %1.").arg(filename)); + return false; + } + + if (!deleteFileNowOrLater(filename)) { + setError(UserDefinedError, tr("Cannot restore backup file for %1.").arg(filename)); + return false; + } + + // got deleted? We might be done, if it didn't exist before + if (backupOfFile.isEmpty()) + return true; + + QFile backupF(backupOfFile); + const bool success = backupF.rename(filename); + if (!success) + setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(filename, backupF.errorString())); + + return success; +} + +bool PrependFileOperation::testOperation() +{ + // TODO + return true; +} + +PrependFileOperation *PrependFileOperation::clone() const +{ + return new PrependFileOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::ExecuteOperation +//////////////////////////////////////////////////////////////////////////// + +ExecuteOperation::ExecuteOperation() + : QObject() +{ + setName(QLatin1String("Execute")); +} + +void ExecuteOperation::backup() +{ + // this is not possible, since the process can do whatever... +} + +#if defined(SUPPORT_DETACHED_PROCESS_EXECUTION) && defined(Q_WS_WIN) +// stolen from qprocess_win.cpp +static QString qt_create_commandline(const QString &program, const QStringList &arguments) +{ + QString args; + if (!program.isEmpty()) { + QString programName = program; + if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) + programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); + programName.replace(QLatin1Char('/'), QLatin1Char('\\')); + + // add the prgram as the first arg ... it works better + args = programName + QLatin1Char(' '); + } + + for (int i = 0; i < arguments.size(); ++i) { + QString tmp = arguments.at(i); + // in the case of \" already being in the string the \ must also be escaped + tmp.replace(QLatin1String("\\\""), QLatin1String("\\\\\"")); + // escape a single " because the arguments will be parsed + tmp.replace(QLatin1Char('\"'), QLatin1String("\\\"")); + if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { + // The argument must not end with a \ since this would be interpreted + // as escaping the quote -- rather put the \ behind the quote: e.g. + // rather use "foo"\ than "foo\" + QString endQuote(QLatin1Char('\"')); + int i = tmp.length(); + while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) { + --i; + endQuote += QLatin1Char('\\'); + } + args += QLatin1String(" \"") + tmp.left(i) + endQuote; + } else { + args += QLatin1Char(' ') + tmp; + } + } + return args; +} +#endif + +bool ExecuteOperation::performOperation() +{ + // This operation receives only one argument. It is the complete + // command line of the external program to execute. + QStringList args = this->arguments(); + if (args.isEmpty()) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count())); + return false; + } + + QList<int> allowedExitCodes; + + QRegExp re(QLatin1String("^\\{((-?\\d+,)*-?\\d+)\\}$")); + if (re.exactMatch(args.first())) { + const QStringList numbers = re.cap(1).split(QLatin1Char(',')); + for (QStringList::const_iterator it = numbers.begin(); it != numbers.end(); ++it) + allowedExitCodes.push_back(it->toInt()); + args.pop_front(); + } else { + allowedExitCodes.push_back(0); + } + + bool success = false; +#ifdef SUPPORT_DETACHED_PROCESS_EXECUTION + // unix style: when there's an ampersand after the command, it's started detached + if (args.count() >= 2 && args.last() == QLatin1String("&")) { + args.pop_back(); +#ifdef Q_WS_WIN + QString arguments = qt_create_commandline(args.front(), args.mid(1)); + + PROCESS_INFORMATION pinfo; + + STARTUPINFOW startupInfo = { sizeof(STARTUPINFO), 0, 0, 0, + static_cast< ulong >(CW_USEDEFAULT), static_cast< ulong >(CW_USEDEFAULT), + static_cast< ulong >(CW_USEDEFAULT), static_cast< ulong >(CW_USEDEFAULT), + 0, 0, 0, STARTF_USESHOWWINDOW, SW_HIDE, 0, 0, 0, 0, 0 + }; + success = CreateProcess(0, const_cast< wchar_t* >(static_cast< const wchar_t* >(arguments.utf16())), + 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, 0, + 0, + &startupInfo, &pinfo); + +#else + success = QProcess::startDetached(args.front(), args.mid(1)); +#endif + } + else +#endif + { + Environment::instance().applyTo(&process); //apply non-persistent variables + process.start(args.front(), args.mid(1)); + + QEventLoop loop; + QObject::connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + QObject::connect(&process, SIGNAL(readyRead()), this, SLOT(readProcessOutput())); + success = process.waitForStarted(-1); + if (success) { + loop.exec(); + setValue(QLatin1String("ExitCode"), process.exitCode()); + success = allowedExitCodes.contains(process.exitCode()); + } + } + if (!success) { + setError(UserDefinedError); + setErrorString(tr("Execution failed: %1").arg(args.join(QLatin1String(" ")))); + } + + return success; +} + +/*! + Cancels the ExecuteOperation. This methods tries to terminate the process + gracefully by calling QProcess::terminate. After 10 seconds, the process gets killed. + */ +void ExecuteOperation::cancelOperation() +{ + if (process.state() == QProcess::Running) + process.terminate(); + if (!process.waitForFinished(10000)) + process.kill(); +} + +void ExecuteOperation::readProcessOutput() +{ + QByteArray output = process.readAll(); + if (!output.isEmpty()) + emit outputTextChanged(QString::fromLocal8Bit(output)); +} + +bool ExecuteOperation::undoOperation() +{ + // this is not possible, since the process can do whatever... + return false; +} + +bool ExecuteOperation::testOperation() +{ + // TODO + return true; +} + +ExecuteOperation *ExecuteOperation::clone() const +{ + return new ExecuteOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::UpdatePackageOperation +//////////////////////////////////////////////////////////////////////////// + +UpdatePackageOperation::UpdatePackageOperation() +{ + setName(QLatin1String("UpdatePackage")); +} + +void UpdatePackageOperation::backup() +{ + const PackageInfo info = application()->packagesInfo()->packageInfo(application()->packagesInfo()->findPackageInfo(arguments().first())); + setValue(QLatin1String("oldVersion"), info.version); + setValue(QLatin1String("oldDate"), info.lastUpdateDate); +} + +bool UpdatePackageOperation::performOperation() +{ + // This operation receives three arguments : the name of the package + // the new version and the release date + const QStringList args = this->arguments(); + if (args.count() != 3) { + setError(InvalidArguments, tr("Invalid arguments: %1 arguments given, 3 expected.").arg(args.count())); + return false; + } + + const QString &packageName = args.at(0); + const QString &version = args.at(1); + const QDate date = QDate::fromString(args.at(2)); + const bool success = application()->packagesInfo()->updatePackage(packageName, version, date); + if (!success) + setError(UserDefinedError, tr("Cannot update %1-%2").arg(packageName, version)); + + return success; +} + +bool UpdatePackageOperation::undoOperation() +{ + const QString packageName = arguments().first(); + const QString version = arguments().at(1); + const QString oldVersion = value(QLatin1String("oldVersion")).toString(); + const QDate oldDate = value(QLatin1String("oldDate")).toDate(); + const bool success = application()->packagesInfo()->updatePackage(packageName, oldVersion, oldDate); + if (!success) + setError(UserDefinedError, tr("Cannot restore %1-%2").arg(packageName, version)); + + return success; +} + +bool UpdatePackageOperation::testOperation() +{ + // TODO + return true; +} + +UpdatePackageOperation *UpdatePackageOperation::clone() const +{ + return new UpdatePackageOperation; +} + + +//////////////////////////////////////////////////////////////////////////// +// KDUpdater::UpdateCompatOperation +//////////////////////////////////////////////////////////////////////////// + +UpdateCompatOperation::UpdateCompatOperation() +{ + setName(QLatin1String("UpdateCompatLevel")); +} + +void UpdateCompatOperation::backup() +{ + setValue(QLatin1String("oldCompatLevel"), application()->packagesInfo()->compatLevel()); +} + +bool UpdateCompatOperation::performOperation() +{ + // This operation receives one argument : the new compat level + const QStringList args = this->arguments(); + if (args.count() != 1) { + setError(InvalidArguments, tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count())); + return false; + } + + const int level = args.first().toInt(); + application()->packagesInfo()->setCompatLevel(level); + return true; +} + +bool UpdateCompatOperation::undoOperation() +{ + if (!hasValue(QLatin1String("oldCompatLevel"))) { + setError(UserDefinedError, tr("Cannot restore previous compat-level")); + return false; + } + + application()->packagesInfo()->setCompatLevel(value(QLatin1String("oldCompatLevel")).toInt()); + return true; +} + +bool UpdateCompatOperation::testOperation() +{ + // TODO + return true; +} + +UpdateCompatOperation *UpdateCompatOperation::clone() const +{ + return new UpdateCompatOperation; +} diff --git a/src/libs/kdtools/kdupdaterupdateoperations.h b/src/libs/kdtools/kdupdaterupdateoperations.h new file mode 100644 index 000000000..40480d1e1 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdateoperations.h @@ -0,0 +1,177 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_OPERATIONS_H +#define KD_UPDATER_UPDATE_OPERATIONS_H + +#include "kdupdaterupdateoperation.h" + +#include <QDir> +#include <QObject> +#include <QProcess> + +namespace KDUpdater { + +class KDTOOLS_EXPORT CopyOperation : public UpdateOperation +{ +public: + CopyOperation(); + ~CopyOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + CopyOperation *clone() const; + + QDomDocument toXml() const; +}; + +class KDTOOLS_EXPORT MoveOperation : public UpdateOperation +{ +public: + MoveOperation(); + ~MoveOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + MoveOperation *clone() const; +}; + +class KDTOOLS_EXPORT DeleteOperation : public UpdateOperation +{ +public: + DeleteOperation(); + ~DeleteOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + DeleteOperation *clone() const; + + QDomDocument toXml() const; +}; + +class KDTOOLS_EXPORT MkdirOperation : public UpdateOperation +{ +public: + MkdirOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + MkdirOperation *clone() const; +}; + +class KDTOOLS_EXPORT RmdirOperation : public UpdateOperation +{ +public: + RmdirOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + RmdirOperation *clone() const; +}; + +class KDTOOLS_EXPORT AppendFileOperation : public UpdateOperation +{ +public: + AppendFileOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + AppendFileOperation *clone() const; +}; + +class KDTOOLS_EXPORT PrependFileOperation : public UpdateOperation +{ +public: + PrependFileOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + PrependFileOperation *clone() const; +}; + +class KDTOOLS_EXPORT ExecuteOperation : public QObject, public UpdateOperation +{ + Q_OBJECT + +public: + ExecuteOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + ExecuteOperation *clone() const; + +public Q_SLOTS: + void cancelOperation(); + +private Q_SLOTS: + void readProcessOutput(); + +Q_SIGNALS: + void outputTextChanged(const QString &text); + +private: + QProcess process; +}; + +class KDTOOLS_EXPORT UpdatePackageOperation : public UpdateOperation +{ +public: + UpdatePackageOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + UpdatePackageOperation *clone() const; +}; + +class KDTOOLS_EXPORT UpdateCompatOperation : public UpdateOperation +{ +public: + UpdateCompatOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + UpdateCompatOperation *clone() const; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_OPERATIONS_H diff --git a/src/libs/kdtools/kdupdaterupdatesinfo.cpp b/src/libs/kdtools/kdupdaterupdatesinfo.cpp new file mode 100644 index 000000000..420876056 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatesinfo.cpp @@ -0,0 +1,352 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdatesinfo_p.h" + +#include <QCoreApplication> +#include <QDomDocument> +#include <QDomElement> +#include <QFile> +#include <QSharedData> + +using namespace KDUpdater; + +// +// UpdatesInfo::UpdatesInfoData +// +struct UpdatesInfo::UpdatesInfoData : public QSharedData +{ + Q_DECLARE_TR_FUNCTIONS(KDUpdater::UpdatesInfoData) + +public: + UpdatesInfoData() : error(UpdatesInfo::NotYetReadError), compatLevel(-1) { } + + QString errorMessage; + UpdatesInfo::Error error; + QString updateXmlFile; + QString applicationName; + QString applicationVersion; + int compatLevel; + QList<UpdateInfo> updateInfoList; + + void parseFile(const QString &updateXmlFile); + bool parsePackageUpdateElement(const QDomElement &updateE); + bool parseCompatUpdateElement(const QDomElement &updateE); + + void setInvalidContentError(const QString &detail); +}; + +void UpdatesInfo::UpdatesInfoData::setInvalidContentError(const QString &detail) +{ + error = UpdatesInfo::InvalidContentError; + errorMessage = tr("Updates.Xml contains invalid content: %1").arg(detail); +} + +void UpdatesInfo::UpdatesInfoData::parseFile(const QString &updateXmlFile) +{ + QFile file(updateXmlFile); + if (!file.open(QFile::ReadOnly)) { + error = UpdatesInfo::CouldNotReadUpdateInfoFileError; + errorMessage = tr("Could not read \"%1\"").arg(updateXmlFile); + return; + } + + QDomDocument doc; + QString parseErrorMessage; + int parseErrorLine; + int parseErrorColumn; + if (!doc.setContent(&file, &parseErrorMessage, &parseErrorLine, &parseErrorColumn)) { + error = UpdatesInfo::InvalidXmlError; + errorMessage = tr("Parse error in %1 at %2, %3: %4") + .arg(updateXmlFile, + QString::number(parseErrorLine), + QString::number(parseErrorColumn), + parseErrorMessage); + return; + } + + QDomElement rootE = doc.documentElement(); + if (rootE.tagName() != QLatin1String("Updates")) { + setInvalidContentError(tr("root element %1 unexpected, should be \"Updates\"").arg(rootE.tagName())); + return; + } + + QDomNodeList childNodes = rootE.childNodes(); + for(int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.at(i); + QDomElement childE = childNode.toElement(); + if (childE.isNull()) + continue; + + if (childE.tagName() == QLatin1String("ApplicationName")) + applicationName = childE.text(); + else if (childE.tagName() == QLatin1String("ApplicationVersion")) + applicationVersion = childE.text(); + else if (childE.tagName() == QLatin1String("RequiredCompatLevel")) + compatLevel = childE.text().toInt(); + else if (childE.tagName() == QLatin1String("PackageUpdate")) { + const bool res = parsePackageUpdateElement(childE); + if (!res) { + //error handled in subroutine + return; + } + } else if (childE.tagName() == QLatin1String("CompatUpdate")) { + const bool res = parseCompatUpdateElement(childE); + if (!res) { + //error handled in subroutine + return; + } + } + } + + if (applicationName.isEmpty()) { + setInvalidContentError(tr("ApplicationName element is missing")); + return; + } + + if (applicationVersion.isEmpty()) { + setInvalidContentError(tr("ApplicationVersion element is missing")); + return; + } + + error = UpdatesInfo::NoError; + errorMessage.clear(); +} + +bool UpdatesInfo::UpdatesInfoData::parsePackageUpdateElement(const QDomElement &updateE) +{ + if (updateE.isNull()) + return false; + + UpdateInfo info; + info.type = PackageUpdate; + + QDomNodeList childNodes = updateE.childNodes(); + for (int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.at(i); + QDomElement childE = childNode.toElement(); + if (childE.isNull()) + continue; + + if (childE.tagName() == QLatin1String("ReleaseNotes")) { + info.data[childE.tagName()] = QUrl(childE.text()); + } else if (childE.tagName() == QLatin1String("UpdateFile")) { + UpdateFileInfo ufInfo; + ufInfo.arch = childE.attribute(QLatin1String("Arch"), QLatin1String("i386")); + ufInfo.os = childE.attribute(QLatin1String("OS")); + ufInfo.compressedSize = childE.attribute(QLatin1String("CompressedSize")).toLongLong(); + ufInfo.uncompressedSize = childE.attribute(QLatin1String("UncompressedSize")).toLongLong(); + ufInfo.sha1sum = QByteArray::fromHex(childE.attribute(QLatin1String("sha1sum")).toAscii()); + ufInfo.fileName = childE.text(); + info.updateFiles.append(ufInfo); + } else if (childE.tagName() == QLatin1String("Licenses")) { + QHash<QString, QVariant> licenseHash; + const QDomNodeList licenseNodes = childE.childNodes(); + for (int i = 0; i < licenseNodes.count(); ++i) { + const QDomNode licenseNode = licenseNodes.at(i); + if (licenseNode.nodeName() == QLatin1String("License")) { + QDomElement element = licenseNode.toElement(); + licenseHash.insert(element.attributeNode(QLatin1String("name")).value(), + element.attributeNode(QLatin1String("file")).value()); + } + } + if (!licenseHash.isEmpty()) + info.data.insert(QLatin1String("Licenses"), licenseHash); + } else if (childE.tagName() == QLatin1String("Version")) { + info.data.insert(QLatin1String("inheritVersionFrom"), childE.attribute(QLatin1String("inheritVersionFrom"))); + info.data[childE.tagName()] = childE.text(); + } else { + info.data[childE.tagName()] = childE.text(); + } + } + + if (!info.data.contains(QLatin1String("Name"))) { + setInvalidContentError(tr("PackageUpdate element without Name")); + return false; + } + if (!info.data.contains(QLatin1String("Version"))) { + setInvalidContentError(tr("PackageUpdate element without Version")); + return false; + } + if (!info.data.contains(QLatin1String("ReleaseDate"))) { + setInvalidContentError(tr("PackageUpdate element without ReleaseDate")); + return false; + } + if (info.updateFiles.isEmpty()) { + setInvalidContentError(tr("PackageUpdate element without UpdateFile")); + return false; + } + + updateInfoList.append(info); + return true; +} + +bool UpdatesInfo::UpdatesInfoData::parseCompatUpdateElement(const QDomElement &updateE) +{ + if (updateE.isNull()) + return false; + + UpdateInfo info; + info.type = CompatUpdate; + + QDomNodeList childNodes = updateE.childNodes(); + for (int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.at(i); + QDomElement childE = childNode.toElement(); + if (childE.isNull()) + continue; + + if (childE.tagName() == QLatin1String("ReleaseNotes")) { + info.data[childE.tagName()] = QUrl(childE.text()); + } else if (childE.tagName() == QLatin1String("UpdateFile")) { + UpdateFileInfo ufInfo; + ufInfo.arch = childE.attribute(QLatin1String("Arch"), QLatin1String("i386")); + ufInfo.os = childE.attribute(QLatin1String("OS")); + ufInfo.fileName = childE.text(); + info.updateFiles.append(ufInfo); + } else { + info.data[childE.tagName()] = childE.text(); + } + } + + if (!info.data.contains(QLatin1String("CompatLevel"))) { + setInvalidContentError(tr("CompatUpdate element without CompatLevel")); + return false; + } + + if (!info.data.contains(QLatin1String("ReleaseDate"))) { + setInvalidContentError(tr("CompatUpdate element without ReleaseDate")); + return false; + } + + if (info.updateFiles.isEmpty()) { + setInvalidContentError(tr("CompatUpdate element without UpdateFile")); + return false; + } + + updateInfoList.append(info); + return true; +} + + +// +// UpdatesInfo +// +UpdatesInfo::UpdatesInfo() + : d(new UpdatesInfo::UpdatesInfoData) +{ +} + +UpdatesInfo::~UpdatesInfo() +{ +} + +bool UpdatesInfo::isValid() const +{ + return d->error == NoError; +} + +QString UpdatesInfo::errorString() const +{ + return d->errorMessage; +} + +void UpdatesInfo::setFileName(const QString &updateXmlFile) +{ + if (d->updateXmlFile == updateXmlFile) + return; + + d->applicationName.clear(); + d->applicationVersion.clear(); + d->updateInfoList.clear(); + + d->updateXmlFile = updateXmlFile; + d->parseFile(d->updateXmlFile); +} + +QString UpdatesInfo::fileName() const +{ + return d->updateXmlFile; +} + +QString UpdatesInfo::applicationName() const +{ + return d->applicationName; +} + +QString UpdatesInfo::applicationVersion() const +{ + return d->applicationVersion; +} + +int UpdatesInfo::compatLevel() const +{ + return d->compatLevel; +} + +int UpdatesInfo::updateInfoCount(int type) const +{ + if (type == AllUpdate) + return d->updateInfoList.count(); + + int count = 0; + for (int i = 0; i < d->updateInfoList.count(); ++i) { + if (d->updateInfoList.at(i).type == type) + ++count; + } + return count; +} + +UpdateInfo UpdatesInfo::updateInfo(int index) const +{ + if (index < 0 || index >= d->updateInfoList.count()) + return UpdateInfo(); + + return d->updateInfoList.at(index); +} + +QList<UpdateInfo> UpdatesInfo::updatesInfo(int type, int compatLevel) const +{ + QList<UpdateInfo> list; + if (compatLevel == -1) { + if (type == AllUpdate) + return d->updateInfoList; + for (int i = 0; i < d->updateInfoList.count(); ++i) { + if (d->updateInfoList.at(i).type == type) + list.append(d->updateInfoList.at(i)); + } + } else { + for (int i = 0; i < d->updateInfoList.count(); ++i) { + UpdateInfo updateInfo = d->updateInfoList.at(i); + if (updateInfo.type == type) { + if (updateInfo.type == CompatUpdate) { + if (updateInfo.data.value(QLatin1String("CompatLevel")) == compatLevel) + list.append(updateInfo); + } else { + if (updateInfo.data.value(QLatin1String("RequiredCompatLevel")) == compatLevel) + list.append(updateInfo); + } + } + } + } + return list; +} diff --git a/src/libs/kdtools/kdupdaterupdatesinfo_p.h b/src/libs/kdtools/kdupdaterupdatesinfo_p.h new file mode 100644 index 000000000..7c4bc5b3f --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatesinfo_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_INFO_H +#define KD_UPDATER_UPDATE_INFO_H + +#include "kdupdater.h" + +#include <QSharedDataPointer> +#include <QString> +#include <QDate> +#include <QList> +#include <QStringList> +#include <QUrl> +#include <QMap> +#include <QVariant> + +// Classes and structures in this header file are for internal use only. +// They are not a part of the public API + +namespace KDUpdater { + +struct UpdateFileInfo +{ + UpdateFileInfo() + : compressedSize(0), + uncompressedSize(0) + {} + + QString arch; + QString os; + QString fileName; + QByteArray sha1sum; + quint64 compressedSize; + quint64 uncompressedSize; +}; + +struct UpdateInfo +{ + int type; + QMap<QString, QVariant> data; + QList<UpdateFileInfo> updateFiles; +}; + +class UpdatesInfo +{ +public: + enum Error + { + NoError = 0, + NotYetReadError, + CouldNotReadUpdateInfoFileError, + InvalidXmlError, + InvalidContentError + }; + + UpdatesInfo(); + ~UpdatesInfo(); + + bool isValid() const; + QString errorString() const; + Error error() const; + + void setFileName(const QString &updateXmlFile); + QString fileName() const; + + QString applicationName() const; + QString applicationVersion() const; + int compatLevel() const; + + int updateInfoCount(int type = AllUpdate) const; + UpdateInfo updateInfo(int index) const; + QList<UpdateInfo> updatesInfo(int type = AllUpdate, int compatLevel = -1) const; + +private: + struct UpdatesInfoData; + QSharedDataPointer<UpdatesInfoData> d; +}; + +} // namespace KDUpdater + +#endif // KD_UPDATER_UPDATE_INFO_H diff --git a/src/libs/kdtools/kdupdaterupdatesourcesinfo.cpp b/src/libs/kdtools/kdupdaterupdatesourcesinfo.cpp new file mode 100644 index 000000000..e4fbae43c --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatesourcesinfo.cpp @@ -0,0 +1,500 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdatesourcesinfo.h" +#include "kdupdaterapplication.h" + +#include <QDomElement> +#include <QDomDocument> +#include <QDomText> +#include <QDomCDATASection> +#include <QFileInfo> +#include <QFile> +#include <QTextStream> + + +/*! + \ingroup kdupdater + \class KDUpdater::UpdateSourcesInfo kdupdaterupdatesourcesinfo.h KDUpdaterUpdateSourcesInfo + \brief Provides access to information about the update sources set for the application. + + An update source is a repository that contains updates applicable for the application. + Applications can download updates from the update source and install them locally. + + Each application can have one or more update sources from which it can download updates. + Information about update source is stored in a file called UpdateSources.xml. This class helps + access and modify the UpdateSources.xml file. + + The complete file name of the UpdateSources.xml file can be specified via the \ref setFileName() + method. The class then parses the XML file and makes available information contained in + that XML file through an easy to use API. You can + + \li Get update sources information via the \ref updateSourceInfoCount() and \ref updateSourceInfo() + methods. + \li You can add/remove/change update source information via the \ref addUpdateSourceInfo(), + \ref removeUpdateSource(), \ref setUpdateSourceAt() methods. + + The class emits appropriate signals to inform listeners about changes in the update application. +*/ + +/*! \enum UpdateSourcesInfo::Error + * Error codes related to retrieving update sources + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::NoError + * No error occurred + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::NotYetReadError + * The package information was not parsed yet from the XML file + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::CouldNotReadSourceFileError + * the specified update source file could not be read (does not exist or not readable) + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::InvalidXmlError + * The source file contains invalid XML. + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::InvalidContentError + * The source file contains valid XML, but does not match the expected format for source descriptions + */ + +/*! \var UpdateSourcesInfo::Error UpdateSourcesInfo::CouldNotSaveChangesError + * Changes made to the object could be saved back to the source file + */ + +using namespace KDUpdater; + +struct UpdateSourceInfoPriorityHigherThan +{ + bool operator()(const UpdateSourceInfo &lhs, const UpdateSourceInfo &rhs) const + { + return lhs.priority > rhs.priority; + } +}; + + +struct UpdateSourcesInfo::UpdateSourcesInfoData +{ + UpdateSourcesInfoData(UpdateSourcesInfo *qq) : + q(qq), + error(UpdateSourcesInfo::NotYetReadError), + application(0), + modified(false) + {} + + UpdateSourcesInfo *q; + + QString errorMessage; + UpdateSourcesInfo::Error error; + Application *application; + bool modified; + QString fileName; + QList<UpdateSourceInfo> updateSourceInfoList; + + void addUpdateSourceFrom(const QDomElement &element); + void addChildElement(QDomDocument &doc, QDomElement &parentE, const QString &tagName, const QString &text, bool htmlText = false); + void setInvalidContentError(const QString &detail); + void clearError(); + void saveChanges(); +}; + +void UpdateSourcesInfo::UpdateSourcesInfoData::setInvalidContentError(const QString &detail) +{ + error = UpdateSourcesInfo::InvalidContentError; + errorMessage = tr("%1 contains invalid content: %2").arg(fileName, detail); +} + +void UpdateSourcesInfo::UpdateSourcesInfoData::clearError() +{ + error = UpdateSourcesInfo::NoError; + errorMessage.clear(); +} + +/*! + \internal +*/ +UpdateSourcesInfo::UpdateSourcesInfo(Application *application) + : QObject(application), + d(new UpdateSourcesInfo::UpdateSourcesInfoData(this)) +{ + d->application = application; +} + +/*! + \internal +*/ +UpdateSourcesInfo::~UpdateSourcesInfo() +{ + d->saveChanges(); + delete d; +} + +/*! + Returns a pointer to the update application for which this class manages update sources. +*/ +Application *UpdateSourcesInfo::application() const +{ + return d->application; +} + +/*! + \internal +*/ +bool UpdateSourcesInfo::isValid() const +{ + return d->error == NoError; +} + +/*! + returns a human-readable description of the error + */ +QString UpdateSourcesInfo::errorString() const +{ + return d->errorMessage; +} + +/*! + returns the last error + */ +UpdateSourcesInfo::Error UpdateSourcesInfo::error() const +{ + return d->error; +} + +bool UpdateSourcesInfo::isModified() const +{ + return d->modified; +} + +void UpdateSourcesInfo::setModified(bool modified) +{ + d->modified = modified; +} + +/*! + Sets the complete file name of the UpdateSources.xml file. The function also issues a call + to refresh() to reload package information from the XML file. + + \sa KDUpdater::Application::setUpdateSourcesXMLFileName() +*/ +void UpdateSourcesInfo::setFileName(const QString &fileName) +{ + if (d->fileName == fileName) + return; + + d->fileName = fileName; + refresh(); // load new file +} + +/*! + Returns the name of the UpdateSources.xml file that this class referred to. +*/ +QString UpdateSourcesInfo::fileName() const +{ + return d->fileName; +} + +/*! + Returns the number of update source info structures contained in this class. +*/ +int UpdateSourcesInfo::updateSourceInfoCount() const +{ + return d->updateSourceInfoList.count(); +} + +/*! + Returns the update source info structure at \c index. If an invalid index is passed + the function returns a dummy constructor. +*/ +UpdateSourceInfo UpdateSourcesInfo::updateSourceInfo(int index) const +{ + if (index < 0 || index >= d->updateSourceInfoList.count()) + return UpdateSourceInfo(); + + return d->updateSourceInfoList[index]; +} + +/*! + Adds an update source info to this class. Upon successful addition, the class emits a + \ref updateSourceInfoAdded() signal. +*/ +void UpdateSourcesInfo::addUpdateSourceInfo(const UpdateSourceInfo &info) +{ + if (d->updateSourceInfoList.contains(info)) + return; + d->updateSourceInfoList.push_back(info); + qSort(d->updateSourceInfoList.begin(), d->updateSourceInfoList.end(), UpdateSourceInfoPriorityHigherThan()); + emit updateSourceInfoAdded(info); + d->modified = true; +} + +/*! + Removes an update source info from this class. Upon successful removal, the class emits a + \ref updateSourceInfoRemoved() signal. +*/ +void UpdateSourcesInfo::removeUpdateSourceInfo(const UpdateSourceInfo &info) +{ + if (!d->updateSourceInfoList.contains(info)) + return; + d->updateSourceInfoList.removeAll(info); + emit updateSourceInfoRemoved(info); + d->modified = true; +} + +/*! + Removes an update source info at \index in this class. Upon successful removal, the class emits a + \ref updateSourceInfoRemoved() signal. +*/ +void UpdateSourcesInfo::removeUpdateSourceInfoAt(int index) +{ + if (index < 0 || index >= d->updateSourceInfoList.count()) + return; + UpdateSourceInfo info = d->updateSourceInfoList[index]; + d->updateSourceInfoList.removeAt(index); + emit updateSourceInfoRemoved(info); + d->modified = true; +} + +/*! + Changes the update source info at \c index to \c info. If \c index is equal to the number of + source info structures in this class (\ref updateSourceInfoCount()) then \c info is appended; + otherwise the existing info at \c index will be changed. + + Depending on what the function does \ref updateSourceInfoAdded() or \ref updateSourceInfoChanged() + signal is emitted. +*/ +void UpdateSourcesInfo::setUpdateSourceInfoAt(int index, const UpdateSourceInfo &info) +{ + if (index < 0 || index > d->updateSourceInfoList.count()) + return; + + if (index == d->updateSourceInfoList.count()) { + d->updateSourceInfoList.append(info); + emit updateSourceInfoAdded(info); + } else { + UpdateSourceInfo oldInfo = d->updateSourceInfoList[index]; + if (info == oldInfo) + return; + + d->updateSourceInfoList[index] = info; + emit updateSourceInfoChanged(info, oldInfo); + } + d->modified = true; +} + +/*! + This slot reloads the update source information from UpdateSources.xml. +*/ +void UpdateSourcesInfo::refresh() +{ + d->saveChanges(); // save changes done in the previous file + d->updateSourceInfoList.clear(); + + QFile file(d->fileName); + + // if the file does not exist then we just skip the reading + if (!file.exists()) { + d->clearError(); + emit reset(); + return; + } + + // Open the XML file + if (!file.open(QFile::ReadOnly)) { + d->errorMessage = tr("Could not read \"%1\"").arg(d->fileName); + d->error = CouldNotReadSourceFileError; + emit reset(); + return; + } + + QDomDocument doc; + QString parseErrorMessage; + int parseErrorLine; + int parseErrorColumn; + if (!doc.setContent(&file, &parseErrorMessage, &parseErrorLine, &parseErrorColumn)) { + d->error = InvalidXmlError; + d->errorMessage = tr("XML Parse error in %1 at %2, %3: %4") + .arg(d->fileName, + QString::number(parseErrorLine), + QString::number(parseErrorColumn), + parseErrorMessage); + emit reset(); + return; + } + + // Now parse the XML file. + QDomElement rootE = doc.documentElement(); + if (rootE.tagName() != QLatin1String("UpdateSources")) { + d->setInvalidContentError(tr("Root element %1 unexpected, should be \"UpdateSources\"").arg(rootE.tagName())); + emit reset(); + return; + } + + QDomNodeList childNodes = rootE.childNodes(); + for (int i = 0; i < childNodes.count(); i++) { + QDomNode childNode = childNodes.item(i); + QDomElement childNodeE = childNode.toElement(); + if (childNodeE.isNull()) + continue; + + if (childNodeE.tagName() == QLatin1String("UpdateSource")) + d->addUpdateSourceFrom(childNodeE); + } + + d->clearError(); + emit reset(); +} + +void UpdateSourcesInfo::UpdateSourcesInfoData::saveChanges() +{ + if (!modified || fileName.isEmpty()) + return; + + const bool hadSaveError = error == UpdateSourcesInfo::CouldNotSaveChangesError; + + QDomDocument doc; + + QDomElement rootE = doc.createElement(QLatin1String("UpdateSources")); + doc.appendChild(rootE); + + for (int i = 0; i < updateSourceInfoList.count(); i++) { + UpdateSourceInfo info = updateSourceInfoList.at(i); + + QDomElement infoE = doc.createElement(QLatin1String("UpdateSource")); + rootE.appendChild(infoE); + addChildElement(doc, infoE, QLatin1String("Name"), info.name); + addChildElement(doc, infoE, QLatin1String("Title"), info.title); + addChildElement(doc, infoE, QLatin1String("Description"), info.description, + (info.description.length() && info.description.at(0) == QLatin1Char('<'))); + addChildElement(doc, infoE, QLatin1String("Url"), info.url.toString()); + } + + QFile file(fileName); + if (!file.open(QFile::WriteOnly)) { + error = UpdateSourcesInfo::CouldNotSaveChangesError; + errorMessage = tr("Could not save changes to \"%1\": %2").arg(fileName, file.errorString()); + return; + } + + QTextStream stream(&file); + doc.save(stream, 2); + stream.flush(); + file.close(); + + if (file.error() != QFile::NoError) { + error = UpdateSourcesInfo::CouldNotSaveChangesError; + errorMessage = tr("Could not save changes to \"%1\": %2").arg(fileName, file.errorString()); + return; + } + + //if there was a write error before, clear the error, as the write was successful now + if (hadSaveError) + clearError(); + + modified = false; +} + +void UpdateSourcesInfo::UpdateSourcesInfoData::addUpdateSourceFrom(const QDomElement &element) +{ + if (element.tagName() != QLatin1String("UpdateSource")) + return; + + QDomNodeList childNodes = element.childNodes(); + if (!childNodes.count()) + return; + + UpdateSourceInfo info; + + for (int i = 0; i<childNodes.count(); i++) { + QDomNode childNode = childNodes.item(i); + QDomElement childNodeE = childNode.toElement(); + if (childNodeE.isNull()) + continue; + + if (childNodeE.tagName() == QLatin1String("Name")) + info.name = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Title")) + info.title = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Description")) + info.description = childNodeE.text(); + else if (childNodeE.tagName() == QLatin1String("Url")) + info.url = childNodeE.text(); + } + + this->updateSourceInfoList.append(info); +} + +void UpdateSourcesInfo::UpdateSourcesInfoData::addChildElement(QDomDocument &doc, QDomElement &parentE, const QString &tagName, const QString &text, bool htmlText) +{ + QDomElement childE = doc.createElement(tagName); + parentE.appendChild(childE); + + if (htmlText) { + QDomCDATASection textE = doc.createCDATASection(text); + childE.appendChild(textE); + } else { + QDomText textE = doc.createTextNode(text); + childE.appendChild(textE); + } +} + +/*! + \ingroup kdupdater + \struct KDUpdater::UpdateSourceInfo kdupdaterupdatesourcesinfo.h KDUpdaterUpdateSourcesInfo + \brief Describes a single update source + + An update source is a repository that contains updates applicable for the application. + This structure describes a single update source in terms of name, title, description, url and priority. +*/ + +/*! + \var QString KDUpdater::UpdateSourceInfo::name +*/ + +/*! + \var QString KDUpdater::UpdateSourceInfo::title +*/ + +/*! + \var QString KDUpdater::UpdateSourceInfo::description +*/ + +/*! + \var QUrl KDUpdater::UpdateSourceInfo::url +*/ + +/*! + \var QUrl KDUpdater::UpdateSourceInfo::priority +*/ + +namespace KDUpdater { + +bool operator==(const UpdateSourceInfo &lhs, const UpdateSourceInfo &rhs) +{ + return lhs.name == rhs.name && lhs.title == rhs.title + && lhs.description == rhs.description && lhs.url == rhs.url; +} + +} // namespace KDUpdater diff --git a/src/libs/kdtools/kdupdaterupdatesourcesinfo.h b/src/libs/kdtools/kdupdaterupdatesourcesinfo.h new file mode 100644 index 000000000..2afeda966 --- /dev/null +++ b/src/libs/kdtools/kdupdaterupdatesourcesinfo.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#ifndef KD_UPDATER_UPDATE_SOURCES_INFO_H +#define KD_UPDATER_UPDATE_SOURCES_INFO_H + +#include "kdupdater.h" + +#include <QObject> +#include <QVariant> +#include <QUrl> + +namespace KDUpdater { + +class Application; + +struct KDTOOLS_EXPORT UpdateSourceInfo +{ + UpdateSourceInfo() : priority(-1) { } + + QString name; + QString title; + QString description; + QUrl url; + int priority; +}; + +KDTOOLS_EXPORT bool operator==(const UpdateSourceInfo &lhs, const UpdateSourceInfo &rhs); + +inline bool operator!=(const UpdateSourceInfo &lhs, const UpdateSourceInfo &rhs) +{ + return !operator==(lhs, rhs); +} + +class KDTOOLS_EXPORT UpdateSourcesInfo : public QObject +{ + Q_OBJECT + +public: + ~UpdateSourcesInfo(); + + enum Error + { + NoError = 0, + NotYetReadError, + CouldNotReadSourceFileError, + InvalidXmlError, + InvalidContentError, + CouldNotSaveChangesError + }; + + Application *application() const; + + bool isValid() const; + QString errorString() const; + Error error() const; + + bool isModified() const; + void setModified(bool modified); + + void setFileName(const QString &fileName); + QString fileName() const; + + int updateSourceInfoCount() const; + UpdateSourceInfo updateSourceInfo(int index) const; + + void addUpdateSourceInfo(const UpdateSourceInfo &info); + void removeUpdateSourceInfo(const UpdateSourceInfo &info); + void removeUpdateSourceInfoAt(int index); + void setUpdateSourceInfoAt(int index, const UpdateSourceInfo &info); + +protected: + explicit UpdateSourcesInfo(Application *application); + +public Q_SLOTS: + void refresh(); + +Q_SIGNALS: + void reset(); + void updateSourceInfoAdded(const UpdateSourceInfo &info); + void updateSourceInfoRemoved(const UpdateSourceInfo &info); + void updateSourceInfoChanged(const UpdateSourceInfo &newInfo, + const UpdateSourceInfo &oldInfo); + +private: + friend class Application; + struct UpdateSourcesInfoData; + UpdateSourcesInfoData *d; +}; + +} // namespace KDUpdater + +Q_DECLARE_METATYPE(KDUpdater::UpdateSourceInfo) + +#endif // KD_UPDATER_UPDATE_SOURCES_INFO_H |