diff --git a/src/edt/edt/edtPartialService.h b/src/edt/edt/edtPartialService.h index ddbc4f9d7..f38a1bb71 100644 --- a/src/edt/edt/edtPartialService.h +++ b/src/edt/edt/edtPartialService.h @@ -37,6 +37,7 @@ #include "edtConfig.h" #include +#include namespace db { class Manager; diff --git a/src/tl/tl/tl.pro b/src/tl/tl/tl.pro index 8e82e0c2e..b1b8a6535 100644 --- a/src/tl/tl/tl.pro +++ b/src/tl/tl/tl.pro @@ -55,7 +55,9 @@ SOURCES = \ tlXMLParser.cc \ tlXMLWriter.cc \ tlThreadedWorkers.cc \ - tlThreads.cc + tlThreads.cc \ + tlDeferredExecution.cc \ + tlUri.cc HEADERS = \ tlAlgorithm.h \ @@ -104,7 +106,9 @@ HEADERS = \ tlXMLParser.h \ tlXMLWriter.h \ tlThreadedWorkers.h \ - tlThreads.h + tlThreads.h \ + tlDeferredExecution.h \ + tlUri.h equals(HAVE_CURL, "1") { @@ -134,11 +138,11 @@ equals(HAVE_CURL, "1") { !equals(HAVE_QT, "0") { HEADERS += \ - tlDeferredExecution.h \ + tlDeferredExecutionQt.h \ tlFileSystemWatcher.h \ SOURCES += \ - tlDeferredExecution.cc \ + tlDeferredExecutionQt.cc \ tlFileSystemWatcher.cc \ } diff --git a/src/tl/tl/tlDeferredExecution.cc b/src/tl/tl/tlDeferredExecution.cc index 30033a788..6f93f7bb2 100644 --- a/src/tl/tl/tlDeferredExecution.cc +++ b/src/tl/tl/tlDeferredExecution.cc @@ -22,31 +22,21 @@ #include "tlDeferredExecution.h" #include "tlAssert.h" -#include "tlLog.h" #include -#include +#if defined(HAVE_QT) +# include "tlDeferredExecutionQt.h" +#endif namespace tl { static DeferredMethodScheduler *s_inst = 0; -DeferredMethodScheduler::DeferredMethodScheduler (QObject *parent) - : QObject (parent), - m_disabled (0), m_scheduled (false) +DeferredMethodScheduler::DeferredMethodScheduler () + : m_disabled (0), m_scheduled (false) { - connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timer ())); - - m_timer.setInterval (0); // immediately - m_timer.setSingleShot (true); // just once - - // set up a fallback timer that cleans up pending execute jobs if something goes wrong - connect (&m_fallback_timer, SIGNAL (timeout ()), this, SLOT (timer ())); - m_fallback_timer.setInterval (500); - m_fallback_timer.setSingleShot (false); - tl_assert (! s_inst); s_inst = this; } @@ -59,31 +49,33 @@ DeferredMethodScheduler::~DeferredMethodScheduler () DeferredMethodScheduler * DeferredMethodScheduler::instance () { - if (! s_inst && qApp) { - new DeferredMethodScheduler (qApp); +// TODO: provide a way to register non-Qt schedulers +#if defined(HAVE_QT) + if (! s_inst) { + new DeferredMethodSchedulerQt (); } +#endif return s_inst; } void DeferredMethodScheduler::schedule (DeferredMethodBase *method) { - m_lock.lock (); + tl::MutexLocker locker (&m_lock); if (! method->m_scheduled || ! method->m_compressed) { m_methods.push_back (method); if (! m_scheduled) { - qApp->postEvent (this, new QEvent (QEvent::User)); + queue_event (); m_scheduled = true; } method->m_scheduled = true; } - m_lock.unlock (); } void DeferredMethodScheduler::unqueue (DeferredMethodBase *method) { - m_lock.lock (); + tl::MutexLocker locker (&m_lock); for (std::list::iterator m = m_methods.begin (); m != m_methods.end (); ) { std::list::iterator mm = m; ++mm; @@ -93,50 +85,18 @@ DeferredMethodScheduler::unqueue (DeferredMethodBase *method) } m = mm; } - m_lock.unlock (); } void DeferredMethodScheduler::do_enable (bool en) { - m_lock.lock (); + tl::MutexLocker locker (&m_lock); if (en) { tl_assert (m_disabled > 0); --m_disabled; } else { ++m_disabled; } - m_lock.unlock (); -} - -bool -DeferredMethodScheduler::event (QEvent *event) -{ - if (event->type () == QEvent::User) { - timer (); - return true; - } else { - return QObject::event (event); - } -} - -void -DeferredMethodScheduler::timer () -{ - if (m_disabled) { - // start again if disabled - m_timer.start (); - } else { - try { - do_execute (); - } catch (tl::Exception &ex) { - tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.msg (); - } catch (std::exception &ex) { - tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.what (); - } catch (...) { - tl::error << tl::to_string (QObject::tr ("Unspecific exception caught")); - } - } } void diff --git a/src/tl/tl/tlDeferredExecution.h b/src/tl/tl/tlDeferredExecution.h index c0877de65..3fee3969b 100644 --- a/src/tl/tl/tlDeferredExecution.h +++ b/src/tl/tl/tlDeferredExecution.h @@ -23,16 +23,9 @@ #ifndef HDR_tlDeferredExecution #define HDR_tlDeferredExecution -#if !defined(HAVE_QT) -# error tl::DeferredExecution not available without Qt -#endif - #include "tlCommon.h" #include "tlObject.h" - -#include -#include -#include +#include "tlThreads.h" #include @@ -69,9 +62,7 @@ template class DeferredMethod; * @brief The deferred method scheduler */ class TL_PUBLIC DeferredMethodScheduler - : public QObject { -Q_OBJECT public: /** * @brief The singleton instance of the scheduler @@ -117,29 +108,43 @@ public: } } -private slots: - void timer (); + /** + * @brief Gets a value indicating whether the scheduler is disabled + */ + bool is_disabled () const + { + return m_disabled; + } + +protected: + /** + * @brief Reimplementation of the interface: queue an event + * In effect, the event should later trigger a call to do_execute (). + */ + virtual void queue_event () = 0; + + /** + * @brief Executes the pending methods + */ + void do_execute (); -private: /** * @brief Constructor */ - DeferredMethodScheduler (QObject *parent); + DeferredMethodScheduler (); /** * @brief Destructor */ - ~DeferredMethodScheduler (); + virtual ~DeferredMethodScheduler (); +private: int m_disabled; bool m_scheduled; std::list m_methods; - QTimer m_timer, m_fallback_timer; - QMutex m_lock; + tl::Mutex m_lock; - virtual bool event (QEvent *event); void do_enable (bool en); - void do_execute (); }; /** diff --git a/src/tl/tl/tlDeferredExecutionQt.cc b/src/tl/tl/tlDeferredExecutionQt.cc new file mode 100644 index 000000000..1b0bbbefa --- /dev/null +++ b/src/tl/tl/tlDeferredExecutionQt.cc @@ -0,0 +1,87 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 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 "tlDeferredExecutionQt.h" +#include "tlException.h" +#include "tlLog.h" + +#include + +namespace tl +{ + +DeferredMethodSchedulerQt::DeferredMethodSchedulerQt () + : QObject (qApp), DeferredMethodScheduler () +{ + connect (&m_timer, SIGNAL (timeout ()), this, SLOT (timer ())); + + m_timer.setInterval (0); // immediately + m_timer.setSingleShot (true); // just once + + // set up a fallback timer that cleans up pending execute jobs if something goes wrong + connect (&m_fallback_timer, SIGNAL (timeout ()), this, SLOT (timer ())); + m_fallback_timer.setInterval (500); + m_fallback_timer.setSingleShot (false); +} + +DeferredMethodSchedulerQt::~DeferredMethodSchedulerQt () +{ + // .. nothing yet .. +} + +void +DeferredMethodSchedulerQt::queue_event () +{ + qApp->postEvent (this, new QEvent (QEvent::User)); +} + +bool +DeferredMethodSchedulerQt::event (QEvent *event) +{ + if (event->type () == QEvent::User) { + timer (); + return true; + } else { + return QObject::event (event); + } +} + +void +DeferredMethodSchedulerQt::timer () +{ + if (is_disabled ()) { + // start again if disabled + m_timer.start (); + } else { + try { + do_execute (); + } catch (tl::Exception &ex) { + tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.msg (); + } catch (std::exception &ex) { + tl::error << tl::to_string (QObject::tr ("Exception caught: ")) << ex.what (); + } catch (...) { + tl::error << tl::to_string (QObject::tr ("Unspecific exception caught")); + } + } +} + +} diff --git a/src/tl/tl/tlDeferredExecutionQt.h b/src/tl/tl/tlDeferredExecutionQt.h new file mode 100644 index 000000000..044aa9328 --- /dev/null +++ b/src/tl/tl/tlDeferredExecutionQt.h @@ -0,0 +1,75 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 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_tlDeferredExecutionQt +#define HDR_tlDeferredExecutionQt + +#include "tlCommon.h" +#include "tlDeferredExecution.h" + +#include +#include +#include + +#include + +namespace tl +{ + +/** + * @brief The deferred method scheduler + */ +class TL_PUBLIC DeferredMethodSchedulerQt + : public QObject, public DeferredMethodScheduler +{ +Q_OBJECT +public: + /** + * @brief Constructor + */ + DeferredMethodSchedulerQt (); + + /** + * @brief Destructor + */ + ~DeferredMethodSchedulerQt (); + +protected: + /** + * @brief Reimplementation of the interface: queue an event + * In effect, the event should later trigger a call to do_execute (). + */ + void queue_event (); + +private slots: + void timer (); + +private: + QTimer m_timer, m_fallback_timer; + + virtual bool event (QEvent *event); +}; + +} + +#endif + diff --git a/src/tl/tl/tlFileUtils.cc b/src/tl/tl/tlFileUtils.cc index dede3f6ce..ed23f366b 100644 --- a/src/tl/tl/tlFileUtils.cc +++ b/src/tl/tl/tlFileUtils.cc @@ -139,6 +139,15 @@ std::vector split_path (const std::string &p, bool keep_last) } parts.push_back (tl::normalized_part (std::string (cp0, 0, cp - cp0))); + } else if ((*cp == '\\' || *cp == '/') && cp[1] && isalpha (cp[1]) && cp[2] == ':') { + + // drive name in the form "/c:" or "\c:" + parts.push_back (std::string ()); + parts.back () += toupper (cp[1]); + parts.back () += ":"; + + cp += 3; + } while (*cp) { @@ -646,6 +655,18 @@ bool file_exists (const std::string &p) return stat_func (p, st) == 0; } +bool is_writable (const std::string &p) +{ + stat_struct st; + return stat_func (p, st) == 0 && (st.st_mode & S_IWUSR) != 0; +} + +bool is_readable (const std::string &p) +{ + stat_struct st; + return stat_func (p, st) == 0 && (st.st_mode & S_IRUSR) != 0; +} + bool is_dir (const std::string &p) { stat_struct st; diff --git a/src/tl/tl/tlFileUtils.h b/src/tl/tl/tlFileUtils.h index 2a1d2db2b..d73853007 100644 --- a/src/tl/tl/tlFileUtils.h +++ b/src/tl/tl/tlFileUtils.h @@ -96,6 +96,16 @@ std::string TL_PUBLIC extension (const std::string &s); */ bool TL_PUBLIC file_exists (const std::string &s); +/** + * @brief Returns true, if the given path is writable + */ +bool TL_PUBLIC is_writable (const std::string &s); + +/** + * @brief Returns true, if the given path is readable + */ +bool TL_PUBLIC is_readable (const std::string &s); + /** * @brief Returns true, if the given path is a directory */ diff --git a/src/tl/tl/tlHttpStreamCurl.cc b/src/tl/tl/tlHttpStreamCurl.cc index b1c3c1416..c29b79467 100644 --- a/src/tl/tl/tlHttpStreamCurl.cc +++ b/src/tl/tl/tlHttpStreamCurl.cc @@ -29,11 +29,8 @@ #include "tlAssert.h" #include "tlStaticObjects.h" #include "tlProgress.h" -#include "tlDeferredExecution.h" - -#include -#include -#include +#include "tlFileUtils.h" +#include "tlUri.h" #include #include @@ -58,8 +55,8 @@ namespace tl std::string server_from_url (const std::string &url) { - QUrl qurl (tl::to_qstring (url)); - return tl::to_string (qurl.scheme ()) + ":" + tl::to_string (qurl.host ()); + tl::URI uri (url); + return uri.scheme () + "://" + uri.authority (); } std::string parse_realm (const std::string &header) @@ -750,7 +747,7 @@ void CurlConnection::check () const } else if (m_status != 0) { - throw tl::HttpErrorException (tl::to_string (QObject::tr ("Connection error (%1)").arg (QString::fromLatin1 (m_error_msg))), m_status, m_url); + throw tl::HttpErrorException (tl::sprintf (tl::to_string (tr ("Connection error (%s)")), m_error_msg), m_status, m_url); } else if (m_http_status < 200 || m_http_status >= 300) { @@ -776,7 +773,7 @@ void CurlConnection::check () const if (error_text) { throw tl::HttpErrorException (error_text, m_http_status, m_url); } else { - throw tl::HttpErrorException (tl::to_string (QObject::tr ("HTTP error")), m_http_status, m_url); + throw tl::HttpErrorException (tl::to_string (tr ("HTTP error")), m_http_status, m_url); } } @@ -1013,7 +1010,7 @@ int CurlNetworkManager::tick () mc = curl_multi_fdset (mp_multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); if (mc != CURLM_OK) { - throw tl::HttpErrorException (tl::to_string (QObject::tr ("Connection error (curl_multi_fdset() failed)")), mc, std::string ()); + throw tl::HttpErrorException (tl::to_string (tr ("Connection error (curl_multi_fdset() failed)")), mc, std::string ()); } /* On success the value of maxfd is guaranteed to be >= -1. We call @@ -1170,7 +1167,7 @@ InputHttpStream::read (char *b, size_t n) tl::NoDeferredMethods silent; if (! m_progress.get ()) { - m_progress.reset (new tl::AbsoluteProgress (tl::to_string (QObject::tr ("Downloading")) + " " + m_connection->url (), 1)); + m_progress.reset (new tl::AbsoluteProgress (tl::to_string (tr ("Downloading")) + " " + m_connection->url (), 1)); } while (n > m_connection->read_available () && ! m_connection->finished () && CurlNetworkManager::instance ()->tick ()) { @@ -1200,13 +1197,13 @@ InputHttpStream::close () void InputHttpStream::reset () { - throw tl::Exception (tl::to_string (QObject::tr ("'reset' is not supported on HTTP input streams"))); + throw tl::Exception (tl::to_string (tr ("'reset' is not supported on HTTP input streams"))); } std::string InputHttpStream::filename () const { - return tl::to_string (QFileInfo (QUrl (tl::to_qstring (m_connection->url ())).path ()).fileName ()); + return tl::filename (tl::URI (m_connection->url ()).path ()); } std::string diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index 8de7fe508..d21402548 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -41,11 +41,7 @@ #include "tlException.h" #include "tlString.h" - -#if defined(HAVE_QT) -# include -# include -#endif +#include "tlUri.h" namespace tl { @@ -157,18 +153,13 @@ InputStream::InputStream (const std::string &abstract_path) mp_delegate = new InputHttpStream (abstract_path); } else #endif -#if !defined (_WIN32) // not available on Windows if (ex.test ("pipe:")) { mp_delegate = new InputPipe (ex.get ()); } else -#endif -#if defined(HAVE_QT) - // TODO: provide a substitute for QUrl if (ex.test ("file:")) { - QUrl url (tl::to_qstring (abstract_path)); - mp_delegate = new InputZLibFile (tl::to_string (url.toLocalFile ())); + tl::URI uri (abstract_path); + mp_delegate = new InputZLibFile (uri.path ()); } else -#endif { mp_delegate = new InputZLibFile (abstract_path); } @@ -183,16 +174,11 @@ std::string InputStream::absolute_path (const std::string &abstract_path) tl::Extractor ex (abstract_path.c_str ()); if (ex.test ("http:") || ex.test ("https:")) { return abstract_path; -#if !defined(_WIN32) // not available on Windows } else if (ex.test ("pipe:")) { return abstract_path; -#endif -#if defined(HAVE_QT) - // TODO: provide a substitute for QUrl } else if (ex.test ("file:")) { - QUrl url (tl::to_qstring (abstract_path)); - return tl::to_string (QFileInfo (url.toLocalFile ()).absoluteFilePath ()); -#endif + tl::URI uri (abstract_path); + return tl::absolute_path (uri.path ()); } else { return tl::absolute_file_path (abstract_path); } diff --git a/src/tl/tl/tlUri.cc b/src/tl/tl/tlUri.cc new file mode 100644 index 000000000..1137cba69 --- /dev/null +++ b/src/tl/tl/tlUri.cc @@ -0,0 +1,240 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 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 "tlUri.h" +#include "tlString.h" + +#include +#include + +namespace tl +{ + +static bool is_hex (char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static char hex2int (char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'A' && c <= 'F') { + return (c - 'A') + 10; + } else if (c >= 'a' || c <= 'f') { + return (c - 'a') + 10; + } else { + return 0; + } +} + +static char int2hex (char c) +{ + c &= 0xf; + if (c < 10) { + return c + '0'; + } else { + return c - 10 + 'A'; + } +} + +static std::string unescape (const std::string &s) +{ + std::string res; + for (const char *cp = s.c_str (); *cp; ++cp) { + if (*cp == '%' && is_hex (cp [1]) && is_hex (cp [2])) { + res += hex2int (cp[1]) * 16 + hex2int (cp[2]); + cp += 2; + } else { + res += *cp; + } + } + return res; +} + +static std::string escape (const std::string &s) +{ + const char *special = "?#[]$&'()*+,;"; + + std::string res; + for (const char *cp = s.c_str (); *cp; ++cp) { + if ((unsigned char) *cp <= 32 || (unsigned char) *cp >= 128 || strchr (special, *cp) != 0) { + res += "%"; + res += int2hex (*cp >> 4); + res += int2hex (*cp); + } else { + res += *cp; + } + } + return res; +} + +URI::URI () +{ + // .. nothing yet .. +} + + +URI::URI (const std::string &uri) +{ + tl::Extractor ex0 (uri.c_str ()); + tl::Extractor ex = ex0; + + if (ex.try_read_word (m_scheme) && *ex == ':') { + // got scheme + ++ex; + } else { + m_scheme.clear (); + ex = ex0; + } + m_scheme = unescape (m_scheme); + + bool prefer_authority = true; + if (m_scheme == "file") { + prefer_authority = false; + // other schemes? + } else if (m_scheme.empty ()) { + prefer_authority = false; + } + + ex0 = ex; + if (ex.test ("//") || (ex.test ("/") ? prefer_authority : prefer_authority)) { + // definitely an authority + while (! ex.at_end () && *ex != '/') { + m_authority += *ex; + ++ex; + } + } else { + ex = ex0; + } + m_authority = unescape (m_authority); + + // parse path + while (! ex.at_end () && *ex != '?' && *ex != '#') { + m_path += *ex; + ++ex; + } + m_path = unescape (m_path); + + // parse parameters + if (*ex == '?') { + ++ex; + while (! ex.at_end () && *ex != '#') { + std::string k, v; + while (! ex.at_end () && *ex != '=' && *ex != '&' && *ex != '#') { + k += *ex; + ++ex; + } + if (*ex == '=') { + ++ex; + while (! ex.at_end () && *ex != '&' && *ex != '#') { + v += *ex; + ++ex; + } + } + m_query[unescape (k)] = unescape (v); + if (*ex == '&') { + ++ex; + } + } + } + + if (*ex == '#') { + ++ex; + while (! ex.at_end ()) { + m_fragment += *ex; + ++ex; + } + } + m_fragment = unescape (m_fragment); +} + +std::string +URI::to_string () const +{ + std::string res; + + if (! m_scheme.empty ()) { + res += escape (m_scheme); + res += ":"; + } + + if (! m_authority.empty ()) { + res += "//"; + res += escape (m_authority); + } + + if (! m_path.empty ()) { + res += escape (m_path); + } + + if (! m_query.empty ()) { + for (std::map::const_iterator p = m_query.begin (); p != m_query.end (); ++p) { + res += (p == m_query.begin () ? "?" : "&"); + res += escape (p->first); + if (! p->second.empty ()) { + res += "="; + res += escape (p->second); + } + } + } + + if (! m_fragment.empty ()) { + res += "#"; + res += m_fragment; + } + + return res; +} + +URI +URI::resolved (const URI &other) const +{ + if (! other.scheme ().empty () && other.scheme () != scheme ()) { + return other; + } + if (! other.authority ().empty () && other.authority () != authority ()) { + return other; + } + + URI res = *this; + + // combine paths + // TODO: normalize? I.e. replace "x/y/../z" by "x/z"? + if (! other.path ().empty ()) { + if (other.path ()[0] == '/') { + res.m_path = other.path (); + } else { + res.m_path += "/"; + res.m_path += other.path (); + } + } + + res.m_query = other.query (); + res.m_fragment = other.fragment (); + + return res; +} + +} + diff --git a/src/tl/tl/tlUri.h b/src/tl/tl/tlUri.h new file mode 100644 index 000000000..704bd9d50 --- /dev/null +++ b/src/tl/tl/tlUri.h @@ -0,0 +1,123 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 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_tlURI +#define HDR_tlURI + +#include "tlCommon.h" + +#include +#include + +namespace tl +{ + +/** + * @brief A class representing a URI + * + * This class is able to parse the URI and deliver the parts. + */ + +class TL_PUBLIC URI +{ +public: + /** + * @brief Creates an empty URI + */ + URI (); + + /** + * @brief Creates an URI from the given string + */ + URI (const std::string &uri); + + /** + * @brief Returns the scheme part or an empty string if there is no scheme + */ + const std::string &scheme () const + { + return m_scheme; + } + + /** + * @brief Returns the authority part or an empty string if there is no authority + * The leading slashes and not part of the authority. + * Percent escaping is undone already. + */ + const std::string &authority () const + { + return m_authority; + } + + /** + * @brief Returns the path part or an empty string if there is no path + * The path contains the leading slash if there is a path. + * Percent escaping is undone already. + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Returns the query part or an empty map if there is no query + * The map is a map of keys vs. values. Percent escaping is undone + * in the keys and values. + */ + const std::map &query () const + { + return m_query; + } + + /** + * @brief Returns the fragment or an empty string if there is none + * Percent escaping is undone on the fragment already. + */ + const std::string &fragment () const + { + return m_fragment; + } + + /** + * @brief Turns the URI into a string + * Percent escaping is employed to escape special characters + */ + std::string to_string () const; + + /** + * @brief Resolves an URI relative to this one + */ + URI resolved (const URI &other) const; + +private: + std::string m_scheme; + std::string m_authority; + std::string m_path; + std::map m_query; + std::string m_fragment; +}; + +} + +#endif + diff --git a/src/tl/tl/tlWebDAV.cc b/src/tl/tl/tlWebDAV.cc index 35a91ea08..9350cfbc1 100644 --- a/src/tl/tl/tlWebDAV.cc +++ b/src/tl/tl/tlWebDAV.cc @@ -28,10 +28,10 @@ #include "tlInternational.h" #include "tlProgress.h" #include "tlLog.h" +#include "tlUri.h" +#include "tlFileUtils.h" #include -#include -#include namespace tl { @@ -125,15 +125,15 @@ tl::XMLStruct xml_struct ("multistatus", ) ); -static std::string item_name (const QString &path1, const QString &path2) +static std::string item_name (const std::string &path1, const std::string &path2) { - QStringList sl1 = path1.split (QChar ('/')); - if (! sl1.empty () && sl1.back ().isEmpty ()) { + std::vector sl1 = tl::split (path1, "/"); + if (! sl1.empty () && sl1.back ().empty ()) { sl1.pop_back (); } - QStringList sl2 = path2.split (QChar ('/')); - if (! sl2.empty () && sl2.back ().isEmpty ()) { + std::vector sl2 = tl::split (path2, "/"); + if (! sl2.empty () && sl2.back ().empty ()) { sl2.pop_back (); } @@ -141,16 +141,16 @@ static std::string item_name (const QString &path1, const QString &path2) // This is the top-level item (echoed in the PROPFIND response) return std::string (); } else if (! sl2.empty ()) { - return tl::to_string (sl2.back ()); + return sl2.back (); } else { - throw tl::Exception (tl::to_string (QObject::tr ("Invalid WebDAV response: %1 is not a collection sub-item of %2").arg (path2).arg (path1))); + throw tl::Exception (tl::to_string (tr ("Invalid WebDAV response: %s is not a collection sub-item of %s")), path2, path1); } } void WebDAVObject::read (const std::string &url, int depth) { - QUrl base_url = QUrl (tl::to_qstring (url)); + tl::URI base_uri (url); tl::InputHttpStream http (url); http.add_header ("User-Agent", "SVN"); @@ -169,10 +169,10 @@ WebDAVObject::read (const std::string &url, int depth) for (MultiStatus::iterator r = multistatus.begin (); r != multistatus.end (); ++r) { bool is_collection = r->propstat.prop.resourcetype.is_collection; - QUrl item_url = base_url.resolved (QUrl (tl::to_qstring (r->href))); + tl::URI item_url = base_uri.resolved (tl::URI (r->href)); - std::string n = item_name (base_url.path (), item_url.path ()); - std::string item_url_string = tl::to_string (item_url.toString ()); + std::string n = item_name (base_uri.path (), item_url.path ()); + std::string item_url_string = item_url.to_string (); if (! n.empty ()) { m_items.push_back (WebDAVItem (is_collection, item_url_string, n)); @@ -211,36 +211,35 @@ void fetch_download_items (const std::string &url, const std::string &target, st if (object.is_collection ()) { - QDir dir (tl::to_qstring (target)); - if (! dir.exists ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Download failed: target directory '%1' does not exists").arg (dir.path ()))); + if (! tl::file_exists (target)) { + throw tl::Exception (tl::to_string (tr ("Download failed: target directory '%s' does not exists")), target); } for (WebDAVObject::iterator i = object.begin (); i != object.end (); ++i) { - QFileInfo new_item (dir.absoluteFilePath (tl::to_qstring (i->name ()))); + std::string item_path = tl::absolute_file_path (tl::combine_path (target, i->name ())); if (i->is_collection ()) { - if (! new_item.exists ()) { - if (! dir.mkdir (tl::to_qstring (i->name ()))) { - throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1'").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + if (! tl::file_exists (item_path)) { + if (! tl::mkpath (item_path)) { + throw tl::Exception (tl::to_string (tr ("Download failed: unable to create subdirectory '%s' in '%s'")), i->name (), target); } - } else if (! new_item.isDir ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - is already a file").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); - } else if (! new_item.isWritable ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Download failed: unable to create subdirectory '%2' in '%1' - no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + } else if (! tl::is_dir (item_path)) { + throw tl::Exception (tl::to_string (tr ("Download failed: unable to create subdirectory '%s' in '%s' - is already a file")), i->name (), target); + } else if (! tl::is_writable (item_path)) { + throw tl::Exception (tl::to_string (tr ("Download failed: unable to create subdirectory '%s' in '%s' - no write permissions")), i->name (), target); } - fetch_download_items (i->url (), tl::to_string (new_item.filePath ()), items, progress); + fetch_download_items (i->url (), item_path, items, progress); } else { - if (new_item.exists () && ! new_item.isWritable ()) { - throw tl::Exception (tl::to_string (QObject::tr ("Download failed: file is '%2' in '%1' - already exists, but no write permissions").arg (dir.path ()).arg (tl::to_qstring (i->name ())))); + if (tl::file_exists (item_path) && ! tl::is_writable (item_path)) { + throw tl::Exception (tl::to_string (tr ("Download failed: file is '%s' in '%s' - already exists, but no write permissions")), i->name (), target); } - items.push_back (DownloadItem (i->url (), tl::to_string (dir.absoluteFilePath (tl::to_qstring (i->name ()))))); + items.push_back (DownloadItem (i->url (), item_path)); } } @@ -266,25 +265,25 @@ WebDAVObject::download (const std::string &url, const std::string &target) try { - tl::info << QObject::tr ("Fetching file structure from ") << url; - tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Fetching directory structure from %1").arg (tl::to_qstring (url)))); + tl::info << tr ("Fetching file structure from ") << url; + tl::AbsoluteProgress progress (tl::sprintf (tl::to_string (tr ("Fetching directory structure from %s")), url)); fetch_download_items (url, target, items, progress); } catch (tl::Exception &ex) { - tl::error << QObject::tr ("Error downloading file structure from '") << url << "':" << tl::endl << ex.msg (); + tl::error << tr ("Error downloading file structure from '") << url << "':" << tl::endl << ex.msg (); return false; } bool has_errors = false; { - tl::info << tl::to_string (QObject::tr ("Downloading %1 file(s) now ..").arg (items.size ())); + tl::info << tl::sprintf (tl::to_string (tr ("Downloading %d file(s) now ..")), items.size ()); - tl::RelativeProgress progress (tl::to_string (QObject::tr ("Downloading file(s) from %1").arg (tl::to_qstring (url))), items.size (), 1); + tl::RelativeProgress progress (tl::sprintf (tl::to_string (tr ("Downloading file(s) from %s")), url), items.size (), 1); for (std::list::const_iterator i = items.begin (); i != items.end (); ++i) { - tl::info << QObject::tr ("Downloading '%1' to '%2' ..").arg (tl::to_qstring (i->url)).arg (tl::to_qstring (i->path)); + tl::info << tl::sprintf (tr ("Downloading '%s' to '%s' .."), i->url, i->path); try { @@ -293,7 +292,7 @@ WebDAVObject::download (const std::string &url, const std::string &target) is->copy_to (os); } catch (tl::Exception &ex) { - tl::error << QObject::tr ("Error downloading file from '") << i->url << "':" << tl::endl << ex.msg (); + tl::error << tr ("Error downloading file from '") << i->url << "':" << tl::endl << ex.msg (); has_errors = true; } diff --git a/src/tl/tl/tlXMLParser.cc b/src/tl/tl/tlXMLParser.cc index 61bc6aba2..3b888d55b 100644 --- a/src/tl/tl/tlXMLParser.cc +++ b/src/tl/tl/tlXMLParser.cc @@ -215,6 +215,16 @@ void XMLCALL start_element_handler (void *user_data, const XML_Char *name, const void XMLCALL end_element_handler (void *user_data, const XML_Char *name); void XMLCALL cdata_handler (void *user_data, const XML_Char *s, int len); +static std::string get_lname (const std::string &name) +{ + size_t colon = name.find (':'); + if (colon != std::string::npos) { + return std::string (name, colon + 1, name.size () - colon - 1); + } else { + return name; + } +} + class XMLParserPrivateData { public: @@ -235,8 +245,8 @@ public: void start_element (const std::string &name) { try { - // TODO: separate qname and lname? - mp_struct_handler->start_element (std::string (), name, name); + // TODO: Provide namespace URI? + mp_struct_handler->start_element (std::string (), get_lname (name), name); } catch (tl::Exception &ex) { error (ex); } @@ -245,8 +255,8 @@ public: void end_element (const std::string &name) { try { - // TODO: separate qname and lname? - mp_struct_handler->end_element (std::string (), name, name); + // TODO: Provide namespace URI? + mp_struct_handler->end_element (std::string (), get_lname (name), name); } catch (tl::Exception &ex) { error (ex); } @@ -861,6 +871,10 @@ XMLStructureHandler::start_element (const std::string &uri, const std::string &l void XMLStructureHandler::end_element (const std::string &uri, const std::string &lname, const std::string &qname) { + if (m_stack.empty ()) { + return; + } + const XMLElementBase *element = m_stack.back (); m_stack.pop_back (); @@ -876,7 +890,7 @@ XMLStructureHandler::end_element (const std::string &uri, const std::string &lna void XMLStructureHandler::characters (const std::string &t) { - if (m_stack.back ()) { + if (! m_stack.empty () && m_stack.back ()) { m_stack.back ()->cdata (t, *mp_state); } } diff --git a/src/tl/unit_tests/tlFileUtils.cc b/src/tl/unit_tests/tlFileUtils.cc index b071f7900..73dabc3f8 100644 --- a/src/tl/unit_tests/tlFileUtils.cc +++ b/src/tl/unit_tests/tlFileUtils.cc @@ -168,6 +168,8 @@ TEST (3) } } +#endif + // Secret mode switchers for testing namespace tl { @@ -225,6 +227,8 @@ TEST (10) EXPECT_EQ (tl::normalize_path ("\\"), "\\"); EXPECT_EQ (tl::normalize_path ("/"), "\\"); EXPECT_EQ (tl::normalize_path ("d:"), "D:"); + EXPECT_EQ (tl::normalize_path ("/d:"), "D:"); + EXPECT_EQ (tl::normalize_path ("\\d:"), "D:"); EXPECT_EQ (tl::normalize_path ("\\\\"), "\\\\"); EXPECT_EQ (tl::normalize_path ("//"), "\\\\"); EXPECT_EQ (tl::normalize_path ("d:\\"), "D:\\"); @@ -370,8 +374,12 @@ TEST(13) tl::rm_dir_recursive (tt); EXPECT_EQ (tl::file_exists (tt), false); + EXPECT_EQ (tl::is_readable (tt), false); + EXPECT_EQ (tl::is_writable (tt), false); EXPECT_EQ (tl::mkpath (tt), true); EXPECT_EQ (tl::file_exists (tt), true); + EXPECT_EQ (tl::is_readable (tt), true); + EXPECT_EQ (tl::is_writable (tt), true); tl::rm_dir_recursive (tt); EXPECT_EQ (tl::file_exists (tt), false); @@ -393,6 +401,8 @@ TEST(13) EXPECT_EQ (tl::file_exists (tt), false); } +#if defined(HAVE_QT) + // absolute_path, relative_path and absolute_file_path TEST(14) { @@ -412,6 +422,8 @@ TEST(14) EXPECT_EQ (tl::relative_path (xpath2, tl::combine_path (xpath, "a")), "doesnotexist/a"); } +#endif + // dir_entries TEST(15) { @@ -497,4 +509,3 @@ TEST(15) EXPECT_EQ (tl::rm_dir (tt), false); // not empty } -#endif diff --git a/src/tl/unit_tests/tlUri.cc b/src/tl/unit_tests/tlUri.cc new file mode 100644 index 000000000..4d138f44e --- /dev/null +++ b/src/tl/unit_tests/tlUri.cc @@ -0,0 +1,144 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2018 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 "tlUri.h" +#include "tlUnitTest.h" + +std::string uri2string (const tl::URI &uri) +{ + std::string res; + + if (! uri.scheme ().empty ()) { + res += "<" + uri.scheme () + ">"; + res += ":"; + } + + if (! uri.authority ().empty ()) { + res += "//<"; + res += uri.authority (); + res += ">"; + } + + if (! uri.path ().empty ()) { + res += "<" + uri.path () + ">"; + } + + if (! uri.query ().empty ()) { + for (std::map::const_iterator p = uri.query ().begin (); p != uri.query ().end (); ++p) { + res += (p == uri.query ().begin () ? "?<" : "&<"); + res += p->first; + res += ">"; + if (! p->second.empty ()) { + res += "=<"; + res += p->second; + res += ">"; + } + } + } + + if (! uri.fragment ().empty ()) { + res += "#<"; + res += uri.fragment (); + res += ">"; + } + + return res; +} + +// basic parsing ability +TEST(1) +{ + tl::URI uri; + EXPECT_EQ (uri2string (uri), ""); + + uri = tl::URI ("scheme:"); + EXPECT_EQ (uri2string (uri), ":"); + + uri = tl::URI ("http:www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), "://"); + + uri = tl::URI ("http:/www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), "://"); + + uri = tl::URI ("http://www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), "://"); + EXPECT_EQ (uri.to_string (), "http://www.klayout.de/path/to/file"); + + uri = tl::URI ("www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), ""); + + uri = tl::URI ("/www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), ""); + + uri = tl::URI ("//www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), "//"); + EXPECT_EQ (uri.to_string (), "//www.klayout.de/path/to/file"); + + uri = tl::URI ("file:www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), ":"); + + uri = tl::URI ("file:/www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), ":"); + + uri = tl::URI ("file://www.klayout.de/path/to/file"); + EXPECT_EQ (uri2string (uri), "://"); + + uri = tl::URI ("file:///path/to/file"); + EXPECT_EQ (uri2string (uri), ":"); + + uri = tl::URI ("file:///c:/path/to/file"); + EXPECT_EQ (uri2string (uri), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("http://www.klayout.de"))), "://"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("http:///other"))), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("../other"))), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("/other"))), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("file:../other"))), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("file:../other?a=b#frag"))), ":?=#"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("file:/other"))), ":"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("file:/other?a=b#frag"))), ":?=#"); + + uri = tl::URI ("//www.klayout.de/path/to/file?a=b"); + EXPECT_EQ (uri2string (uri), "//?="); + + uri = tl::URI ("/path/to/file?a=b"); + EXPECT_EQ (uri2string (uri), "?="); + + uri = tl::URI ("/path/to/file?a=v1&c=v3&b=v2"); + EXPECT_EQ (uri2string (uri), "?=&=&="); + + uri = tl::URI ("/path/to/file?a=v1&c=v3&b=v2#fragment"); + EXPECT_EQ (uri2string (uri), "?=&=&=#"); + EXPECT_EQ (uri.to_string (), "/path/to/file?a=v1&b=v2&c=v3#fragment"); + + uri = tl::URI ("/path/to/file#fragment"); + EXPECT_EQ (uri2string (uri), "#"); + EXPECT_EQ (uri.to_string (), "/path/to/file#fragment"); + + uri = tl::URI ("/path/to/%2c%2C%20%file#fragment"); + EXPECT_EQ (uri2string (uri), "#"); + EXPECT_EQ (uri.to_string (), "/path/to/%2C%2C%20%file#fragment"); + EXPECT_EQ (tl::URI (uri.to_string ()).to_string (), "/path/to/%2C%2C%20%file#fragment"); + + uri = tl::URI ("/path/to/file?%61=v%31&%63=v%33&%62=v%32#fragment"); + EXPECT_EQ (uri2string (uri), "?=&=&=#"); + EXPECT_EQ (uri2string (uri.resolved (tl::URI ("../other"))), ""); +} diff --git a/src/tl/unit_tests/tlWebDAV.cc b/src/tl/unit_tests/tlWebDAV.cc index b8d79e1fe..864e3586c 100644 --- a/src/tl/unit_tests/tlWebDAV.cc +++ b/src/tl/unit_tests/tlWebDAV.cc @@ -23,8 +23,7 @@ #include "tlWebDAV.h" #include "tlUnitTest.h" - -#include +#include "tlFileUtils.h" static std::string test_url1 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata"); static std::string test_url2 ("http://www.klayout.org/svn-public/klayout-resources/trunk/testdata/text"); @@ -96,34 +95,29 @@ TEST(5) { tl::WebDAVObject collection; - QDir tmp_dir (tl::to_qstring (tmp_file ("tmp"))); - EXPECT_EQ (tmp_dir.exists (), false); + std::string tmp_dir (tmp_file ("tmp")); + EXPECT_EQ (tl::file_exists (tmp_dir), false); - tmp_dir.cdUp (); - tmp_dir.mkdir (tl::to_qstring ("tmp")); - tmp_dir.cd (tl::to_qstring ("tmp")); + tl::mkpath (tmp_dir); + EXPECT_EQ (tl::file_exists (tmp_dir), true); - bool res = collection.download (test_url1, tl::to_string (tmp_dir.absolutePath ())); + bool res = collection.download (test_url1, tl::absolute_file_path (tmp_dir)); EXPECT_EQ (res, true); - QDir dir1 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir1"))); - QDir dir2 (tmp_dir.absoluteFilePath (QString::fromUtf8 ("dir2"))); - QDir dir21 (dir2.absoluteFilePath (QString::fromUtf8 ("dir21"))); - EXPECT_EQ (dir1.exists (), true); - EXPECT_EQ (dir2.exists (), true); - EXPECT_EQ (dir21.exists (), true); + std::string dir1 (tl::absolute_file_path (tl::combine_path (tmp_dir, "dir1"))); + std::string dir2 (tl::absolute_file_path (tl::combine_path (tmp_dir, "dir2"))); + std::string dir21 (tl::absolute_file_path (tl::combine_path (dir2, "dir21"))); + EXPECT_EQ (tl::file_exists (dir1), true); + EXPECT_EQ (tl::file_exists (dir2), true); + EXPECT_EQ (tl::file_exists (dir21), true); - QByteArray ba; - - QFile text1 (dir1.absoluteFilePath (QString::fromUtf8 ("text"))); - text1.open (QIODevice::ReadOnly); - ba = text1.read (10000); - EXPECT_EQ (ba.constData (), "A text.\n"); + tl::InputStream text1 (tl::combine_path (dir1, "text")); + std::string ba1 = text1.read_all (); + EXPECT_EQ (ba1, "A text.\n"); text1.close (); - QFile text21 (dir21.absoluteFilePath (QString::fromUtf8 ("text"))); - text21.open (QIODevice::ReadOnly); - ba = text21.read (10000); - EXPECT_EQ (ba.constData (), "A text II.I.\n"); + tl::InputStream text21 (tl::combine_path (dir21, "text")); + std::string ba21 = text21.read_all (); + EXPECT_EQ (ba21, "A text II.I.\n"); text21.close (); } diff --git a/src/tl/unit_tests/tlXMLParser.cc b/src/tl/unit_tests/tlXMLParser.cc index da4f1f332..ecc745b8f 100644 --- a/src/tl/unit_tests/tlXMLParser.cc +++ b/src/tl/unit_tests/tlXMLParser.cc @@ -195,7 +195,7 @@ TEST (5) #if defined (HAVE_QT) EXPECT_EQ (error, "XML parser error: tag mismatch in line 2, column 33"); #else - EXPECT_EQ (error, "XML parser error: mismatched tag in line 2, column 29"); + EXPECT_EQ (error, "XML parser error: mismatched tag in line 2, column 28"); #endif } diff --git a/src/tl/unit_tests/unit_tests.pro b/src/tl/unit_tests/unit_tests.pro index fb987e542..212db744c 100644 --- a/src/tl/unit_tests/unit_tests.pro +++ b/src/tl/unit_tests/unit_tests.pro @@ -30,13 +30,14 @@ SOURCES = \ tlVariant.cc \ tlInt128Support.cc \ tlXMLParser.cc \ + tlUri.cc \ + tlWebDAV.cc \ equals(HAVE_QT, "0") { # nothing } else { SOURCES += \ - tlWebDAV.cc \ tlDeferredExecution.cc \ tlFileSystemWatcher.cc \