diff --git a/src/lay/lay.pro b/src/lay/lay.pro index da3d53cb0..4c4c804f7 100644 --- a/src/lay/lay.pro +++ b/src/lay/lay.pro @@ -51,7 +51,8 @@ HEADERS = \ laySaltGrains.h \ laySaltManagerDialog.h \ laySaltGrainDetailsTextWidget.h \ - laySaltGrainPropertiesDialog.h + laySaltGrainPropertiesDialog.h \ + laySaltDownloadManager.h FORMS = \ ClipDialog.ui \ @@ -147,7 +148,8 @@ SOURCES = \ laySaltGrains.cc \ laySaltManagerDialog.cc \ laySaltGrainDetailsTextWidget.cc \ - laySaltGrainPropertiesDialog.cc + laySaltGrainPropertiesDialog.cc \ + laySaltDownloadManager.cc RESOURCES = layBuildInMacros.qrc \ layHelpResources.qrc \ diff --git a/src/lay/laySalt.cc b/src/lay/laySalt.cc index 24559a2d1..156cf0ec3 100644 --- a/src/lay/laySalt.cc +++ b/src/lay/laySalt.cc @@ -21,9 +21,14 @@ */ #include "laySalt.h" +#include "laySaltDownloadManager.h" #include "tlString.h" +#include "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" #include +#include namespace lay { @@ -47,6 +52,32 @@ Salt &Salt::operator= (const Salt &other) return *this; } +Salt::flat_iterator +Salt::begin_flat () +{ + validate (); + return mp_flat_grains.begin (); +} + +Salt::flat_iterator +Salt::end_flat () +{ + validate (); + return mp_flat_grains.end (); +} + +SaltGrain * +Salt::grain_by_name (const std::string &name) +{ + validate (); + std::map::const_iterator g = m_grains_by_name.find (name); + if (g != m_grains_by_name.end ()) { + return g->second; + } else { + return 0; + } +} + void Salt::add_location (const std::string &path) { @@ -60,8 +91,7 @@ Salt::add_location (const std::string &path) lay::SaltGrains gg = lay::SaltGrains::from_path (path); m_root.add_collection (gg); - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); } void @@ -71,8 +101,7 @@ Salt::remove_location (const std::string &path) for (lay::SaltGrains::collection_iterator g = m_root.begin_collections (); g != m_root.end_collections (); ++g) { if (QFileInfo (tl::to_qstring (g->path ())) == fi) { m_root.remove_collection (g, false); - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); return; } } @@ -87,8 +116,7 @@ Salt::refresh () } if (new_root != m_root) { m_root = new_root; - mp_flat_grains.clear (); - emit collections_changed (); + invalidate (); } } @@ -119,12 +147,150 @@ struct NameCompare } void -Salt::ensure_flat_present () +Salt::validate () { if (mp_flat_grains.empty ()) { + add_collection_to_flat (m_root); + + m_grains_by_name.clear (); + for (std::vector::const_iterator i = mp_flat_grains.begin (); i != mp_flat_grains.end (); ++i) { + m_grains_by_name.insert (std::make_pair ((*i)->name (), *i)); + } + + // NOTE: we intentionally sort after the name list has been built - this way + // the first entry will win in the name to grain map. std::sort (mp_flat_grains.begin (), mp_flat_grains.end (), NameCompare ()); + } } +void +Salt::invalidate () +{ + mp_flat_grains.clear (); + emit collections_changed (); +} + + +static +bool remove_from_collection (SaltGrains &collection, const std::string &name) +{ + bool res = false; + + for (SaltGrains::grain_iterator g = collection.begin_grains (); g != collection.end_grains (); ++g) { + if (g->name () == name) { + SaltGrains::grain_iterator gnext = g; + ++gnext; + collection.remove_grain (g, true); + res = true; + } + } + + for (SaltGrains::collection_iterator gg = collection.begin_collections (); gg != collection.end_collections (); ++gg) { + // TODO: remove this const_cast + if (remove_from_collection (const_cast (*gg), name)) { + res = true; + } + } + + return res; +} + +bool +Salt::remove_grain (const SaltGrain &grain) +{ + tl::info << QObject::tr ("Removing package '%1' ..").arg (tl::to_qstring (grain.name ())); + if (remove_from_collection (m_root, grain.name ())) { + tl::info << QObject::tr ("Package '%1' removed.").arg (tl::to_qstring (grain.name ())); + invalidate (); + return true; + } else { + tl::warn << QObject::tr ("Failed to remove package '%1'.").arg (tl::to_qstring (grain.name ())); + return false; + } +} + +bool +Salt::create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager) +{ + tl_assert (!m_root.is_empty ()); + + const SaltGrains *coll = m_root.begin_collections ().operator-> (); + + std::string path = target.path (); + if (! path.empty ()) { + coll = 0; + for (SaltGrains::collection_iterator gg = m_root.begin_collections (); gg != m_root.end_collections (); ++gg) { + if (tl::is_parent_path (tl::to_qstring (gg->path ()), tl::to_qstring (path))) { + coll = gg.operator-> (); + break; + } + } + tl_assert (coll != 0); + } + + tl::info << QObject::tr ("Installing package '%1' ..").arg (tl::to_qstring (target.name ())); + + QDir target_dir (tl::to_qstring (coll->path ())); + + try { + + // change down to the desired target location and create the directory structure while doing so + std::vector name_parts = tl::split (target.name (), "/"); + for (std::vector::const_iterator n = name_parts.begin (); n != name_parts.end (); ++n) { + QDir subdir (target_dir.filePath (tl::to_qstring (*n))); + if (! subdir.exists ()) { + if (! target_dir.mkdir (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to create target directory '%1' for installing package").arg (subdir.path ()))); + } + if (! target_dir.cd (tl::to_qstring (*n))) { + throw tl::Exception (tl::to_string (tr ("Unable to change to target directory '%1' for installing package").arg (subdir.path ()))); + } + } + } + + } catch (tl::Exception &ex) { + tl::error << ex.msg (); + return false; + } + + bool res = true; + + target = templ; + target.set_path (tl::to_string (target_dir.absolutePath ())); + + if (! templ.path ().empty ()) { + + // if the template represents an actual folder, use the files from there + tl::info << QObject::tr ("Copying package from '%1' to '%2' ..").arg (tl::to_qstring (templ.path ())).arg (tl::to_qstring (target.path ())); + res = tl::cp_dir_recursive (templ.path (), target.path ()); + + } else if (! templ.url ().empty ()) { + + // otherwise download from the URL + tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ())); + res = download_manager.download (templ.url (), target.path ()); + + } + + if (res) { + + tl::info << QObject::tr ("Package '%1' installed").arg (tl::to_qstring (target.name ())); + + target.set_installed_time (QDateTime::currentDateTime ()); + target.save (); + + } else { + + tl::warn << QObject::tr ("Failed to install package '%1' - removing files ..").arg (tl::to_qstring (target.name ())); + if (! tl::rm_dir_recursive (target.path ())) { + tl::warn << QObject::tr ("Failed to remove files").arg (tl::to_qstring (target.name ())); + } + + } + + return res; +} + } diff --git a/src/lay/laySalt.h b/src/lay/laySalt.h index 19d21145f..4ade8acf9 100644 --- a/src/lay/laySalt.h +++ b/src/lay/laySalt.h @@ -29,9 +29,13 @@ #include +#include + namespace lay { +class SaltDownloadManager; + /** * @brief The global salt (package manager) object * This object can be configured to represent a couple of locations. @@ -107,20 +111,48 @@ public: /** * @brief A flat iterator of (sorted) grains (begin) */ - flat_iterator begin_flat () - { - ensure_flat_present (); - return mp_flat_grains.begin (); - } + flat_iterator begin_flat (); /** * @brief A flat iterator of (sorted) grains (end) */ - flat_iterator end_flat () - { - ensure_flat_present (); - return mp_flat_grains.end (); - } + flat_iterator end_flat (); + + /** + * @brief Gets the grain with the given name + */ + SaltGrain *grain_by_name (const std::string &name); + + /** + * @brief Removes a grain from the salt + * + * This operation will remove the grain with the given name from the salt and delete all files and directories related to it. + * If multiple grains with the same name exist, they will all be removed. + * + * Returns true, if the package could be removed successfully. + */ + bool remove_grain (const SaltGrain &grain); + + /** + * @brief Creates a new grain from a template + * + * This method will create a folder for a grain with the given path and download or copy + * all files related to this grain. It will copy the download URL from the template into the + * new grain, so updates will come from the original location. + * + * The target's name must be set. If a specific target location is desired, the target's + * path must be set too. + * + * This method refuses to overwrite existing grains, so an update needs to be performed by first + * deleting the grain and then re-installing it. + * + * The target grain will be updated with the installation information. If the target grain + * contains an installation path prior to the installation, this path will be used for the + * installation of the grain files. + * + * Returns true, if the package could be created successfully. + */ + bool create_grain (const SaltGrain &templ, SaltGrain &target, SaltDownloadManager &download_manager); signals: /** @@ -131,8 +163,10 @@ signals: private: SaltGrains m_root; std::vector mp_flat_grains; + std::map m_grains_by_name; - void ensure_flat_present (); + void validate (); + void invalidate (); void add_collection_to_flat (lay::SaltGrains &gg); }; diff --git a/src/lay/laySaltDownloadManager.cc b/src/lay/laySaltDownloadManager.cc new file mode 100644 index 000000000..f6970b851 --- /dev/null +++ b/src/lay/laySaltDownloadManager.cc @@ -0,0 +1,40 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "laySaltDownloadManager.h" + +namespace lay +{ + +SaltDownloadManager::SaltDownloadManager () +{ + // .. nothing yet .. +} + +bool +SaltDownloadManager::download (const std::string &url, const std::string &target_dir) +{ + // @@@ + return false; +} + +} diff --git a/src/lay/laySaltDownloadManager.h b/src/lay/laySaltDownloadManager.h new file mode 100644 index 000000000..141df238a --- /dev/null +++ b/src/lay/laySaltDownloadManager.h @@ -0,0 +1,60 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef HDR_laySaltDownloadManager +#define HDR_laySaltDownloadManager + +#include "layCommon.h" + +#include +#include + +namespace lay +{ + +/** + * @brief The download manager + * This class is responsible for handling the downloads for + * grains. + */ +class LAY_PUBLIC SaltDownloadManager + : public QObject +{ +Q_OBJECT + +public: + /** + * @brief Default constructor + */ + SaltDownloadManager (); + + /** + * @brief Downloads the files from the given URL to the given target location + * The target directory needs to exist. + * Returns true, if the download was successful, false otherwise. + */ + bool download (const std::string &url, const std::string &target_dir); +}; + +} + +#endif diff --git a/src/lay/laySaltGrainPropertiesDialog.cc b/src/lay/laySaltGrainPropertiesDialog.cc index 88fd3d652..ab4e08d43 100644 --- a/src/lay/laySaltGrainPropertiesDialog.cc +++ b/src/lay/laySaltGrainPropertiesDialog.cc @@ -593,8 +593,10 @@ SaltGrainPropertiesDialog::exec_dialog (lay::SaltGrain *grain, lay::Salt *salt) update_controls (); bool res = exec (); - if (res) { + if (res && *grain != m_grain) { *grain = m_grain; + // save modified grain + grain->save (); } delete dependencies->itemDelegateForColumn (0); diff --git a/src/lay/laySaltManagerDialog.cc b/src/lay/laySaltManagerDialog.cc index 8c83aa9f8..7730310bc 100644 --- a/src/lay/laySaltManagerDialog.cc +++ b/src/lay/laySaltManagerDialog.cc @@ -313,6 +313,7 @@ SaltManagerDialog::current_changed () } else { details_frame->setEnabled (true); delete_button->setEnabled (true); + edit_button->setEnabled (! g->is_readonly ()); } } diff --git a/src/tl/tlFileUtils.cc b/src/tl/tlFileUtils.cc index 518bb7989..9ae545118 100644 --- a/src/tl/tlFileUtils.cc +++ b/src/tl/tlFileUtils.cc @@ -21,6 +21,8 @@ */ #include "tlFileUtils.h" +#include "tlLog.h" +#include "tlInternational.h" #include #include @@ -28,10 +30,27 @@ namespace tl { +bool +is_parent_path (const QString &parent, const QString &path) +{ + QFileInfo parent_info (parent); + QFileInfo path_info (path); + + while (parent_info != path_info) { + path_info = path_info.path (); + if (path_info.isRoot ()) { + return false; + } + } + + return true; +} + bool rm_dir_recursive (const QString &path) { QDir dir (path); + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { QFileInfo fi (dir.absoluteFilePath (*e)); @@ -39,15 +58,82 @@ rm_dir_recursive (const QString &path) if (! rm_dir_recursive (fi.filePath ())) { return false; } - if (! dir.rmdir (*e)) { - return false; - } } else if (fi.isFile ()) { if (! dir.remove (*e)) { + tl::error << QObject::tr ("Unable to remove file: %1").arg (dir.absoluteFilePath (*e)); return false; } } } + + QString name = dir.dirName (); + if (dir.cdUp ()) { + if (! dir.rmdir (name)) { + tl::error << QObject::tr ("Unable to remove directory: %1").arg (dir.absoluteFilePath (name)); + return false; + } + } + + return true; +} + +bool +cp_dir_recursive (const QString &source, const QString &target) +{ + QDir dir (source); + QDir dir_target (target); + + QStringList entries = dir.entryList (QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + for (QStringList::const_iterator e = entries.begin (); e != entries.end (); ++e) { + + QFileInfo fi (dir.absoluteFilePath (*e)); + QFileInfo fi_target (dir_target.absoluteFilePath (*e)); + + if (fi.isDir ()) { + + // Copy subdirectory + if (! fi_target.exists ()) { + if (! dir_target.mkdir (*e)) { + tl::error << QObject::tr ("Unable to create target directory: %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + } else if (! fi_target.isDir ()) { + tl::error << QObject::tr ("Unable to create target directory (is a file already): %1").arg (dir_target.absoluteFilePath (*e)); + return false; + } + if (! cp_dir_recursive (fi.filePath (), fi_target.filePath ())) { + return false; + } + + // TODO: leave symlinks symlinks? How to copy symlinks with Qt? + } else if (fi.isFile ()) { + + QFile file (fi.filePath ()); + QFile file_target (fi_target.filePath ()); + + if (! file.open (QIODevice::ReadOnly)) { + tl::error << QObject::tr ("Unable to open source file for reading: %1").arg (fi.filePath ()); + return false; + } + if (! file_target.open (QIODevice::WriteOnly)) { + tl::error << QObject::tr ("Unable to open target file for writing: %1").arg (fi_target.filePath ()); + return false; + } + + size_t chunk_size = 64 * 1024; + + while (! file.atEnd ()) { + QByteArray data = file.read (chunk_size); + file_target.write (data); + } + + file.close (); + file_target.close (); + + } + + } + return true; } diff --git a/src/tl/tlFileUtils.h b/src/tl/tlFileUtils.h index 01c171a99..be25bd843 100644 --- a/src/tl/tlFileUtils.h +++ b/src/tl/tlFileUtils.h @@ -24,17 +24,57 @@ #define HDR_tlFileUtils #include "tlCommon.h" +#include "tlString.h" #include namespace tl { +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path + */ +bool TL_PUBLIC is_parent_path (const QString &parent, const QString &path); + +/** + * @brief Returns a value indicating whether the parent path is a parent directory of the path (version with std::string) + */ +inline bool TL_PUBLIC is_parent_path (const std::string &parent, const std::string &path) +{ + return is_parent_path (tl::to_qstring (parent), tl::to_qstring (path)); +} + /** * @brief Recursively remove the given directory, the files from that directory and all sub-directories - * @return True, if successful. False otherwise. + * @return True, if successful. false otherwise. */ bool TL_PUBLIC rm_dir_recursive (const QString &path); +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC rm_dir_recursive (const std::string &path) +{ + return rm_dir_recursive (tl::to_qstring (path)); +} + +/** + * @brief Recursively copies a given directory to a target directory + * Both target and source directories need to exist. New directories are created in the target + * directory if required. + * @return True, if successful. false otherwise. + */ +bool TL_PUBLIC cp_dir_recursive (const QString &source, const QString &target); + +/** + * @brief Recursively remove the given directory, the files from that directory and all sub-directories (version with std::string) + * @return True, if successful. false otherwise. + */ +inline bool TL_PUBLIC cp_dir_recursive (const std::string &source, const std::string &target) +{ + return cp_dir_recursive (tl::to_qstring (source), tl::to_qstring (target)); +} + } #endif diff --git a/src/unit_tests/laySalt.cc b/src/unit_tests/laySalt.cc index 7eb557499..62a62826a 100644 --- a/src/unit_tests/laySalt.cc +++ b/src/unit_tests/laySalt.cc @@ -201,8 +201,6 @@ TEST (3) QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); - tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); - lay::SaltGrains gg; gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); EXPECT_EQ (gg.is_empty (), true); @@ -275,8 +273,6 @@ TEST (4) QDir dir_cc (dir_c.filePath (QString::fromUtf8 ("c"))); QDir dir_ccv (dir_cc.filePath (QString::fromUtf8 ("v"))); - tl_assert (tl::rm_dir_recursive (tmp_dir.path ())); - lay::SaltGrains gg; gg = lay::SaltGrains::from_path (tl::to_string (tmp_dir.path ())); EXPECT_EQ (gg.is_empty (), true); @@ -341,4 +337,8 @@ TEST (4) salt.remove_location (tl::to_string (dir_c.path ())); EXPECT_EQ (spy.count (), 0); EXPECT_EQ (salt_to_string (salt), "[b,c/c/v,c/u]"); + + EXPECT_EQ (salt.grain_by_name ("x"), 0); + EXPECT_EQ (salt.grain_by_name ("b")->name (), "b"); + EXPECT_EQ (salt.grain_by_name ("c/c/v")->name (), "c/c/v"); } diff --git a/src/unit_tests/tlFileUtils.cc b/src/unit_tests/tlFileUtils.cc new file mode 100644 index 000000000..7eb3032a8 --- /dev/null +++ b/src/unit_tests/tlFileUtils.cc @@ -0,0 +1,155 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2017 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "tlFileUtils.h" +#include "utHead.h" + +#include +#include +#include + +TEST (1) +{ + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home/matthias"), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/home"), "/home"), true); + EXPECT_EQ (tl::is_parent_path (std::string (""), ""), true); + EXPECT_EQ (tl::is_parent_path (std::string ("/opt/klayout"), "/home/matthias"), false); + EXPECT_EQ (tl::is_parent_path (std::string ("/home/klayout"), "/home/matthias"), false); +} + +TEST (2) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + EXPECT_EQ (tl::rm_dir_recursive (tl::to_string (adir.absolutePath ())), true); + EXPECT_EQ (adir.exists (), false); + + tmp_dir.mkdir (QString::fromUtf8 ("a")); + EXPECT_EQ (adir.exists (), true); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + EXPECT_EQ (adir.exists (), true); + EXPECT_EQ (tl::rm_dir_recursive (adir.absolutePath ()), true); + EXPECT_EQ (adir.exists (), false); + EXPECT_EQ (b1dir.exists (), false); + EXPECT_EQ (b2dir.exists (), false); + EXPECT_EQ (QFileInfo (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))).exists (), false); +} + +TEST (3) +{ + QDir tmp_dir = QFileInfo (tl::to_qstring (tmp_file ())).absoluteDir (); + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("a"))); + tmp_dir.mkdir (QString::fromUtf8 ("a")); + + QDir adir = tmp_dir; + adir.cd (QString::fromUtf8 ("a")); + + adir.mkdir (QString::fromUtf8 ("b1")); + QDir b1dir = adir; + b1dir.cd (QString::fromUtf8 ("b1")); + + adir.mkdir (QString::fromUtf8 ("b2")); + QDir b2dir = adir; + b2dir.cd (QString::fromUtf8 ("b2")); + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("x"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world!\n"); + file.close (); + } + + { + QFile file (b2dir.absoluteFilePath (QString::fromUtf8 ("y"))); + file.open (QIODevice::WriteOnly); + file.write ("hello, world II!\n"); + file.close (); + } + + tl::rm_dir_recursive (tmp_dir.filePath (QString::fromUtf8 ("acopy"))); + tmp_dir.mkdir (QString::fromUtf8 ("acopy")); + + tl::cp_dir_recursive (tmp_dir.absoluteFilePath (QString::fromUtf8 ("a")), tmp_dir.absoluteFilePath (QString::fromUtf8 ("acopy"))); + + QDir acopydir = tmp_dir; + EXPECT_EQ (acopydir.cd (QString::fromUtf8 ("acopy")), true); + EXPECT_EQ (acopydir.exists (), true); + + QDir b1copydir = acopydir; + EXPECT_EQ (b1copydir.cd (QString::fromUtf8 ("b1")), true); + EXPECT_EQ (b1copydir.exists (), true); + + QDir b2copydir = acopydir; + EXPECT_EQ (b2copydir.cd (QString::fromUtf8 ("b2")), true); + EXPECT_EQ (b2copydir.exists (), true); + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("x"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world!\n"); + file.close (); + } + + { + QFile file (b2copydir.absoluteFilePath (QString::fromUtf8 ("y"))); + EXPECT_EQ (file.exists (), true); + file.open (QIODevice::ReadOnly); + EXPECT_EQ (file.readAll ().constData (), "hello, world II!\n"); + file.close (); + } +} diff --git a/src/unit_tests/unit_tests.pro b/src/unit_tests/unit_tests.pro index 547d7616c..31dd201a4 100644 --- a/src/unit_tests/unit_tests.pro +++ b/src/unit_tests/unit_tests.pro @@ -96,7 +96,8 @@ SOURCES = \ tlXMLParser.cc \ gsiTest.cc \ tlFileSystemWatcher.cc \ - laySalt.cc + laySalt.cc \ + tlFileUtils.cc # main components: SOURCES += \ diff --git a/src/ut/utMain.cc b/src/ut/utMain.cc index 8a563e177..359e2bad5 100644 --- a/src/ut/utMain.cc +++ b/src/ut/utMain.cc @@ -26,6 +26,7 @@ #include "pya.h" #include "tlStaticObjects.h" #include "tlTimer.h" +#include "tlFileUtils.h" #include "layApplication.h" #include "gsiExpression.h" #include "gsiExternalMain.h" @@ -518,8 +519,12 @@ bool TestBase::do_test (const std::string & /*mode*/) { // Ensures the test temp directory is present QDir dir (testtmp ()); + QDir tmpdir (dir.absoluteFilePath (tl::to_qstring (m_testdir))); + if (tmpdir.exists () && ! tl::rm_dir_recursive (tmpdir.absolutePath ())) { + throw tl::Exception ("Unable to clean temporary dir: " + tl::to_string (tmpdir.absolutePath ())); + } if (! dir.mkpath (tl::to_qstring (m_testdir))) { - throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (dir.filePath (tl::to_qstring (m_test)))); + throw tl::Exception ("Unable to create path for temporary files: " + tl::to_string (tmpdir.absolutePath ())); } dir.cd (tl::to_qstring (m_testdir));