1
0
Fork 0
forked from blue/squawk

transitioned urlstorage to LMDBAL, made it possible to build against latest qxmpp

This commit is contained in:
Blue 2023-04-15 15:07:27 -03:00
parent 81cf0f8d34
commit 5fbb03fc46
Signed by untrusted user: blue
GPG key ID: 9B203B252A63EE38
10 changed files with 363 additions and 599 deletions

View file

@ -1,11 +1,13 @@
set(SOURCE_FILES
networkaccess.cpp
clientcache.cpp
urlstorage.cpp
)
set(HEADER_FILES
networkaccess.h
clientcache.h
urlstorage.h
)
target_sources(squawk PRIVATE

View file

@ -35,13 +35,11 @@ Core::NetworkAccess::NetworkAccess(QObject* parent):
currentPath = settings.value("downloadsPath").toString();
}
Core::NetworkAccess::~NetworkAccess()
{
Core::NetworkAccess::~NetworkAccess() {
stop();
}
void Core::NetworkAccess::downladFile(const QString& url)
{
void Core::NetworkAccess::downladFile(const QString& url) {
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
qDebug() << "NetworkAccess received a request to download a file" << url << ", but the file is currently downloading, skipping";
@ -50,27 +48,25 @@ void Core::NetworkAccess::downladFile(const QString& url)
std::pair<QString, std::list<Shared::MessageInfo>> p = storage.getPath(url);
if (p.first.size() > 0) {
QFileInfo info(p.first);
if (info.exists() && info.isFile()) {
if (info.exists() && info.isFile())
emit downloadFileComplete(p.second, p.first);
} else {
else
startDownload(p.second, url);
}
} else {
startDownload(p.second, url);
}
} catch (const Archive::NotFound& e) {
} catch (const LMDBAL::NotFound& e) {
qDebug() << "NetworkAccess received a request to download a file" << url << ", but there is now record of which message uses that file, downloading anyway";
storage.addFile(url);
startDownload(std::list<Shared::MessageInfo>(), url);
} catch (const Archive::Unknown& e) {
} catch (const LMDBAL::Unknown& e) {
qDebug() << "Error requesting file path:" << e.what();
emit loadFileError(std::list<Shared::MessageInfo>(), QString("Database error: ") + e.what(), false);
}
}
}
void Core::NetworkAccess::start()
{
void Core::NetworkAccess::start() {
if (!running) {
manager = new QNetworkAccessManager();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
@ -81,8 +77,7 @@ void Core::NetworkAccess::start()
}
}
void Core::NetworkAccess::stop()
{
void Core::NetworkAccess::stop() {
if (running) {
storage.close();
manager->deleteLater();
@ -96,8 +91,7 @@ void Core::NetworkAccess::stop()
}
}
void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = downloads.find(url);
@ -115,8 +109,7 @@ void Core::NetworkAccess::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
}
}
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
{
void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code) {
qDebug() << "DEBUG: DOWNLOAD ERROR";
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
qDebug() << rpl->errorString();
@ -134,8 +127,7 @@ void Core::NetworkAccess::onDownloadError(QNetworkReply::NetworkError code)
}
}
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
{
void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors) {
qDebug() << "DEBUG: DOWNLOAD SSL ERRORS";
for (const QSslError& err : errors) {
qDebug() << err.errorString();
@ -154,9 +146,7 @@ void Core::NetworkAccess::onDownloadSSLError(const QList<QSslError>& errors)
}
}
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
{
QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code) {
QString errorText("");
switch (code) {
case QNetworkReply::NoError:
@ -280,8 +270,7 @@ QString Core::NetworkAccess::getErrorText(QNetworkReply::NetworkError code)
}
void Core::NetworkAccess::onDownloadFinished()
{
void Core::NetworkAccess::onDownloadFinished() {
qDebug() << "DEBUG: DOWNLOAD FINISHED";
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
@ -296,11 +285,11 @@ void Core::NetworkAccess::onDownloadFinished()
QStringList hops = url.split("/");
QString fileName = hops.back();
QString jid;
if (dwn->messages.size() > 0) {
if (dwn->messages.size() > 0)
jid = dwn->messages.front().jid;
} else {
else
qDebug() << "An attempt to save the file but it doesn't seem to belong to any message, download is definately going to be broken";
}
QString path = prepareDirectory(jid);
if (path.size() > 0) {
path = checkFileName(fileName, path);
@ -319,11 +308,10 @@ void Core::NetworkAccess::onDownloadFinished()
err = "Couldn't prepare a directory for file";
}
if (path.size() > 0) {
if (path.size() > 0)
emit downloadFileComplete(dwn->messages, path);
} else {
else
emit loadFileError(dwn->messages, "Error saving file " + url + "; " + err, false);
}
}
dwn->reply->deleteLater();
@ -332,8 +320,7 @@ void Core::NetworkAccess::onDownloadFinished()
}
}
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url)
{
void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& msgs, const QString& url) {
Transfer* dwn = new Transfer({msgs, 0, 0, true, "", url, 0});
QNetworkRequest req(url);
dwn->reply = manager->get(req);
@ -349,8 +336,7 @@ void Core::NetworkAccess::startDownload(const std::list<Shared::MessageInfo>& ms
emit loadFileProgress(dwn->messages, 0, false);
}
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
{
void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code) {
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@ -368,8 +354,7 @@ void Core::NetworkAccess::onUploadError(QNetworkReply::NetworkError code)
}
}
void Core::NetworkAccess::onUploadFinished()
{
void Core::NetworkAccess::onUploadFinished() {
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@ -389,20 +374,16 @@ void Core::NetworkAccess::onUploadFinished()
// Copy {TEMPDIR}/squawk_img_attach_XXXXXX.png to Download folder
bool copyResult = QFile::copy(upl->path, Shared::resolvePath(newPath));
if (copyResult) {
// Change storage
upl->path = newPath;
} else {
if (copyResult)
upl->path = newPath; // Change storage
else
err = "copying to " + newPath + " failed";
}
} else {
err = "Couldn't prepare a directory for file";
}
if (err.size() != 0) {
if (err.size() != 0)
qDebug() << "failed to copy temporary upload file " << upl->path << " to download folder:" << err;
}
}
storage.addFile(upl->messages, upl->url, upl->path);
@ -417,8 +398,7 @@ void Core::NetworkAccess::onUploadFinished()
}
}
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTotal) {
QNetworkReply* rpl = static_cast<QNetworkReply*>(sender());
QString url = rpl->url().toString();
std::map<QString, Transfer*>::const_iterator itr = uploads.find(url);
@ -436,13 +416,12 @@ void Core::NetworkAccess::onUploadProgress(qint64 bytesReceived, qint64 bytesTot
}
}
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
{
QString Core::NetworkAccess::getFileRemoteUrl(const QString& path) {
QString p = Shared::squawkifyPath(path);
try {
p = storage.getUrl(p);
} catch (const Archive::NotFound& err) {
} catch (const LMDBAL::NotFound& err) {
p = "";
} catch (...) {
throw;
@ -451,8 +430,13 @@ QString Core::NetworkAccess::getFileRemoteUrl(const QString& path)
return p;
}
void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QString& path, const QUrl& put, const QUrl& get, const QMap<QString, QString> headers)
{
void Core::NetworkAccess::uploadFile(
const Shared::MessageInfo& info,
const QString& path,
const QUrl& put,
const QUrl& get,
const QMap<QString, QString> headers
) {
QFile* file = new QFile(path);
Transfer* upl = new Transfer({{info}, 0, 0, true, path, get.toString(), file});
QNetworkRequest req(put);
@ -479,22 +463,18 @@ void Core::NetworkAccess::uploadFile(const Shared::MessageInfo& info, const QStr
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id)
{
void Core::NetworkAccess::registerFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
storage.addFile(url, account, jid, id);
std::map<QString, Transfer*>::iterator itr = downloads.find(url);
if (itr != downloads.end()) {
if (itr != downloads.end())
itr->second->messages.emplace_back(account, jid, id); //TODO notification is going to happen the next tick, is that okay?
}
}
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id)
{
void Core::NetworkAccess::registerFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
storage.addFile(url, path, account, jid, id);
}
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path)
{
bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QString& jid, const QString id, const QString path) {
for (const std::pair<const QString, Transfer*>& pair : uploads) {
Transfer* info = pair.second;
if (pair.second->path == path) {
@ -516,8 +496,7 @@ bool Core::NetworkAccess::checkAndAddToUploading(const QString& acc, const QStri
return false;
}
QString Core::NetworkAccess::prepareDirectory(const QString& jid)
{
QString Core::NetworkAccess::prepareDirectory(const QString& jid) {
QString path = currentPath;
QString addition;
if (jid.size() > 0) {
@ -529,25 +508,23 @@ QString Core::NetworkAccess::prepareDirectory(const QString& jid)
if (!location.exists()) {
bool res = location.mkpath(path);
if (!res) {
if (!res)
return "";
} else {
else
return "squawk://" + addition;
}
}
return "squawk://" + addition;
}
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path)
{
QString Core::NetworkAccess::checkFileName(const QString& name, const QString& path) {
QStringList parts = name.split(".");
QString suffix("");
QStringList::const_iterator sItr = parts.begin();
QString realName = *sItr;
++sItr;
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr) {
for (QStringList::const_iterator sEnd = parts.end(); sItr != sEnd; ++sItr)
suffix += "." + (*sItr);
}
QString postfix("");
QString resolvedPath = Shared::resolvePath(path);
QString count("");
@ -562,18 +539,15 @@ QString Core::NetworkAccess::checkFileName(const QString& name, const QString& p
return path + QDir::separator() + realName + count + suffix;
}
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id)
{
QString Core::NetworkAccess::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id) {
return storage.addMessageAndCheckForPath(url, account, jid, id);
}
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path)
{
std::list<Shared::MessageInfo> Core::NetworkAccess::reportPathInvalid(const QString& path) {
return storage.deletedFile(path);
}
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
{
void Core::NetworkAccess::moveFilesDirectory(const QString& newPath) {
QDir dir(currentPath);
bool success = true;
qDebug() << "moving" << currentPath << "to" << newPath;
@ -582,8 +556,8 @@ void Core::NetworkAccess::moveFilesDirectory(const QString& newPath)
success = dir.rename(fileName, newPath + QDir::separator() + fileName) && success;
}
if (!success) {
if (!success)
qDebug() << "couldn't move downloads directory, most probably downloads will be broken";
}
currentPath = newPath;
}

View file

@ -30,15 +30,11 @@
#include <set>
#include <core/storage/urlstorage.h>
#include <shared/pathcheck.h>
#include "urlstorage.h"
namespace Core {
/**
* @todo write docs
*/
//TODO Need to describe how to get rid of records when file is no longer reachable;
class NetworkAccess : public QObject
{

View file

@ -0,0 +1,289 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include "urlstorage.h"
Core::UrlStorage::UrlStorage(const QString& p_name):
base(p_name),
urlToInfo(base.addStorage<QString, UrlInfo>("urlToInfo")),
pathToUrl(base.addStorage<QString, QString>("pathToUrl"))
{}
Core::UrlStorage::~UrlStorage() {
close();
}
void Core::UrlStorage::open() {
base.open();
}
void Core::UrlStorage::close() {
base.close();
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, bool overwrite) {
LMDBAL::TransactionID txn = base.beginTransaction();
try {
writeInfo(key, info, txn, overwrite);
base.commitTransaction(txn);
} catch (...) {
base.abortTransaction(txn);
throw;
}
}
void Core::UrlStorage::writeInfo(const QString& key, const Core::UrlStorage::UrlInfo& info, MDB_txn* txn, bool overwrite) {
if (overwrite)
urlToInfo->forceRecord(key, info, txn);
else
urlToInfo->addRecord(key, info, txn);
if (info.hasPath())
pathToUrl->forceRecord(info.getPath(), key, txn);
}
void Core::UrlStorage::addFile(const QString& url) {
addToInfo(url, "", "", "");
}
void Core::UrlStorage::addFile(const QString& url, const QString& path) {
addToInfo(url, "", "", "", path);
}
void Core::UrlStorage::addFile(const QString& url, const QString& account, const QString& jid, const QString& id) {
addToInfo(url, account, jid, id);
}
void Core::UrlStorage::addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id) {
addToInfo(url, account, jid, id, path);
}
void Core::UrlStorage::addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path) {
UrlInfo info (path, msgs);
writeInfo(url, info, true);
}
QString Core::UrlStorage::addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id){
return addToInfo(url, account, jid, id).getPath();
}
Core::UrlStorage::UrlInfo Core::UrlStorage::addToInfo(
const QString& url,
const QString& account,
const QString& jid,
const QString& id,
const QString& path
) {
UrlInfo info;
LMDBAL::TransactionID txn = base.beginTransaction();
try {
urlToInfo->getRecord(url, info, txn);
} catch (const LMDBAL::NotFound& e) {
} catch (...) {
base.abortTransaction(txn);
throw;
}
bool pathChange = false;
bool listChange = false;
if (path != "-s") {
if (info.getPath() != path) {
info.setPath(path);
pathChange = true;
}
}
if (account.size() > 0 && jid.size() > 0 && id.size() > 0)
listChange = info.addMessage(account, jid, id);
if (pathChange || listChange) {
try {
writeInfo(url, info, txn, true);
base.commitTransaction(txn);
} catch (...) {
base.abortTransaction(txn);
throw;
}
} else {
base.abortTransaction(txn);
}
return info;
}
std::list<Shared::MessageInfo> Core::UrlStorage::setPath(const QString& url, const QString& path) {
std::list<Shared::MessageInfo> list;
LMDBAL::TransactionID txn = base.beginTransaction();
UrlInfo info;
try {
urlToInfo->getRecord(url, info, txn);
info.getMessages(list);
} catch (const LMDBAL::NotFound& e) {
} catch (...) {
base.abortTransaction(txn);
throw;
}
info.setPath(path);
try {
writeInfo(url, info, txn, true);
base.commitTransaction(txn);
} catch (...) {
base.abortTransaction(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::removeFile(const QString& url) {
std::list<Shared::MessageInfo> list;
LMDBAL::TransactionID txn = base.beginTransaction();
UrlInfo info;
try {
urlToInfo->getRecord(url, info, txn);
urlToInfo->removeRecord(url);
info.getMessages(list);
if (info.hasPath())
pathToUrl->removeRecord(info.getPath());
base.commitTransaction(txn);
} catch (...) {
base.abortTransaction(txn);
throw;
}
return list;
}
std::list<Shared::MessageInfo> Core::UrlStorage::deletedFile(const QString& path) {
std::list<Shared::MessageInfo> list;
LMDBAL::TransactionID txn = base.beginTransaction();
try {
QString url = pathToUrl->getRecord(path, txn);
pathToUrl->removeRecord(path);
UrlInfo info = urlToInfo->getRecord(url, txn);
info.getMessages(list);
info.setPath(QString());
urlToInfo->changeRecord(url, info, txn);
base.commitTransaction(txn);
} catch (...) {
base.abortTransaction(txn);
throw;
}
return list;
}
QString Core::UrlStorage::getUrl(const QString& path) {
return pathToUrl->getRecord(path);
}
std::pair<QString, std::list<Shared::MessageInfo>> Core::UrlStorage::getPath(const QString& url) {
UrlInfo info = urlToInfo->getRecord(url);
std::list<Shared::MessageInfo> container;
info.getMessages(container);
return std::make_pair(info.getPath(), container);
}
Core::UrlStorage::UrlInfo::UrlInfo():
localPath(),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path):
localPath(path),
messages() {}
Core::UrlStorage::UrlInfo::UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs):
localPath(path),
messages(msgs) {}
Core::UrlStorage::UrlInfo::~UrlInfo() {}
bool Core::UrlStorage::UrlInfo::addMessage(const QString& acc, const QString& jid, const QString& id) {
for (const Shared::MessageInfo& info : messages) {
if (info.account == acc && info.jid == jid && info.messageId == id) {
return false;
}
}
messages.emplace_back(acc, jid, id);
return true;
}
void Core::UrlStorage::UrlInfo::serialize(QDataStream& data) const {
data << localPath;
std::list<Shared::MessageInfo>::size_type size = messages.size();
data << quint32(size);
for (const Shared::MessageInfo& info : messages) {
data << info.account;
data << info.jid;
data << info.messageId;
}
}
QDataStream & operator << (QDataStream& in, const Core::UrlStorage::UrlInfo& info) {
info.serialize(in);
return in;
}
QDataStream & operator >> (QDataStream& out, Core::UrlStorage::UrlInfo& info) {
info.deserialize(out);
return out;
}
void Core::UrlStorage::UrlInfo::deserialize(QDataStream& data) {
data >> localPath;
quint32 size;
data >> size;
for (quint32 i = 0; i < size; ++i) {
messages.emplace_back();
Shared::MessageInfo& info = messages.back();
data >> info.account;
data >> info.jid;
data >> info.messageId;
}
}
void Core::UrlStorage::UrlInfo::getMessages(std::list<Shared::MessageInfo>& container) const {
for (const Shared::MessageInfo& info : messages)
container.emplace_back(info);
}
QString Core::UrlStorage::UrlInfo::getPath() const {
return localPath;
}
bool Core::UrlStorage::UrlInfo::hasPath() const {
return localPath.size() > 0;
}
void Core::UrlStorage::UrlInfo::setPath(const QString& path) {
localPath = path;
}

View file

@ -0,0 +1,97 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_URLSTORAGE_H
#define CORE_URLSTORAGE_H
#include <QString>
#include <QDataStream>
#include <list>
#include <storage.h>
#include <shared/messageinfo.h>
namespace Core {
class UrlStorage {
public:
class UrlInfo;
public:
UrlStorage(const QString& name);
~UrlStorage();
void open();
void close();
void addFile(const QString& url);
void addFile(const QString& url, const QString& path);
void addFile(const QString& url, const QString& account, const QString& jid, const QString& id);
void addFile(const QString& url, const QString& path, const QString& account, const QString& jid, const QString& id);
void addFile(const std::list<Shared::MessageInfo>& msgs, const QString& url, const QString& path); //this one overwrites all that was
std::list<Shared::MessageInfo> removeFile(const QString& url); //removes entry like it never was in the database, returns affected message infos
std::list<Shared::MessageInfo> deletedFile(const QString& path); //empties the localPath of the entry, returns affected message infos
std::list<Shared::MessageInfo> setPath(const QString& url, const QString& path);
QString getUrl(const QString& path);
QString addMessageAndCheckForPath(const QString& url, const QString& account, const QString& jid, const QString& id);
std::pair<QString, std::list<Shared::MessageInfo>> getPath(const QString& url);
private:
LMDBAL::Base base;
LMDBAL::Storage<QString, UrlInfo>* urlToInfo;
LMDBAL::Storage<QString, QString>* pathToUrl;
private:
void writeInfo(const QString& key, const UrlInfo& info, bool overwrite = false);
void writeInfo(const QString& key, const UrlInfo& info, MDB_txn* txn, bool overwrite = false);
UrlInfo addToInfo(const QString& url, const QString& account, const QString& jid, const QString& id, const QString& path = "-s");
public:
class UrlInfo {
public:
UrlInfo(const QString& path);
UrlInfo(const QString& path, const std::list<Shared::MessageInfo>& msgs);
UrlInfo();
~UrlInfo();
void serialize(QDataStream& data) const;
void deserialize(QDataStream& data);
QString getPath() const;
bool hasPath() const;
void setPath(const QString& path);
bool addMessage(const QString& acc, const QString& jid, const QString& id);
void getMessages(std::list<Shared::MessageInfo>& container) const;
private:
QString localPath;
std::list<Shared::MessageInfo> messages;
};
};
}
QDataStream& operator >> (QDataStream &in, Core::UrlStorage::UrlInfo& info);
QDataStream& operator << (QDataStream &out, const Core::UrlStorage::UrlInfo& info);
#endif // CORE_URLSTORAGE_H