Merge pull request #2846 from csoler/v0.6-Collection

Attempt at making RsCollection faster to use (Needs #144)
This commit is contained in:
csoler 2024-04-01 18:32:39 +02:00 committed by GitHub
commit d92404d047
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1630 additions and 1644 deletions

@ -1 +1 @@
Subproject commit 353596b0ee5ea76611eb663b90bf3ab1c9f34ad7 Subproject commit 9dd9d7f94a600e8c8478887a4f7784fdc3294034

@ -1 +1 @@
Subproject commit 2ddc86fb575a61170f4c06a00152e3e7dc74c8f4 Subproject commit 659423769541169457c41f71c8a038e2d64ba079

@ -1 +1 @@
Subproject commit 526cdfb9c1e9f91fa7ef17dcaf447a353eb0caed Subproject commit 0c16c4a07eea82a5946bf49ebccb41d7c1e82368

View File

@ -30,7 +30,7 @@
#include "gui/RetroShareLink.h" #include "gui/RetroShareLink.h"
#include "retroshare-gui/RsAutoUpdatePage.h" #include "retroshare-gui/RsAutoUpdatePage.h"
#include "gui/msgs/MessageComposer.h" #include "gui/msgs/MessageComposer.h"
#include "gui/common/RsCollection.h" #include "gui/common/RsCollectionDialog.h"
#include "gui/common/FilesDefs.h" #include "gui/common/FilesDefs.h"
#include "gui/common/RsUrlHandler.h" #include "gui/common/RsUrlHandler.h"
#include "gui/settings/rsharesettings.h" #include "gui/settings/rsharesettings.h"
@ -497,25 +497,23 @@ void SearchDialog::collCreate()
int selectedCount = selectedItems.size() ; int selectedCount = selectedItems.size() ;
QTreeWidgetItem * item ; QTreeWidgetItem * item ;
for (int i = 0; i < selectedCount; ++i) { RsFileTree tree;
for (int i = 0; i < selectedCount; ++i)
{
item = selectedItems.at(i) ; item = selectedItems.at(i) ;
if (!item->text(SR_HASH_COL).isEmpty()) { if (!item->text(SR_HASH_COL).isEmpty())
{
std::string name = item->text(SR_NAME_COL).toUtf8().constData(); std::string name = item->text(SR_NAME_COL).toUtf8().constData();
RsFileHash hash( item->text(SR_HASH_COL).toStdString() ); RsFileHash hash( item->text(SR_HASH_COL).toStdString() );
uint64_t count = item->text(SR_SIZE_COL).toULongLong(); uint64_t count = item->text(SR_SIZE_COL).toULongLong();
DirDetails details; tree.addFile(tree.root(),name,hash,count);
details.name = name;
details.hash = hash;
details.size = count;
details.type = DIR_TYPE_FILE;
dirVec.push_back(details);
} }
} }
RsCollection(dirVec,RS_FILE_HINTS_LOCAL).openNewColl(this); RsCollectionDialog::openNewCollection(tree);
} }
void SearchDialog::collModif() void SearchDialog::collModif()
@ -542,12 +540,8 @@ void SearchDialog::collModif()
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath());
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath());
}//if (qinfo.absoluteFilePath().endsWith(RsCollectionFile::ExtensionString))
}//if (qinfo.exists())
} }
void SearchDialog::collView() void SearchDialog::collView()
@ -574,12 +568,8 @@ void SearchDialog::collView()
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath(), true);
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath(), true);
}//if (qinfo.absoluteFilePath().endsWith(RsCollectionFile::ExtensionString))
}//if (qinfo.exists())
} }
void SearchDialog::collOpen() void SearchDialog::collOpen()
@ -597,32 +587,35 @@ void SearchDialog::collOpen()
if (rsFiles->FileDetails(hash, RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_LOCAL if (rsFiles->FileDetails(hash, RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_LOCAL
| RS_FILE_HINTS_BROWSABLE | RS_FILE_HINTS_NETWORK_WIDE | RS_FILE_HINTS_BROWSABLE | RS_FILE_HINTS_NETWORK_WIDE
| RS_FILE_HINTS_SPEC_ONLY, info)) { | RS_FILE_HINTS_SPEC_ONLY, info))
{
/* make path for downloaded files */ /* make path for downloaded files */
std::string path; std::string path;
path = info.path; path = info.path;
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
RsCollection::RsCollectionErrorCode err;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::downloadFiles(RsCollection(qinfo.absoluteFilePath(),err));
RsCollection collection;
if (collection.load(qinfo.absoluteFilePath())) {
collection.downloadFiles();
return;
}
}
}
} }
} }
} }
RsCollection collection; QString fileName;
if (collection.load(this)) { if (!misc::getOpenFileName(nullptr, RshareSettings::LASTDIR_EXTRAFILE, QApplication::translate("RsCollectionFile", "Open collection file"), QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")", fileName))
collection.downloadFiles(); return ;
}//if (collection.load(this))
std::cerr << "Got file name: " << fileName.toStdString() << std::endl;
RsCollection::RsCollectionErrorCode err;
RsCollection collection(fileName, err);
if(err == RsCollection::RsCollectionErrorCode::NO_ERROR)
RsCollectionDialog::downloadFiles(collection);
else
QMessageBox::information(nullptr,tr("Error open RsCollection file"),RsCollection::errorString(err));
} }
void SearchDialog::downloadDirectory(const QTreeWidgetItem *item, const QString &base) void SearchDialog::downloadDirectory(const QTreeWidgetItem *item, const QString &base)

View File

@ -27,7 +27,7 @@
#include "gui/RetroShareLink.h" #include "gui/RetroShareLink.h"
#include "gui/ShareManager.h" #include "gui/ShareManager.h"
#include "gui/common/PeerDefs.h" #include "gui/common/PeerDefs.h"
#include "gui/common/RsCollection.h" #include "gui/common/RsCollectionDialog.h"
#include "gui/msgs/MessageComposer.h" #include "gui/msgs/MessageComposer.h"
#include "gui/gxschannels/GxsChannelDialog.h" #include "gui/gxschannels/GxsChannelDialog.h"
#include "gui/gxsforums/GxsForumsDialog.h" #include "gui/gxsforums/GxsForumsDialog.h"
@ -652,7 +652,7 @@ void SharedFilesDialog::copyLinks(const QModelIndexList& lst, bool remote,QList<
QString dir_name = QDir(QString::fromUtf8(details.name.c_str())).dirName(); QString dir_name = QDir(QString::fromUtf8(details.name.c_str())).dirName();
RetroShareLink link = RetroShareLink::createFileTree(dir_name,ft->mTotalSize,ft->mTotalFiles,QString::fromStdString(ft->toRadix64())) ; RetroShareLink link = RetroShareLink::createFileTree(dir_name,ft->totalFileSize(),ft->numFiles(),QString::fromStdString(ft->toRadix64())) ;
if(link.valid()) if(link.valid())
urls.push_back(link) ; urls.push_back(link) ;
@ -734,7 +734,32 @@ void SharedFilesDialog::sendLinkTo()
void SharedFilesDialog::collCreate() void SharedFilesDialog::collCreate()
{ {
QModelIndexList lst = getSelected(); QModelIndexList lst = getSelected();
model->createCollectionFile(this, lst);
std::vector <DirDetails> dirVec;
model->getDirDetailsFromSelect(lst, dirVec);
auto RemoteMode = isRemote();
FileSearchFlags f = RemoteMode?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL ;
QString dir_name;
if(!RemoteMode)
{
if(!dirVec.empty())
{
const DirDetails& details = dirVec[0];
dir_name = QDir(QString::fromUtf8(details.name.c_str())).dirName();
}
}
RsFileTree tree;
for(uint32_t i=0;i<dirVec.size();++i)
tree.addFileTree(tree.root(),*RsFileTree::fromDirDetails(dirVec[i],RemoteMode,true));
RsCollectionDialog::openNewCollection(tree);
//auto ft = RsFileTree::fromDirDetails(details,remote);
} }
void SharedFilesDialog::collModif() void SharedFilesDialog::collModif()
@ -759,12 +784,8 @@ void SharedFilesDialog::collModif()
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::editExistingCollection(qinfo.absoluteFilePath());
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath());
}
}
} }
void SharedFilesDialog::collView() void SharedFilesDialog::collView()
@ -789,12 +810,8 @@ void SharedFilesDialog::collView()
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath(), true);
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath(), true);
}
}
} }
void SharedFilesDialog::collOpen() void SharedFilesDialog::collOpen()
@ -821,20 +838,24 @@ void SharedFilesDialog::collOpen()
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists()) {
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) {
RsCollection collection;
if (collection.load(qinfo.absoluteFilePath())) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath(),true);
collection.downloadFiles();
return;
}
} }
} }
} }
} }
RsCollection collection; QString fileName;
if (collection.load(this)) { if (!misc::getOpenFileName(nullptr, RshareSettings::LASTDIR_EXTRAFILE, QApplication::translate("RsCollectionFile", "Open collection file"), QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")", fileName))
collection.downloadFiles(); return ;
}
std::cerr << "Got file name: " << fileName.toStdString() << std::endl;
RsCollection::RsCollectionErrorCode err;
RsCollection collection(fileName,err);
if(err == RsCollection::RsCollectionErrorCode::NO_ERROR)
RsCollectionDialog::downloadFiles(collection);
} }
void LocalSharedFilesDialog::playselectedfiles() void LocalSharedFilesDialog::playselectedfiles()
@ -1145,12 +1166,14 @@ void LocalSharedFilesDialog::spawnCustomPopupMenu( QPoint point )
collViewAct->setEnabled(bIsRsColl); collViewAct->setEnabled(bIsRsColl);
collOpenAct->setEnabled(true); collOpenAct->setEnabled(true);
QMenu collectionMenu(tr("Collection"), this); QMenu collectionMenu(tr("Retroshare Collection"), this);
collectionMenu.setIcon(QIcon(IMAGE_LIBRARY)); collectionMenu.setIcon(QIcon(IMAGE_LIBRARY));
collectionMenu.addAction(collCreateAct); collectionMenu.addAction(collCreateAct);
collectionMenu.addAction(collModifAct);
collectionMenu.addAction(collViewAct); if(bIsRsColl)
collectionMenu.addAction(collOpenAct); collectionMenu.addAction(collModifAct);
//collectionMenu.addAction(collViewAct);
//collectionMenu.addAction(collOpenAct);
switch (type) { switch (type) {
case DIR_TYPE_DIR : case DIR_TYPE_DIR :

View File

@ -24,7 +24,7 @@
#include "gui/SoundManager.h" #include "gui/SoundManager.h"
#include "gui/RetroShareLink.h" #include "gui/RetroShareLink.h"
#include "gui/common/FilesDefs.h" #include "gui/common/FilesDefs.h"
#include "gui/common/RsCollection.h" #include "gui/common/RsCollectionDialog.h"
#include "gui/common/RSTreeView.h" #include "gui/common/RSTreeView.h"
#include "gui/common/RsUrlHandler.h" #include "gui/common/RsUrlHandler.h"
#include "gui/FileTransfer/DetailsDialog.h" #include "gui/FileTransfer/DetailsDialog.h"
@ -1975,7 +1975,7 @@ void TransfersDialog::pasteLink()
for(auto &it : links) for(auto &it : links)
col.merge_in(it.name(),it.size(),RsFileHash(it.hash().toStdString())) ; col.merge_in(it.name(),it.size(),RsFileHash(it.hash().toStdString())) ;
col.downloadFiles(); RsCollectionDialog::downloadFiles(col);
} }
void TransfersDialog::getDLSelectedItems(std::set<RsFileHash> *ids, std::set<int> *rows) void TransfersDialog::getDLSelectedItems(std::set<RsFileHash> *ids, std::set<int> *rows)
@ -2466,21 +2466,17 @@ void TransfersDialog::collCreate()
std::set<RsFileHash>::iterator it ; std::set<RsFileHash>::iterator it ;
getDLSelectedItems(&items, NULL); getDLSelectedItems(&items, NULL);
RsFileTree tree;
for (it = items.begin(); it != items.end(); ++it) for (it = items.begin(); it != items.end(); ++it)
{ {
FileInfo info; FileInfo info;
if (!rsFiles->FileDetails(*it, RS_FILE_HINTS_DOWNLOAD, info)) continue; if (!rsFiles->FileDetails(*it, RS_FILE_HINTS_DOWNLOAD, info)) continue;
DirDetails details; tree.addFile(tree.root(),info.fname,info.hash,info.size);
details.name = info.fname;
details.hash = info.hash;
details.size = info.size;
details.type = DIR_TYPE_FILE;
dirVec.push_back(details);
} }
RsCollection(dirVec,RS_FILE_HINTS_LOCAL).openNewColl(this); RsCollectionDialog::openNewCollection(tree);
} }
void TransfersDialog::collModif() void TransfersDialog::collModif()
@ -2504,12 +2500,8 @@ void TransfersDialog::collModif()
/* open collection */ /* open collection */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath());
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath());
}
}
} }
} }
@ -2534,12 +2526,8 @@ void TransfersDialog::collView()
/* open collection */ /* open collection */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath(), true);
RsCollection collection;
collection.openColl(qinfo.absoluteFilePath(), true);
}
}
} }
} }
@ -2564,23 +2552,29 @@ void TransfersDialog::collOpen()
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) { {
RsCollection collection; RsCollection::RsCollectionErrorCode code;
if (collection.load(qinfo.absoluteFilePath())) { RsCollectionDialog::downloadFiles(RsCollection(qinfo.absoluteFilePath(),code));
collection.downloadFiles(); return;
return; }
}
}
}
} }
} }
} }
RsCollection collection; QString fileName;
if (collection.load(this)) { if (!misc::getOpenFileName(nullptr, RshareSettings::LASTDIR_EXTRAFILE, QApplication::translate("RsCollectionFile", "Open collection file"), QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")", fileName))
collection.downloadFiles(); return ;
}
std::cerr << "Got file name: " << fileName.toStdString() << std::endl;
RsCollection::RsCollectionErrorCode code;
RsCollection collection(fileName,code);
if(code == RsCollection::RsCollectionErrorCode::NO_ERROR)
RsCollectionDialog::downloadFiles(collection);
else
QMessageBox::information(nullptr,tr("Error openning collection file"),RsCollection::errorString(code));
} }
void TransfersDialog::collAutoOpen(const QString &fileHash) void TransfersDialog::collAutoOpen(const QString &fileHash)
@ -2592,21 +2586,18 @@ void TransfersDialog::collAutoOpen(const QString &fileHash)
if (rsFiles->FileDetails(hash, RS_FILE_HINTS_DOWNLOAD, info)) { if (rsFiles->FileDetails(hash, RS_FILE_HINTS_DOWNLOAD, info)) {
/* make path for downloaded files */ /* make path for downloaded files */
if (info.downloadStatus == FT_STATE_COMPLETE) { if (info.downloadStatus == FT_STATE_COMPLETE)
{
std::string path; std::string path;
path = info.path + "/" + info.fname; path = info.path + "/" + info.fname;
/* open file with a suitable application */ /* open file with a suitable application */
QFileInfo qinfo; QFileInfo qinfo;
qinfo.setFile(QString::fromUtf8(path.c_str())); qinfo.setFile(QString::fromUtf8(path.c_str()));
if (qinfo.exists()) { RsCollection::RsCollectionErrorCode err;
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) {
RsCollection collection; if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
if (collection.load(qinfo.absoluteFilePath(), false)) { RsCollectionDialog::downloadFiles(RsCollection(qinfo.absoluteFilePath(),err));
collection.autoDownloadFiles();
}
}
}
} }
} }
} }

View File

@ -120,7 +120,7 @@
#include "gui/statistics/StatisticsWindow.h" #include "gui/statistics/StatisticsWindow.h"
#include "gui/connect/ConnectFriendWizard.h" #include "gui/connect/ConnectFriendWizard.h"
#include "gui/common/RsCollection.h" #include "gui/common/RsCollectionDialog.h"
#include "settings/rsettingswin.h" #include "settings/rsettingswin.h"
#include "settings/rsharesettings.h" #include "settings/rsharesettings.h"
#include "common/StatusDefs.h" #include "common/StatusDefs.h"
@ -1705,19 +1705,9 @@ void MainWindow::retroshareLinkActivated(const QUrl &url)
void MainWindow::openRsCollection(const QString &filename) void MainWindow::openRsCollection(const QString &filename)
{ {
QFileInfo qinfo(filename); QFileInfo qinfo(filename);
if (qinfo.exists()) {
if (qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString)) {
RsCollection collection;
if(!collection.load(filename)) if (qinfo.exists() && qinfo.absoluteFilePath().endsWith(RsCollection::ExtensionString))
{ RsCollectionDialog::openExistingCollection(qinfo.absoluteFilePath());
RsErr() << "Could not open Rscollection file " << filename.toStdString();
return;
}
collection.downloadFiles();
//collection.openColl(qinfo.absoluteFilePath());
}
}
} }
void MainWindow::switchVisibilityStatus(StatusElement e,bool b) void MainWindow::switchVisibilityStatus(StatusElement e,bool b)

View File

@ -24,7 +24,7 @@
#include "gui/common/FilesDefs.h" #include "gui/common/FilesDefs.h"
#include "gui/common/GroupDefs.h" #include "gui/common/GroupDefs.h"
#include "gui/common/RsCollection.h" #include "gui/common/RsCollectionDialog.h"
#include "gui/common/RsUrlHandler.h" #include "gui/common/RsUrlHandler.h"
#include "gui/gxs/GxsIdDetails.h" #include "gui/gxs/GxsIdDetails.h"
#include "retroshare/rsfiles.h" #include "retroshare/rsfiles.h"
@ -1233,31 +1233,6 @@ bool RetroshareDirModel::requestDirDetails(void *ref, bool remote,DirDetails& d)
return false ; return false ;
} }
void RetroshareDirModel::createCollectionFile(QWidget *parent, const QModelIndexList &list)
{
/* if(RemoteMode)
{
std::cerr << "Cannot create a collection file from remote" << std::endl;
return ;
}*/
std::vector <DirDetails> dirVec;
getDirDetailsFromSelect(list, dirVec);
FileSearchFlags f = RemoteMode?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL ;
QString dir_name;
if(!RemoteMode)
{
if(!dirVec.empty())
{
const DirDetails& details = dirVec[0];
dir_name = QDir(QString::fromUtf8(details.name.c_str())).dirName();
}
}
RsCollection(dirVec,f).openNewColl(parent,dir_name);
}
void RetroshareDirModel::downloadSelected(const QModelIndexList &list,bool interactive) void RetroshareDirModel::downloadSelected(const QModelIndexList &list,bool interactive)
{ {
if (!RemoteMode) if (!RemoteMode)
@ -1278,7 +1253,7 @@ void RetroshareDirModel::downloadSelected(const QModelIndexList &list,bool inter
FileSearchFlags f = RemoteMode?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL ; FileSearchFlags f = RemoteMode?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL ;
if(interactive) if(interactive)
RsCollection(dirVec,f).downloadFiles() ; RsCollectionDialog::downloadFiles(RsCollection(dirVec,f)) ;
else /* Fire off requests */ else /* Fire off requests */
for (int i = 0, n = dirVec.size(); i < n; ++i) for (int i = 0, n = dirVec.size(); i < n; ++i)
{ {

View File

@ -68,8 +68,6 @@ class RetroshareDirModel : public QAbstractItemModel
/* Callback from GUI */ /* Callback from GUI */
void downloadSelected(const QModelIndexList &list, bool interactive); void downloadSelected(const QModelIndexList &list, bool interactive);
void createCollectionFile(QWidget *parent, const QModelIndexList &list);
void getDirDetailsFromSelect (const QModelIndexList &list, std::vector <DirDetails>& dirVec); void getDirDetailsFromSelect (const QModelIndexList &list, std::vector <DirDetails>& dirVec);
int getType ( const QModelIndex & index ) const ; int getType ( const QModelIndex & index ) const ;

View File

@ -25,7 +25,7 @@
#include "HomePage.h" #include "HomePage.h"
#include "chat/ChatDialog.h" #include "chat/ChatDialog.h"
#include "common/PeerDefs.h" #include "common/PeerDefs.h"
#include "common/RsCollection.h" #include "common/RsCollectionDialog.h"
#include "common/RsUrlHandler.h" #include "common/RsUrlHandler.h"
#include "connect/ConfCertDialog.h" #include "connect/ConfCertDialog.h"
#include "connect/ConnectFriendWizard.h" #include "connect/ConnectFriendWizard.h"
@ -1143,11 +1143,13 @@ QString RetroShareLink::toHtmlSize() const
if (type() == TYPE_FILE && RsCollection::isCollectionFile(name())) { if (type() == TYPE_FILE && RsCollection::isCollectionFile(name())) {
FileInfo finfo; FileInfo finfo;
if (rsFiles->FileDetails(RsFileHash(hash().toStdString()), RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_LOCAL, finfo)) { if (rsFiles->FileDetails(RsFileHash(hash().toStdString()), RS_FILE_HINTS_EXTRA | RS_FILE_HINTS_LOCAL, finfo))
RsCollection collection; {
if (collection.load(QString::fromUtf8(finfo.path.c_str()), false)) { RsCollection::RsCollectionErrorCode code;
RsCollection collection(QString::fromUtf8(finfo.path.c_str()), code) ;
if(code == RsCollection::RsCollectionErrorCode::NO_ERROR)
size += QString(" [%1]").arg(misc::friendlyUnit(collection.size())); size += QString(" [%1]").arg(misc::friendlyUnit(collection.size()));
}
} }
} }
QString link = QString("<a href=\"%1\">%2</a> <font color=\"blue\">%3</font>").arg(toString()).arg(name()).arg(size); QString link = QString("<a href=\"%1\">%2</a> <font color=\"blue\">%3</font>").arg(toString()).arg(name()).arg(size);
@ -1722,10 +1724,9 @@ static void processList(const QStringList &list, const QString &textSingular, co
case TYPE_FILE_TREE: case TYPE_FILE_TREE:
{ {
auto ft = RsFileTree::fromRadix64( auto ft = RsFileTree::fromRadix64(link.radix().toStdString() );
link.radix().toStdString() ); RsCollectionDialog::downloadFiles(RsCollection(*ft));
RsCollection(*ft).downloadFiles(); break;
break;
} }
case TYPE_CHAT_ROOM: case TYPE_CHAT_ROOM:
@ -1776,7 +1777,7 @@ static void processList(const QStringList &list, const QString &textSingular, co
// were single file links found? // were single file links found?
if (fileLinkFound) if (fileLinkFound)
col.downloadFiles(); RsCollectionDialog::downloadFiles(col);
int countProcessed = 0; int countProcessed = 0;
int countError = 0; int countError = 0;

View File

@ -37,534 +37,411 @@
const QString RsCollection::ExtensionString = QString("rscollection") ; const QString RsCollection::ExtensionString = QString("rscollection") ;
RsCollection::RsCollection(QObject *parent) RsCollection::RsCollection()
: QObject(parent), _xml_doc("RsCollection")
{ {
_root = _xml_doc.createElement("RsCollection"); mFileTree = std::unique_ptr<RsFileTree>(new RsFileTree());
_xml_doc.appendChild(_root); }
RsCollection::RsCollection(const RsFileTree& ft)
{
mFileTree = std::unique_ptr<RsFileTree>(new RsFileTree(ft));
for(uint64_t i=0;i<mFileTree->numFiles();++i)
mHashes.insert(std::make_pair(mFileTree->fileData(i).hash,i ));
} }
RsCollection::RsCollection(const RsFileTree& fr) RsCollection::RsCollection(const std::vector<DirDetails>& file_infos,FileSearchFlags flags)
: _xml_doc("RsCollection") : mFileTree(new RsFileTree)
{ {
_root = _xml_doc.createElement("RsCollection"); if(! ( (flags & RS_FILE_HINTS_LOCAL) || (flags & RS_FILE_HINTS_REMOTE)))
_xml_doc.appendChild(_root); {
std::cerr << "(EE) Wrong flags passed to RsCollection constructor. Please fix the code!" << std::endl;
return ;
}
recursAddElements(_xml_doc,fr,0,_root) ; for(uint32_t i = 0;i<file_infos.size();++i)
} recursAddElements(mFileTree->root(),file_infos[i],flags) ;
RsCollection::RsCollection(const std::vector<DirDetails>& file_infos,FileSearchFlags flags, QObject *parent) for(uint64_t i=0;i<mFileTree->numFiles();++i)
: QObject(parent), _xml_doc("RsCollection") mHashes.insert(std::make_pair(mFileTree->fileData(i).hash,i ));
{
_root = _xml_doc.createElement("RsCollection");
_xml_doc.appendChild(_root);
if(! ( (flags & RS_FILE_HINTS_LOCAL) || (flags & RS_FILE_HINTS_REMOTE)))
{
std::cerr << "(EE) Wrong flags passed to RsCollection constructor. Please fix the code!" << std::endl;
return ;
}
for(uint32_t i = 0;i<file_infos.size();++i)
recursAddElements(_xml_doc,file_infos[i],_root,flags) ;
} }
RsCollection::~RsCollection() RsCollection::~RsCollection()
{ {
} }
void RsCollection::downloadFiles() const
{
// print out the element names of all elements that are direct children
// of the outermost element.
QDomElement docElem = _xml_doc.documentElement();
std::vector<ColFileInfo> colFileInfos ;
recursCollectColFileInfos(docElem,colFileInfos,QString(),false) ;
RsCollectionDialog(_fileName, colFileInfos, false).exec() ;
}
void RsCollection::autoDownloadFiles() const
{
QDomElement docElem = _xml_doc.documentElement();
std::vector<ColFileInfo> colFileInfos;
recursCollectColFileInfos(docElem,colFileInfos,QString(),false);
QString dlDir = QString::fromUtf8(rsFiles->getDownloadDirectory().c_str());
foreach(ColFileInfo colFileInfo, colFileInfos)
{
autoDownloadFiles(colFileInfo, dlDir);
}
}
void RsCollection::autoDownloadFiles(ColFileInfo colFileInfo, QString dlDir) const void RsCollection::autoDownloadFiles(ColFileInfo colFileInfo, QString dlDir) const
{ {
if (!colFileInfo.filename_has_wrong_characters) if (!colFileInfo.filename_has_wrong_characters)
{ {
QString cleanPath = dlDir + colFileInfo.path ; QString cleanPath = dlDir + colFileInfo.path ;
std::cout << "making directory " << cleanPath.toStdString() << std::endl; std::cout << "making directory " << cleanPath.toStdString() << std::endl;
if(!QDir(QApplication::applicationDirPath()).mkpath(cleanPath)) if(!QDir(QApplication::applicationDirPath()).mkpath(cleanPath))
std::cerr << "Unable to make path: " + cleanPath.toStdString() << std::endl; std::cerr << "Unable to make path: " + cleanPath.toStdString() << std::endl;
if (colFileInfo.type==DIR_TYPE_FILE) if (colFileInfo.type==DIR_TYPE_FILE)
rsFiles->FileRequest(colFileInfo.name.toUtf8().constData(), rsFiles->FileRequest(colFileInfo.name.toUtf8().constData(),
RsFileHash(colFileInfo.hash.toStdString()), RsFileHash(colFileInfo.hash.toStdString()),
colFileInfo.size, colFileInfo.size,
cleanPath.toUtf8().constData(), cleanPath.toUtf8().constData(),
RS_FILE_REQ_ANONYMOUS_ROUTING, RS_FILE_REQ_ANONYMOUS_ROUTING,
std::list<RsPeerId>()); std::list<RsPeerId>());
} }
foreach(ColFileInfo colFileInfoChild, colFileInfo.children) foreach(ColFileInfo colFileInfoChild, colFileInfo.children)
{ {
autoDownloadFiles(colFileInfoChild, dlDir); autoDownloadFiles(colFileInfoChild, dlDir);
} }
} }
static QString purifyFileName(const QString& input,bool& bad) static QString purifyFileName(const QString& input,bool& bad)
{ {
static const QString bad_chars = "/\\\"*:?<>|" ; static const QString bad_chars = "/\\\"*:?<>|" ;
bad = false ; bad = false ;
QString output = input ; QString output = input ;
for(int i=0;i<output.length();++i) for(int i=0;i<output.length();++i)
for(int j=0;j<bad_chars.length();++j) for(int j=0;j<bad_chars.length();++j)
if(output[i] == bad_chars[j]) if(output[i] == bad_chars[j])
{ {
output[i] = '_' ; output[i] = '_' ;
bad = true ; bad = true ;
} }
return output ; return output ;
} }
void RsCollection::merge_in(const QString& fname,uint64_t size,const RsFileHash& hash) void RsCollection::merge_in(const QString& fname,uint64_t size,const RsFileHash& hash,RsFileTree::DirIndex parent_index)
{ {
ColFileInfo info ; mHashes[hash]= mFileTree->addFile(parent_index,fname.toStdString(),hash,size);
info.type = DIR_TYPE_FILE ;
info.name = fname ;
info.size = size ;
info.hash = QString::fromStdString(hash.toStdString()) ;
recursAddElements(_xml_doc,info,_root) ;
} }
void RsCollection::merge_in(const RsFileTree& tree) void RsCollection::merge_in(const RsFileTree& tree, RsFileTree::DirIndex parent_index)
{ {
recursAddElements(_xml_doc,tree,0,_root) ; recursMergeTree(mFileTree->root(),tree,tree.directoryData(parent_index)) ;
} }
void RsCollection::recursCollectColFileInfos(const QDomElement& e,std::vector<ColFileInfo>& colFileInfos,const QString& current_path, bool bad_chars_in_parent) const void RsCollection::recursMergeTree(RsFileTree::DirIndex parent,const RsFileTree& tree,const RsFileTree::DirData& dd)
{ {
QDomNode n = e.firstChild() ; for(uint32_t i=0;i<dd.subfiles.size();++i)
#ifdef COLLECTION_DEBUG {
std::cerr << "Parsing element " << e.tagName().toStdString() << std::endl; const RsFileTree::FileData& fd(tree.fileData(dd.subfiles[i]));
#endif mHashes[fd.hash] = mFileTree->addFile(parent,fd.name,fd.hash,fd.size);
}
for(uint32_t i=0;i<dd.subdirs.size();++i)
{
const RsFileTree::DirData& ld(tree.directoryData(dd.subdirs[i]));
while(!n.isNull()) auto new_dir_index = mFileTree->addDirectory(parent,ld.name);
{ recursMergeTree(new_dir_index,tree,ld);
QDomElement ee = n.toElement(); // try to convert the node to an element. }
#ifdef COLLECTION_DEBUG
std::cerr << " Seeing child " << ee.tagName().toStdString() << std::endl;
#endif
if(ee.tagName() == QString("File"))
{
ColFileInfo newChild ;
newChild.hash = ee.attribute(QString("sha1")) ;
bool bad_chars_detected = false ;
newChild.name = purifyFileName(ee.attribute(QString("name")), bad_chars_detected) ;
newChild.filename_has_wrong_characters = bad_chars_detected || bad_chars_in_parent ;
newChild.size = ee.attribute(QString("size")).toULongLong() ;
newChild.path = current_path ;
newChild.type = DIR_TYPE_FILE ;
colFileInfos.push_back(newChild) ;
}
else if(ee.tagName() == QString("Directory"))
{
ColFileInfo newParent ;
bool bad_chars_detected = false ;
QString cleanDirName = purifyFileName(ee.attribute(QString("name")),bad_chars_detected) ;
newParent.name=cleanDirName;
newParent.filename_has_wrong_characters = bad_chars_detected || bad_chars_in_parent ;
newParent.size = 0;
newParent.path = current_path ;
newParent.type = DIR_TYPE_DIR ;
recursCollectColFileInfos(ee,newParent.children,current_path + "/" + cleanDirName, bad_chars_in_parent || bad_chars_detected) ;
uint32_t size = newParent.children.size();
for(uint32_t i=0;i<size;++i)
{
const ColFileInfo &colFileInfo = newParent.children[i];
newParent.size +=colFileInfo.size ;
}
colFileInfos.push_back(newParent) ;
}
n = n.nextSibling() ;
}
} }
void RsCollection::recursAddElements(RsFileTree::DirIndex parent, const DirDetails& dd, FileSearchFlags flags)
void RsCollection::recursAddElements(QDomDocument& doc,const DirDetails& details,QDomElement& e,FileSearchFlags flags) const
{ {
if (details.type == DIR_TYPE_FILE) if (dd.type == DIR_TYPE_FILE)
{ mHashes[dd.hash] = mFileTree->addFile(parent,dd.name,dd.hash,dd.size);
QDomElement f = doc.createElement("File") ; else if (dd.type == DIR_TYPE_DIR)
{
RsFileTree::DirIndex new_dir_index = mFileTree->addDirectory(parent,dd.name);
f.setAttribute(QString("name"),QString::fromUtf8(details.name.c_str())) ; for(uint32_t i=0;i<dd.children.size();++i)
f.setAttribute(QString("sha1"),QString::fromStdString(details.hash.toStdString())) ; {
f.setAttribute(QString("size"),QString::number(details.size)) ; if (!dd.children[i].ref)
continue;
e.appendChild(f) ; DirDetails subDirDetails;
}
else if (details.type == DIR_TYPE_DIR)
{
QDomElement d = doc.createElement("Directory") ;
d.setAttribute(QString("name"),QString::fromUtf8(details.name.c_str())) ; if (!rsFiles->RequestDirDetails(dd.children[i].ref, subDirDetails, flags))
continue;
for(uint32_t i=0;i<details.children.size();++i) recursAddElements(new_dir_index,subDirDetails,flags) ;
{ }
if (!details.children[i].ref) }
continue;
DirDetails subDirDetails;
if (!rsFiles->RequestDirDetails(details.children[i].ref, subDirDetails, flags))
continue;
recursAddElements(doc,subDirDetails,d,flags) ;
}
e.appendChild(d) ;
}
} }
void RsCollection::recursAddElements(QDomDocument& doc,const ColFileInfo& colFileInfo,QDomElement& e) const QString RsCollection::errorString(RsCollectionErrorCode code)
{ {
if (colFileInfo.type == DIR_TYPE_FILE) switch(code)
{ {
QDomElement f = doc.createElement("File") ; default: [[fallthrough]] ;
case RsCollectionErrorCode::UNKNOWN_ERROR: return QObject::tr("Unknown error");
f.setAttribute(QString("name"),colFileInfo.name) ; case RsCollectionErrorCode::NO_ERROR: return QObject::tr("No error");
f.setAttribute(QString("sha1"),colFileInfo.hash) ; case RsCollectionErrorCode::FILE_READ_ERROR: return QObject::tr("Error while openning file");
f.setAttribute(QString("size"),QString::number(colFileInfo.size)) ; case RsCollectionErrorCode::FILE_CONTAINS_HARMFUL_STRINGS: return QObject::tr("Collection file contains potentially harmful code");
case RsCollectionErrorCode::INVALID_ROOT_NODE: return QObject::tr("Invalid root node. RsCollection node was expected.");
e.appendChild(f) ; case RsCollectionErrorCode::XML_PARSING_ERROR: return QObject::tr("XML parsing error in collection file");
} }
else if (colFileInfo.type == DIR_TYPE_DIR)
{
QDomElement d = doc.createElement("Directory") ;
d.setAttribute(QString("name"),colFileInfo.name) ;
for (std::vector<ColFileInfo>::const_iterator it = colFileInfo.children.begin(); it != colFileInfo.children.end(); ++it)
recursAddElements(doc,(*it),d) ;
e.appendChild(d) ;
}
} }
void RsCollection::recursAddElements( RsCollection::RsCollection(const RsCollection& col)
QDomDocument& doc, const RsFileTree& ft, uint32_t index, : mFileTree(new RsFileTree(*col.mFileTree)),mHashes(col.mHashes)
QDomElement& e ) const
{ {
std::vector<uint64_t> subdirs;
std::vector<RsFileTree::FileData> subfiles ;
std::string name;
if(!ft.getDirectoryContent(name, subdirs, subfiles, index)) return;
QDomElement d = doc.createElement("Directory") ;
d.setAttribute(QString("name"),QString::fromUtf8(name.c_str())) ;
e.appendChild(d) ;
for (uint32_t i=0;i<subdirs.size();++i)
recursAddElements(doc,ft,subdirs[i],d) ;
for(uint32_t i=0;i<subfiles.size();++i)
{
QDomElement f = doc.createElement("File") ;
f.setAttribute(QString("name"),QString::fromUtf8(subfiles[i].name.c_str())) ;
f.setAttribute(QString("sha1"),QString::fromStdString(subfiles[i].hash.toStdString())) ;
f.setAttribute(QString("size"),QString::number(subfiles[i].size)) ;
d.appendChild(f) ;
}
} }
static void showErrorBox(const QString& fileName, const QString& error) RsCollection::RsCollection(const QString& fileName, RsCollectionErrorCode& error)
: mFileTree(new RsFileTree)
{ {
QMessageBox mb(QMessageBox::Warning, QObject::tr("Failed to process collection file"), QObject::tr("The collection file %1 could not be opened.\nReported error is: \n\n%2").arg(fileName).arg(error), QMessageBox::Ok); if (!checkFile(fileName,error))
mb.exec(); return ;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
{
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl;
error = RsCollectionErrorCode::FILE_READ_ERROR;
//showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName));
return ;
}
QDomDocument xml_doc;
bool ok = xml_doc.setContent(&file) ;
if(!ok)
{
error = RsCollectionErrorCode::XML_PARSING_ERROR;
return;
}
file.close();
QDomNode root = xml_doc.elementsByTagName("RsCollection").at(0).toElement();
if(root.isNull())
{
error = RsCollectionErrorCode::INVALID_ROOT_NODE;
return;
}
if(!recursParseXml(xml_doc,root,0))
error = RsCollectionErrorCode::XML_PARSING_ERROR;
else
error = RsCollectionErrorCode::NO_ERROR;
} }
bool RsCollection::load(const QString& fileName, bool showError /* = true*/) // check that the file is a valid rscollection file, and not a lol bomb or some shit like this
bool RsCollection::checkFile(const QString& fileName, RsCollectionErrorCode& error)
{ {
QFile file(fileName);
error = RsCollectionErrorCode::NO_ERROR;
if (!checkFile(fileName,showError)) return false; if (!file.open(QIODevice::ReadOnly))
QFile file(fileName); {
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl;
error = RsCollectionErrorCode::FILE_READ_ERROR;
if (!file.open(QIODevice::ReadOnly)) //showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName));
{ return false;
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl; }
if (showError) { if (file.reset()){
showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName)); std::cerr << "Checking this file for bomb elements and various wrong stuff" << std::endl;
} char c ;
return false;
}
bool ok = _xml_doc.setContent(&file) ; std::vector<std::string> bad_strings ;
file.close(); bad_strings.push_back(std::string("<!entity ")) ;
static const int max_size = 12 ; // should be as large as the largest element in bad_strings
char current[max_size] = { 0,0,0,0,0,0,0,0,0,0,0,0 } ;
int n=0 ;
if (ok) { while( !file.atEnd() || n >= 0)
_fileName = fileName; {
} else { if (!file.atEnd())
if (showError) { file.getChar(&c);
showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Error parsing xml file")); else
} c=0;
}
return ok; if(c == '\t' || c == '\n' || c == '\b' || c == '\r')
} continue ;
// check that the file is a valid rscollection file, and not a lol bomb or some shit like this if (n == max_size || file.atEnd())
bool RsCollection::checkFile(const QString& fileName, bool showError) for(int i=0;i<n-1;++i)
{ current[i] = current[i+1] ;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) if(n == max_size)
{ --n ;
std::cerr << "Cannot open file " << fileName.toStdString() << " !!" << std::endl;
if (showError) {
showErrorBox(fileName, QApplication::translate("RsCollectionFile", "Cannot open file %1").arg(fileName));
}
return false;
}
if (file.reset()){
std::cerr << "Checking this file for bomb elements and various wrong stuff" << std::endl;
char c ;
std::vector<std::string> bad_strings ; if(c >= 'A' && c <= 'Z') c += 'a' - 'A' ;
bad_strings.push_back(std::string("<!entity ")) ;
static const int max_size = 12 ; // should be as large as the largest element in bad_strings
char current[max_size] = { 0,0,0,0,0,0,0,0,0,0,0,0 } ;
int n=0 ;
while( !file.atEnd() || n >= 0) if(!file.atEnd())
{ current[n] = c ;
if (!file.atEnd()) else
file.getChar(&c); current[n] = 0 ;
else
c=0;
if(c == '\t' || c == '\n' || c == '\b' || c == '\r') //std::cerr << "n==" << n <<" Checking string " << std::string(current,n+1) << " c = " << std::hex << (int)c << std::dec << std::endl;
continue ;
if (n == max_size || file.atEnd()) for(uint i=0;i<bad_strings.size();++i)
for(int i=0;i<n-1;++i) if(std::string(current,bad_strings[i].length()) == bad_strings[i])
current[i] = current[i+1] ; {
//showErrorBox(file.fileName(), QApplication::translate("RsCollectionFile", "This file contains the string \"%1\" and is therefore an invalid collection file. \n\nIf you believe it is correct, remove the corresponding line from the file and re-open it with Retroshare.").arg(bad_strings[i].c_str()));
error = RsCollectionErrorCode::FILE_CONTAINS_HARMFUL_STRINGS;
file.close();
return false ;
//std::cerr << "Bad string detected" << std::endl;
}
if(n == max_size) if(file.atEnd())
--n ; n-- ;
else if(n < max_size)
if(c >= 'A' && c <= 'Z') c += 'a' - 'A' ; ++n ;
}
if(!file.atEnd()) file.close();
current[n] = c ; return true;
else }
current[n] = 0 ; error = RsCollectionErrorCode::UNKNOWN_ERROR;
return false;
//std::cerr << "n==" << n <<" Checking string " << std::string(current,n+1) << " c = " << std::hex << (int)c << std::dec << std::endl;
for(uint i=0;i<bad_strings.size();++i)
if(std::string(current,bad_strings[i].length()) == bad_strings[i])
{
showErrorBox(file.fileName(), QApplication::translate("RsCollectionFile", "This file contains the string \"%1\" and is therefore an invalid collection file. \n\nIf you believe it is correct, remove the corresponding line from the file and re-open it with Retroshare.").arg(bad_strings[i].c_str()));
file.close();
return false ;
//std::cerr << "Bad string detected" << std::endl;
}
if(file.atEnd())
n-- ;
else if(n < max_size)
++n ;
}
file.close();
return true;
}
return false;
}
bool RsCollection::load(QWidget *parent)
{
QString fileName;
if (!misc::getOpenFileName(parent, RshareSettings::LASTDIR_EXTRAFILE, QApplication::translate("RsCollectionFile", "Open collection file"), QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")", fileName))
return false;
std::cerr << "Got file name: " << fileName.toStdString() << std::endl;
return load(fileName, true);
} }
bool RsCollection::save(const QString& fileName) const bool RsCollection::save(const QString& fileName) const
{ {
QFile file(fileName); QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) if (!file.open(QIODevice::WriteOnly))
{ {
std::cerr << "Cannot write to file " << fileName.toStdString() << " !!" << std::endl; std::cerr << "Cannot write to file " << fileName.toStdString() << " !!" << std::endl;
return false; return false;
} }
QTextStream stream(&file) ; QDomDocument xml_doc ;
stream.setCodec("UTF-8") ; QDomElement root = xml_doc.createElement("RsCollection");
stream << _xml_doc.toString() ; const RsFileTree::DirData& root_data(mFileTree->directoryData(mFileTree->root()));
file.close(); if(!recursExportToXml(xml_doc,root,root_data))
return false;
return true; xml_doc.appendChild(root);
QTextStream stream(&file) ;
stream.setCodec("UTF-8") ;
stream << xml_doc.toString() ;
file.close();
return true;
} }
bool RsCollection::save(QWidget *parent) const bool RsCollection::recursParseXml(QDomDocument& doc,const QDomNode& e,const RsFileTree::DirIndex parent)
{ {
QString fileName; mHashes.clear();
if(!misc::getSaveFileName(parent, RshareSettings::LASTDIR_EXTRAFILE, QApplication::translate("RsCollectionFile", "Create collection file"), QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")", fileName)) QDomNode n = e.firstChild() ;
return false; #ifdef COLLECTION_DEBUG
std::cerr << "Parsing element " << e.tagName().toStdString() << std::endl;
#endif
if (!fileName.endsWith("." + RsCollection::ExtensionString)) while(!n.isNull())
fileName += "." + RsCollection::ExtensionString ; {
QDomElement ee = n.toElement(); // try to convert the node to an element.
std::cerr << "Got file name: " << fileName.toStdString() << std::endl; #ifdef COLLECTION_DEBUG
std::cerr << " Seeing child " << ee.tagName().toStdString() << std::endl;
#endif
return save(fileName); if(ee.tagName() == QString("File"))
{
RsFileHash hash(ee.attribute(QString("sha1")).toStdString()) ;
bool bad_chars_detected = false;
std::string name = purifyFileName(ee.attribute(QString("name")), bad_chars_detected).toUtf8().constData() ;
uint64_t size = ee.attribute(QString("size")).toULongLong() ;
mHashes[hash] = mFileTree->addFile(parent,name,hash,size);
}
else if(ee.tagName() == QString("Directory"))
{
bool bad_chars_detected = false ;
std::string cleanDirName = purifyFileName(ee.attribute(QString("name")),bad_chars_detected).toUtf8().constData() ;
RsFileTree::DirIndex new_dir_index = mFileTree->addDirectory(parent,cleanDirName);
recursParseXml(doc,ee,new_dir_index);
}
n = n.nextSibling() ;
}
return true;
} }
bool RsCollection::recursExportToXml(QDomDocument& doc,QDomElement& e,const RsFileTree::DirData& dd) const
bool RsCollection::openNewColl(QWidget *parent, QString fileName)
{ {
if(!misc::getSaveFileName(parent, RshareSettings::LASTDIR_EXTRAFILE for(uint32_t i=0;i<dd.subfiles.size();++i)
, QApplication::translate("RsCollectionFile", "Create collection file") {
, QApplication::translate("RsCollectionFile", "Collection files") + " (*." + RsCollection::ExtensionString + ")" QDomElement f = doc.createElement("File") ;
, fileName,0, QFileDialog::DontConfirmOverwrite))
return false;
if (!fileName.endsWith("." + RsCollection::ExtensionString)) const RsFileTree::FileData& fd(mFileTree->fileData(dd.subfiles[i]));
fileName += "." + RsCollection::ExtensionString ;
std::cerr << "Got file name: " << fileName.toStdString() << std::endl; f.setAttribute(QString("name"),QString::fromUtf8(fd.name.c_str())) ;
f.setAttribute(QString("sha1"),QString::fromStdString(fd.hash.toStdString())) ;
f.setAttribute(QString("size"),QString::number(fd.size)) ;
QFile file(fileName) ; e.appendChild(f) ;
}
if(file.exists()) for(uint32_t i=0;i<dd.subdirs.size();++i)
{ {
if (!checkFile(fileName,true)) return false; const RsFileTree::DirData& di(mFileTree->directoryData(dd.subdirs[i]));
QMessageBox mb; QDomElement d = doc.createElement("Directory") ;
mb.setText(tr("Save Collection File.")); d.setAttribute(QString("name"),QString::fromUtf8(di.name.c_str())) ;
mb.setInformativeText(tr("File already exists.")+"\n"+tr("What do you want to do?"));
QAbstractButton *btnOwerWrite = mb.addButton(tr("Overwrite"), QMessageBox::YesRole);
QAbstractButton *btnMerge = mb.addButton(tr("Merge"), QMessageBox::NoRole);
QAbstractButton *btnCancel = mb.addButton(tr("Cancel"), QMessageBox::ResetRole);
mb.setIcon(QMessageBox::Question);
mb.exec();
if (mb.clickedButton()==btnOwerWrite) { if(!recursExportToXml(doc,d,di))
//Nothing to do _xml_doc already up to date return false;
} else if (mb.clickedButton()==btnMerge) {
//Open old file to merge it with _xml_doc
QDomDocument qddOldFile("RsCollection");
if (qddOldFile.setContent(&file)) {
QDomElement docOldElem = qddOldFile.elementsByTagName("RsCollection").at(0).toElement();
std::vector<ColFileInfo> colOldFileInfos;
recursCollectColFileInfos(docOldElem,colOldFileInfos,QString(),false);
QDomElement root = _xml_doc.elementsByTagName("RsCollection").at(0).toElement(); e.appendChild(d) ;
for(uint32_t i = 0;i<colOldFileInfos.size();++i){ }
recursAddElements(_xml_doc,colOldFileInfos[i],root) ;
}
}
} else if (mb.clickedButton()==btnCancel) { return true;
return false;
} else {
return false;
}
}//if(file.exists())
_fileName=fileName;
std::vector<ColFileInfo> colFileInfos ;
recursCollectColFileInfos(_xml_doc.documentElement(),colFileInfos,QString(),false) ;
RsCollectionDialog* rcd = new RsCollectionDialog(fileName, colFileInfos,true);
connect(rcd,SIGNAL(saveColl(std::vector<ColFileInfo>, QString)),this,SLOT(saveColl(std::vector<ColFileInfo>, QString))) ;
_saved=false;
rcd->exec() ;
delete rcd;
return _saved;
} }
bool RsCollection::openColl(const QString& fileName, bool readOnly /* = false */, bool showError /* = true*/) qulonglong RsCollection::count() const
{ {
if (load(fileName, showError)) { return mFileTree->numFiles();
std::vector<ColFileInfo> colFileInfos ;
recursCollectColFileInfos(_xml_doc.documentElement(),colFileInfos,QString(),false) ;
RsCollectionDialog* rcd = new RsCollectionDialog(fileName, colFileInfos, true, readOnly);
connect(rcd,SIGNAL(saveColl(std::vector<ColFileInfo>, QString)),this,SLOT(saveColl(std::vector<ColFileInfo>, QString))) ;
_saved=false;
rcd->exec() ;
delete rcd;
return _saved;
}
return false;
} }
qulonglong RsCollection::size() qulonglong RsCollection::size()
{ {
QDomElement docElem = _xml_doc.documentElement(); return mFileTree->totalFileSize();
std::vector<ColFileInfo> colFileInfos;
recursCollectColFileInfos(docElem, colFileInfos, QString(),false);
uint64_t size = 0;
for (uint32_t i = 0; i < colFileInfos.size(); ++i) {
size += colFileInfos[i].size;
}
return size;
} }
bool RsCollection::isCollectionFile(const QString &fileName) bool RsCollection::isCollectionFile(const QString &fileName)
{ {
QString ext = QFileInfo(fileName).suffix().toLower(); QString ext = QFileInfo(fileName).suffix().toLower();
return (ext == RsCollection::ExtensionString); return (ext == RsCollection::ExtensionString);
} }
void RsCollection::saveColl(std::vector<ColFileInfo> colFileInfos, const QString &fileName) void RsCollection::updateHashes(const std::map<RsFileHash,RsFileHash>& old_to_new_hashes)
{ {
for(auto it:old_to_new_hashes)
{
auto fit = mHashes.find(it.first);
QDomElement root = _xml_doc.elementsByTagName("RsCollection").at(0).toElement(); if(fit == mHashes.end())
while (root.childNodes().count()>0) root.removeChild(root.firstChild()); {
for(uint32_t i = 0;i<colFileInfos.size();++i) RsErr() << "Could not find hash " << it.first << " in RsCollection list of hashes. This is a bug." ;
recursAddElements(_xml_doc,colFileInfos[i],root) ; return ;
}
_saved=save(fileName); const auto& fd(mFileTree->fileData(fit->second));
if(fd.hash != it.first)
{
RsErr() << "Mismatch hash for file " << fd.name << " (found " << fd.hash << " instead of " << it.first << ") in RsCollection list of hashes. This is a bug." ;
return ;
}
mFileTree->updateFile(fit->second,fd.name,it.second,fd.size);
}
}
bool RsCollection::removeFile(RsFileTree::FileIndex index_to_remove,RsFileTree::DirIndex parent_index)
{
return mFileTree->removeFile(index_to_remove,parent_index);
}
bool RsCollection::removeDirectory(RsFileTree::DirIndex index_to_remove,RsFileTree::DirIndex parent_index)
{
return mFileTree->removeDirectory(index_to_remove,parent_index);
}
void RsCollection::cleanup()
{
RsDbg() << "Cleaning up RsCollection with " << mFileTree->numDirs() << " dirs and " << mFileTree->numFiles() << " files." ;
mFileTree = RsFileTree::fromTreeCleaned(*mFileTree);
RsDbg() << "Simplified to " << mFileTree->numDirs() << " dirs and " << mFileTree->numFiles() << " files.";
} }

View File

@ -26,7 +26,6 @@
#pragma once #pragma once
#include <QObject>
#include <QString> #include <QString>
#include <QDomDocument> #include <QDomDocument>
#include <QFile> #include <QFile>
@ -53,66 +52,70 @@ public:
}; };
Q_DECLARE_METATYPE(ColFileInfo) Q_DECLARE_METATYPE(ColFileInfo)
class RsCollection : public QObject class RsCollection
{ {
Q_OBJECT
public: public:
enum class RsCollectionErrorCode:uint8_t {
NO_ERROR = 0x00,
UNKNOWN_ERROR = 0x01,
FILE_READ_ERROR = 0x02,
FILE_CONTAINS_HARMFUL_STRINGS = 0x03,
INVALID_ROOT_NODE = 0x04,
XML_PARSING_ERROR = 0x05,
};
RsCollection(QObject *parent = 0) ; RsCollection();
// create from list of files and directories RsCollection(const RsCollection&);
RsCollection(const std::vector<DirDetails>& file_entries, FileSearchFlags flags, QObject *parent = 0) ; RsCollection(const std::vector<DirDetails>& file_entries, FileSearchFlags flags) ;
RsCollection(const RsFileTree& fr); RsCollection(const RsFileTree& ft);
virtual ~RsCollection() ; RsCollection(const QString& filename,RsCollectionErrorCode& error_code);
void merge_in(const QString& fname,uint64_t size,const RsFileHash& hash) ; static QString errorString(RsCollectionErrorCode code);
void merge_in(const RsFileTree& tree) ;
virtual ~RsCollection() ;
void merge_in(const QString& fname,uint64_t size,const RsFileHash& hash,RsFileTree::DirIndex parent_index=0) ;
void merge_in(const RsFileTree& tree,RsFileTree::DirIndex parent_index=0) ;
bool removeFile(RsFileTree::FileIndex index_to_remove,RsFileTree::DirIndex parent_index);
bool removeDirectory(RsFileTree::DirIndex index_to_remove,RsFileTree::DirIndex parent_index);
void cleanup(); // cleans up the collection, which may contain unreferenced files/dirs after lazy editing.
static const QString ExtensionString ; static const QString ExtensionString ;
// Loads file from disk.
bool load(QWidget *parent);
bool load(const QString& fileName, bool showError = true);
// Save to disk // Save to disk
bool save(QWidget *parent) const ;
bool save(const QString& fileName) const ; bool save(const QString& fileName) const ;
// Open new collection // returns the file tree
bool openNewColl(QWidget *parent, QString fileName = ""); const RsFileTree& fileTree() const { return *mFileTree; }
// Open existing collection // total size of files in the collection
bool openColl(const QString& fileName, bool readOnly = false, bool showError = true); qulonglong size();
// total number of files in the collection
// Download the content. qulonglong count() const;
void downloadFiles() const ;
// Auto Download all the content.
void autoDownloadFiles() const ;
qulonglong size();
static bool isCollectionFile(const QString& fileName); static bool isCollectionFile(const QString& fileName);
private slots: void updateHashes(const std::map<RsFileHash,RsFileHash>& old_to_new_hashes);
void saveColl(std::vector<ColFileInfo> colFileInfos, const QString& fileName);
private: private:
void recursAddElements(QDomDocument&, const DirDetails&, QDomElement&, FileSearchFlags flags) const ; bool recursExportToXml(QDomDocument& doc,QDomElement& e,const RsFileTree::DirData& dd) const;
void recursAddElements(QDomDocument&,const ColFileInfo&,QDomElement&) const; bool recursParseXml(QDomDocument& doc, const QDomNode &e, RsFileTree::DirIndex dd) ;
void recursAddElements(
QDomDocument& doc, const RsFileTree& ft, uint32_t index, // This function is used to populate a RsCollection from locally or remotly shared files.
QDomElement& e ) const; void recursAddElements(RsFileTree::DirIndex parent, const DirDetails& dd, FileSearchFlags flags) ;
// This function is used to merge an existing RsFileTree into the RsCollection
void recursMergeTree(RsFileTree::DirIndex parent, const RsFileTree& tree, const RsFileTree::DirData &dd);
// check that the file is a valid rscollection file, and not a lol bomb or some shit like this
static bool checkFile(const QString &fileName, RsCollectionErrorCode &error);
void recursCollectColFileInfos(const QDomElement&,std::vector<ColFileInfo>& colFileInfos,const QString& current_dir,bool bad_chars_in_parent) const ;
// check that the file is a valid rscollection file, and not a lol bomb or some shit like this
static bool checkFile(const QString &fileName, bool showError);
// Auto Download recursively. // Auto Download recursively.
void autoDownloadFiles(ColFileInfo colFileInfo, QString dlDir) const ; void autoDownloadFiles(ColFileInfo colFileInfo, QString dlDir) const ;
QDomDocument _xml_doc ; std::unique_ptr<RsFileTree> mFileTree;
QString _fileName ; std::map<RsFileHash,RsFileTree::FileIndex> mHashes; // used to efficiently update files being hashed
bool _saved;
QDomElement _root ;
friend class RsCollectionDialog ; friend class RsCollectionDialog ;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -18,8 +18,10 @@
* * * *
*******************************************************************************/ *******************************************************************************/
#include <set>
#include "ui_RsCollectionDialog.h" #include "ui_RsCollectionDialog.h"
#include "RsCollection.h" #include "RsCollection.h"
#include "RsCollectionModel.h"
#include <QFileSystemModel> #include <QFileSystemModel>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
@ -30,33 +32,45 @@ class RsCollectionDialog: public QDialog
Q_OBJECT Q_OBJECT
public: public:
RsCollectionDialog(const QString& filename
, const std::vector<ColFileInfo> &colFileInfos
, const bool& creation
, const bool& readOnly = false) ;
virtual ~RsCollectionDialog(); virtual ~RsCollectionDialog();
// Open new collection
static bool openNewCollection(const RsFileTree &tree = RsFileTree());
// Edit existing collection
static bool editExistingCollection(const QString& fileName, bool showError = true);
// Open existing collection for download
static bool openExistingCollection(const QString& fileName, bool showError = true);
// Open existing collection for download
static bool downloadFiles(const RsCollection& collection);
protected: protected:
bool eventFilter(QObject *obj, QEvent *ev); static QString errorString(RsCollection::RsCollectionErrorCode code);
void init(const QString& collectionFileName);
enum RsCollectionDialogMode {
UNKNOWN = 0x00,
EDIT = 0x01,
DOWNLOAD = 0x02,
};
RsCollectionDialog(const QString& filename, RsCollectionDialogMode mode) ;
RsCollectionDialog(const RsCollection& coll, RsCollectionDialogMode mode) ;
private slots: private slots:
void directoryLoaded(QString dirLoaded); void directoryLoaded(QString dirLoaded);
void updateSizes() ; void updateSizes() ;
void changeFileName() ; void changeFileName() ;
void add() ; void addSelection() ;
void addRecursive() ; void addSelectionRecursive() ;
void remove() ; void remove() ;
void chooseDestinationDirectory(); void chooseDestinationDirectory();
void setDestinationDirectory(); void setDestinationDirectory();
void openDestinationDirectoryMenu(); void openDestinationDirectoryMenu();
void processItem(QMap<QString, QString> &dirToAdd
, int &index
, ColFileInfo &parent
) ;
void makeDir() ; void makeDir() ;
void fileHashingFinished(QList<HashedFile> hashedFiles) ; void fileHashingFinished(QList<HashedFile> hashedFiles) ;
void itemChanged(QTreeWidgetItem* item,int col) ;
void updateRemoveDuplicate(bool checked);
void cancel() ; void cancel() ;
void download() ; void download() ;
void save() ; void save() ;
@ -66,26 +80,20 @@ signals:
private: private:
void processSettings(bool bLoad) ; void processSettings(bool bLoad) ;
QTreeWidgetItem* getRootItem(); void addSelection(bool recursive) ;
bool updateList();
bool addChild(QTreeWidgetItem *parent, const std::vector<ColFileInfo> &child);
bool removeItem(QTreeWidgetItem *item, bool &removeOnlyFile) ;
void addRecursive(bool recursive) ;
bool addAllChild(QFileInfo &fileInfoParent
, QMap<QString, QString > &dirToAdd
, QStringList &fileToHash
, int &count);
void saveChild(QTreeWidgetItem *parentItem, ColFileInfo *parentInfo = NULL);
Ui::RsCollectionDialog ui; Ui::RsCollectionDialog ui;
QString _fileName ;
const bool _creationMode ; RsCollectionDialogMode _mode;
const bool _readOnly;
std::vector<ColFileInfo> _newColFileInfos ;
QFileSystemModel *_dirModel; QFileSystemModel *_dirModel;
QSortFilterProxyModel *_tree_proxyModel; QSortFilterProxyModel *_tree_proxyModel;
QItemSelectionModel *_selectionProxy; QItemSelectionModel *_selectionProxy;
bool _dirLoaded; bool _dirLoaded;
QHash<QString,QString> _listOfFilesAddedInDir; QHash<QString,QString> _listOfFilesAddedInDir;
RsCollectionModel *mCollectionModel;
RsCollection *mCollection;
std::map<QString,RsFileHash> mFilesBeingHashed; // map of file path vs. temporary ID used for the file while hashing
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>600</width> <width>761</width>
<height>400</height> <height>434</height>
</rect> </rect>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@ -17,7 +17,7 @@
<string>Collection</string> <string>Collection</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../images.qrc"> <iconset>
<normaloff>:/images/mimetypes/rscollection-16.png</normaloff>:/images/mimetypes/rscollection-16.png</iconset> <normaloff>:/images/mimetypes/rscollection-16.png</normaloff>:/images/mimetypes/rscollection-16.png</iconset>
</property> </property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
@ -157,7 +157,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="_changeFile"> <widget class="QToolButton" name="_changeFile">
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>21</width> <width>21</width>
@ -170,6 +170,10 @@
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/browsable_blue_128.png</normaloff>:/icons/browsable_blue_128.png</iconset>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -283,7 +287,7 @@
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../images.qrc"> <iconset resource="../images.qrc">
<normaloff>:/images/feedback_arrow.png</normaloff>:/images/feedback_arrow.png</iconset> <normaloff>:/images/start.png</normaloff>:/images/start.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@ -309,7 +313,7 @@
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../images.qrc"> <iconset resource="../images.qrc">
<normaloff>:/images/update.png</normaloff>:/images/update.png</iconset> <normaloff>:/images/startall.png</normaloff>:/images/startall.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@ -334,8 +338,8 @@
<string notr="true"/> <string notr="true"/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="../images.qrc">
<normaloff>:/images/deletemail24.png</normaloff>:/images/deletemail24.png</iconset> <normaloff>:/images/delete.png</normaloff>:/images/delete.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@ -382,7 +386,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QTreeWidget" name="_fileEntriesTW"> <widget class="QTreeView" name="_fileEntriesTW">
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set> <set>QAbstractItemView::NoEditTriggers</set>
</property> </property>
@ -398,11 +402,6 @@
<property name="allColumnsShowFocus"> <property name="allColumnsShowFocus">
<bool>true</bool> <bool>true</bool>
</property> </property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget> </widget>
</widget> </widget>
</item> </item>
@ -411,26 +410,6 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="buttons_HL"> <layout class="QHBoxLayout" name="buttons_HL">
<item>
<widget class="QCheckBox" name="_removeDuplicate_CB">
<property name="text">
<string>Remove Duplicate</string>
</property>
</widget>
</item>
<item>
<spacer name="buttons_HSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QLabel" name="downloadFolder_LB"> <widget class="QLabel" name="downloadFolder_LB">
<property name="text"> <property name="text">
@ -451,6 +430,12 @@
</item> </item>
<item> <item>
<widget class="QLineEdit" name="downloadFolder_LE"> <widget class="QLineEdit" name="downloadFolder_LE">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum> <enum>Qt::CustomContextMenu</enum>
</property> </property>
@ -462,6 +447,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QPushButton" name="_cancel_PB"> <widget class="QPushButton" name="_cancel_PB">
<property name="text"> <property name="text">
@ -517,7 +515,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../images.qrc"/> <include location="../images.qrc"/>
<include location="../../../../plugins/FeedReader/gui/FeedReader_images.qrc"/> <include location="../icons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -0,0 +1,616 @@
#include <string>
#include <QBrush>
#include "RsCollectionModel.h"
// #define DEBUG_COLLECTION_MODEL 1
static const int COLLECTION_MODEL_FILENAME = 0;
static const int COLLECTION_MODEL_SIZE = 1;
static const int COLLECTION_MODEL_HASH = 2;
static const int COLLECTION_MODEL_COUNT = 3;
static const int COLLECTION_MODEL_NB_COLUMN = 4;
RsCollectionModel::RsCollectionModel(const RsCollection& col, QObject *parent)
: QAbstractItemModel(parent),mCollection(col)
{
postMods();
}
static std::ostream& operator<<(std::ostream& o,const RsCollectionModel::EntryIndex& i)
{
return o << ((i.is_file)?("File"):"Dir") << " with index " << (int)i.index ;
}
static std::ostream& operator<<(std::ostream& o,const QModelIndex& i)
{
return o << "QModelIndex (row " << i.row() << ", ref " << i.internalId() << ")" ;
}
#ifdef DEBUG_COLLECTION_MODEL
#endif
// Indernal Id is always a quintptr_t (basically a uint with the size of a pointer). Depending on the
// Indernal Id is always a quintptr_t (basically a uint with the size of a pointer). Depending on the
// architecture, the pointer may have 4 or 8 bytes. We use the low-level bit for type (0=dir, 1=file) and
// the remaining bits for the index (which will be accordingly understood as a FileIndex or a DirIndex)
// This way, index 0 is always the top dir.
bool RsCollectionModel::convertIndexToInternalId(const EntryIndex& e,quintptr& ref)
{
ref = (e.index << 1) | e.is_file;
return true;
}
bool RsCollectionModel::convertInternalIdToIndex(quintptr ref, EntryIndex& e)
{
e.is_file = (bool)(ref & 1);
e.index = ref >> 1;
return true;
}
int RsCollectionModel::rowCount(const QModelIndex& parent) const
{
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "Asking rowCount of " << parent << std::endl;
#endif
if(parent.column() >= COLLECTION_MODEL_NB_COLUMN)
return 0;
if(!parent.isValid())
{
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << " root! returning " << mCollection.fileTree().directoryData(0).subdirs.size()
+ mCollection.fileTree().directoryData(0).subfiles.size() << std::endl;
#endif
return mCollection.fileTree().directoryData(0).subdirs.size()
+ mCollection.fileTree().directoryData(0).subfiles.size();
}
EntryIndex i;
if(!convertInternalIdToIndex(parent.internalId(),i))
return 0;
if(i.is_file)
{
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << " file: returning 0" << std::endl;
#endif
return 0;
}
else
{
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << " dir: returning " << mCollection.fileTree().directoryData(i.index).subdirs.size() + mCollection.fileTree().directoryData(i.index).subfiles.size()
<< std::endl;
#endif
return mCollection.fileTree().directoryData(i.index).subdirs.size() + mCollection.fileTree().directoryData(i.index).subfiles.size();
}
}
bool RsCollectionModel::hasChildren(const QModelIndex & parent) const
{
if(!parent.isValid())
return true;
EntryIndex i;
if(!convertInternalIdToIndex(parent.internalId(),i))
return false;
if(i.is_file)
return false;
else if(mCollection.fileTree().directoryData(i.index).subdirs.size() + mCollection.fileTree().directoryData(i.index).subfiles.size() > 0)
return true;
else
return false;
}
int RsCollectionModel::columnCount(const QModelIndex&) const
{
return COLLECTION_MODEL_NB_COLUMN;
}
QVariant RsCollectionModel::headerData(int section, Qt::Orientation,int role) const
{
if(role == Qt::DisplayRole)
switch(section)
{
case 0: return tr("File");
case 1: return tr("Size");
case 2: return tr("Hash");
case 3: return tr("Count");
default:
return QVariant();
}
return QVariant();
}
QModelIndex RsCollectionModel::index(int row, int column, const QModelIndex & parent) const
{
if(row < 0 || column < 0 || column >= columnCount(parent) || row >= rowCount(parent))
return QModelIndex();
EntryIndex parent_index;
if(!parent.isValid()) // root
{
parent_index.is_file = false;
parent_index.index = 0;
}
else if(!convertInternalIdToIndex(parent.internalId(),parent_index))
return QModelIndex();
if(parent_index.is_file || parent_index.index >= mCollection.fileTree().numDirs())
return QModelIndex();
const auto& parentData(mCollection.fileTree().directoryData(parent_index.index));
if((size_t)row < parentData.subdirs.size())
{
EntryIndex e;
e.is_file = false;
e.index = parentData.subdirs[row];
quintptr ref;
convertIndexToInternalId(e,ref);
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "creating index for row " << row << " of parent " << parent << ". result is " << createIndex(row,column,ref) << std::endl;
#endif
return createIndex(row,column,ref);
}
if((size_t)row < parentData.subdirs.size() + parentData.subfiles.size())
{
EntryIndex e;
e.is_file = true;
e.index = parentData.subfiles[row - parentData.subdirs.size()];
quintptr ref;
convertIndexToInternalId(e,ref);
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "creating index for row " << row << " of parent " << parent << ". result is " << createIndex(row,column,ref) << std::endl;
#endif
return createIndex(row,column,ref);
}
return QModelIndex();
}
Qt::ItemFlags RsCollectionModel::flags ( const QModelIndex & index ) const
{
if(index.isValid() && index.column() == COLLECTION_MODEL_FILENAME)
{
EntryIndex e;
if(!convertInternalIdToIndex(index.internalId(),e))
return QAbstractItemModel::flags(index) ;
if(e.is_file)
return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable;
else
return QAbstractItemModel::flags(index) | Qt::ItemIsAutoTristate | Qt::ItemIsUserCheckable;
}
return QAbstractItemModel::flags(index) ;
}
QModelIndex RsCollectionModel::parent(const QModelIndex & index) const
{
if(!index.isValid())
return QModelIndex();
EntryIndex i;
if(index.internalId()==0 || !convertInternalIdToIndex(index.internalId(),i))
return QModelIndex();
EntryIndex p;
p.is_file = false; // all parents are directories
int row;
if(i.is_file)
{
p.index = mFileInfos[i.index].parent_index;
row = mFileInfos[i.index].parent_row;
}
else
{
p.index = mDirInfos[i.index].parent_index;
row = mDirInfos[i.index].parent_row;
}
quintptr ref;
convertIndexToInternalId(p,ref);
return createIndex(row,0,ref);
}
QVariant RsCollectionModel::data(const QModelIndex& index, int role) const
{
EntryIndex i;
if(!convertInternalIdToIndex(index.internalId(),i))
return QVariant();
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "Asking data of " << i << std::endl;
#endif
switch(role)
{
case Qt::DisplayRole: return displayRole(i,index.column());
case Qt::DecorationRole: return decorationRole(i,index.column());
case Qt::CheckStateRole: return checkStateRole(i,index.column());
case Qt::TextColorRole: return textColorRole(i,index.column());
default:
return QVariant();
}
}
bool RsCollectionModel::setData(const QModelIndex& index,const QVariant& value,int role)
{
if(!index.isValid())
return false;
EntryIndex e;
if(role==Qt::CheckStateRole && convertInternalIdToIndex(index.internalId(), e))
{
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "Setting check state of item " << index << " to " << value.toBool() << std::endl;
#endif
RsFileTree::DirIndex dir_index ;
if(e.is_file)
{
mFileInfos[e.index].is_checked = value.toBool();
dir_index = mFileInfos[e.index].parent_index;
}
else
{
std::function<void(RsFileTree::DirIndex,bool)> recursSetCheckFlag = [&](RsFileTree::DirIndex index,bool s) -> void
{
mDirInfos[index].check_state = (s)?SELECTED:UNSELECTED;
auto& dir_data(mCollection.fileTree().directoryData(index));
mDirInfos[index].total_size = 0;
mDirInfos[index].total_count = 0;
for(uint32_t i=0;i<dir_data.subdirs.size();++i)
{
recursSetCheckFlag(dir_data.subdirs[i],s);
mDirInfos[index].total_size += mDirInfos[dir_data.subdirs[i]].total_size ;
mDirInfos[index].total_count+= mDirInfos[dir_data.subdirs[i]].total_count;
}
for(uint32_t i=0;i<dir_data.subfiles.size();++i)
{
mFileInfos[dir_data.subfiles[i]].is_checked = s;
if(s)
{
mDirInfos[index].total_size += mCollection.fileTree().fileData(dir_data.subfiles[i]).size;
++mDirInfos[index].total_count;
}
}
};
recursSetCheckFlag(e.index,value.toBool());
dir_index = mDirInfos[e.index].parent_index;
}
// now go up the directories and update the check tristate flag, depending on whether the children are all checked/unchecked or mixed.
do
{
auto& dit(mDirInfos[dir_index]);
const RsFileTree::DirData& dir_data(mCollection.fileTree().directoryData(dir_index)); // get the directory data
bool locally_all_checked = true;
bool locally_all_unchecked = true;
dit.total_size = 0;
dit.total_count = 0;
for(uint32_t i=0;i<dir_data.subdirs.size();++i)
{
const auto& dit2(mDirInfos[dir_data.subdirs[i]]);
dit.total_size += dit2.total_size;
dit.total_count += dit2.total_count;
if(dit2.check_state == UNSELECTED || dit2.check_state == PARTIALLY_SELECTED)
locally_all_checked = false;
if(dit2.check_state == SELECTED || dit2.check_state == PARTIALLY_SELECTED)
locally_all_unchecked = false;
}
for(uint32_t i=0;i<dir_data.subfiles.size();++i)
{
const auto& fit2(mFileInfos[dir_data.subfiles[i]]);
if(fit2.is_checked)
{
dit.total_size += mCollection.fileTree().fileData(dir_data.subfiles[i]).size;
++dit.total_count;
locally_all_unchecked = false;
}
else
locally_all_checked = false;
}
if(locally_all_checked)
dit.check_state = SELECTED;
else if(locally_all_unchecked)
dit.check_state = UNSELECTED;
else
dit.check_state = PARTIALLY_SELECTED;
if(dir_index == mCollection.fileTree().root())
break;
else
dir_index = dit.parent_index; // get the directory data
}
while(true);
const auto& top_dir(mCollection.fileTree().directoryData(mCollection.fileTree().root()));
emit dataChanged(createIndex(0,0,(void*)NULL),
createIndex(top_dir.subdirs.size() + top_dir.subfiles.size() - 1,
COLLECTION_MODEL_NB_COLUMN-1,
(void*)NULL),
{ Qt::CheckStateRole });
emit sizesChanged();
return true;
}
else
return QAbstractItemModel::setData(index,value,role);
}
QVariant RsCollectionModel::textColorRole(const EntryIndex& i,int col) const
{
if(i.is_file && mFilesBeingHashed.find(mCollection.fileTree().fileData(i.index).hash) != mFilesBeingHashed.end())
return QVariant(QBrush(QColor::fromRgbF(0.1,0.9,0.2)));
else
return QVariant();
}
QVariant RsCollectionModel::checkStateRole(const EntryIndex& i,int col) const
{
if(col == COLLECTION_MODEL_FILENAME)
{
if(i.is_file)
{
if(mFileInfos[i.index].is_checked)
return QVariant(Qt::Checked);
else
return QVariant(Qt::Unchecked);
}
else
{
switch(mDirInfos[i.index].check_state)
{
case SELECTED: return QVariant::fromValue((int)Qt::Checked);
case PARTIALLY_SELECTED: return QVariant::fromValue((int)Qt::PartiallyChecked);
default:
case UNSELECTED: return QVariant::fromValue((int)Qt::Unchecked);
}
}
}
else
return QVariant();
}
QVariant RsCollectionModel::displayRole(const EntryIndex& i,int col) const
{
switch(col)
{
case COLLECTION_MODEL_FILENAME: if(i.is_file)
return QString::fromUtf8(mCollection.fileTree().fileData(i.index).name.c_str());
else
return QString::fromUtf8(mCollection.fileTree().directoryData(i.index).name.c_str());
case COLLECTION_MODEL_SIZE: if(i.is_file)
return QVariant((qulonglong)mCollection.fileTree().fileData(i.index).size) ;
else
return QVariant((qulonglong)mDirInfos[i.index].total_size);
case COLLECTION_MODEL_HASH: if(i.is_file)
{
if(mFilesBeingHashed.find(mCollection.fileTree().fileData(i.index).hash)!=mFilesBeingHashed.end())
return tr("[File is being hashed]");
else
return QString::fromStdString(mCollection.fileTree().fileData(i.index).hash.toStdString());
}
else
return QVariant();
case COLLECTION_MODEL_COUNT: if(i.is_file)
return (qulonglong)mFileInfos[i.index].is_checked;
else
return (qulonglong)(mDirInfos[i.index].total_count);
}
return QVariant();
}
QVariant RsCollectionModel::sortRole(const EntryIndex& i,int col) const
{
return QVariant();
}
QVariant RsCollectionModel::decorationRole(const EntryIndex& i,int col) const
{
return QVariant();
}
RsCollectionModel::EntryIndex RsCollectionModel::getIndex(const QModelIndex& i) const
{
EntryIndex res;
res.is_file = false;
res.index = 0;
convertInternalIdToIndex(i.internalId(),res);
return res;
}
bool RsCollectionModel::isChecked(EntryIndex i)
{
if(i.is_file)
return mFileInfos[i.index].is_checked;
else
return mDirInfos[i.index].check_state != DirCheckState::UNSELECTED;
}
void RsCollectionModel::notifyFilesBeingHashed(const std::list<RsFileHash>& files)
{
mFilesBeingHashed.insert(files.begin(),files.end());
}
void RsCollectionModel::fileHashingFinished(const RsFileHash& hash)
{
mFilesBeingHashed.erase(hash);
}
void RsCollectionModel::preMods()
{
mUpdating = true;
emit layoutAboutToBeChanged();
}
void RsCollectionModel::postMods()
{
// update all the local structures
mDirInfos.clear();
mFileInfos.clear();
mDirInfos.resize(mCollection.fileTree().numDirs());
mFileInfos.resize(mCollection.fileTree().numFiles());
mDirInfos[0].parent_index = 0;
#ifdef DEBUG_COLLECTION_MODEL
std::cerr << "Updating from tree: " << std::endl;
#endif
recursUpdateLocalStructures(mCollection.fileTree().root(),0);
mUpdating = false;
emit layoutChanged();
emit sizesChanged();
// debugDump();
}
void RsCollectionModel::recursUpdateLocalStructures(RsFileTree::DirIndex dir_index,int depth)
{
uint64_t total_size = 0;
uint64_t total_count = 0;
bool all_checked = true;
bool all_unchecked = false;
const auto& dd(mCollection.fileTree().directoryData(dir_index));
for(uint32_t i=0;i<dd.subdirs.size();++i)
{
#ifdef DEBUG_COLLECTION_MODEL
for(int j=0;j<depth;++j) std::cerr << " ";
std::cerr << "Dir \"" << mCollection.fileTree().directoryData(dd.subdirs[i]).name << "\"" << std::endl ;
#endif
recursUpdateLocalStructures(dd.subdirs[i],depth+1);
auto& ref(mDirInfos[dd.subdirs[i]]);
total_size += ref.total_size;
total_count += ref.total_count;
ref.parent_index = dir_index;
ref.parent_row = i;
all_checked = all_checked && (ref.check_state == SELECTED);
all_unchecked = all_unchecked && (ref.check_state == UNSELECTED);
}
for(uint32_t i=0;i<dd.subfiles.size();++i)
{
#ifdef DEBUG_COLLECTION_MODEL
for(int j=0;j<depth;++j) std::cerr << " ";
std::cerr << "File \"" << mCollection.fileTree().fileData(dd.subfiles[i]).name << "\"" << std::endl;
#endif
auto& ref(mFileInfos[dd.subfiles[i]]);
if(ref.is_checked)
{
total_size += mCollection.fileTree().fileData(dd.subfiles[i]).size;
++total_count;
}
ref.parent_index = dir_index;
ref.parent_row = i + dd.subdirs.size();
all_checked = all_checked && ref.is_checked;
all_unchecked = all_unchecked && !ref.is_checked;
}
auto& r(mDirInfos[dir_index]);
r.total_size = total_size;
r.total_count = total_count;
if(all_checked)
r.check_state = SELECTED;
else if(all_unchecked)
r.check_state = UNSELECTED;
else
r.check_state = PARTIALLY_SELECTED;
}
void RsCollectionModel::debugDump()
{
std::function<void(RsFileTree::DirIndex,int)> recursDump = [&](RsFileTree::DirIndex indx,int depth) {
const auto& dir_data(mCollection.fileTree().directoryData(indx));
for(int i=0;i<depth;++i) std::cerr << " ";
std::cerr << "Directory: \"" << dir_data.name << "\" total_size: " << mDirInfos[indx].total_size << " cnt: " << mDirInfos[indx].total_count << std::endl;
for(uint32_t i=0;i<dir_data.subdirs.size();++i)
recursDump(dir_data.subdirs[i],depth+1);
for(uint32_t i=0;i<dir_data.subfiles.size();++i)
{
for(int i=0;i<depth+1;++i) std::cerr << " ";
std::cerr << "File: \"" << mCollection.fileTree().fileData(dir_data.subfiles[i]).name << "\"" << std::endl;
}
};
std::cerr << "mCollectionModel data: " << std::endl;
recursDump(mCollection.fileTree().root(),0);
std::function<void(QModelIndex index,int)> recursDump2 = [&](QModelIndex indx,int depth) {
for(int i=0;i<depth;++i) std::cerr << " ";
EntryIndex entry;
convertInternalIdToIndex(indx.internalId(),entry);
std::cerr << "Index: " << indx << " has_children: " << RsCollectionModel::hasChildren(indx)
<< ", rowCount: " << RsCollectionModel::rowCount(indx)
<< ", internalId: " << (uint64_t)indx.internalId()
<< ", type: " << (entry.is_file?"file":"dir") << " number " << entry.index
<< ", prow: " << (entry.is_file?mFileInfos[entry.index].parent_row:mDirInfos[entry.index].parent_row)
<< ", parent: " << RsCollectionModel::parent(indx)
<< ", display role: \"" << displayRole(entry,0).toString().toStdString()
<< std::endl;
for(int i=0;i<RsCollectionModel::rowCount(indx);++i)
recursDump2(RsCollectionModel::index(i,0,indx),depth+1);
};
std::cerr << "mCollectionModel index structure: " << std::endl;
recursDump2(QModelIndex(),0);
std::cerr << "mCollectionModel internal data: " << std::endl;
std::cerr << "Directories: "<< std::endl;
for(uint32_t i=0;i<mCollection.fileTree().numDirs();++i)
std::cerr << " " << i << ": "<< mCollection.fileTree().directoryData(i).name << std::endl;
std::cerr << "Files: "<< std::endl;
for(uint32_t i=0;i<mCollection.fileTree().numFiles();++i)
std::cerr << " " << i << ": " << mCollection.fileTree().fileData(i).name << std::endl;
}

View File

@ -0,0 +1,108 @@
#include <QAbstractItemModel>
#include "RsCollection.h"
class RsCollectionModel: public QAbstractItemModel
{
Q_OBJECT
public:
enum Roles{ FileNameRole = Qt::UserRole+1, SortRole = Qt::UserRole+2, FilterRole = Qt::UserRole+3 };
RsCollectionModel(const RsCollection& col, QObject *parent = 0);
virtual ~RsCollectionModel() = default;
/* Callback from Core */
void preMods(); // always call this before updating the RsCollection!
void postMods(); // always call this after updating the RsCollection!
/* Callback from GUI */
void update() ;
void filterItems(const std::list<std::string>& keywords, uint32_t& found) ;
// Overloaded from QAbstractItemModel
virtual QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex() ) const override;
virtual QModelIndex parent ( const QModelIndex & index ) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
virtual bool hasChildren(const QModelIndex & parent = QModelIndex()) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex& index,const QVariant& value,int role) override;
virtual Qt::ItemFlags flags ( const QModelIndex & index ) const override;
#ifdef TODO
virtual QStringList mimeTypes () const override;
virtual QMimeData * mimeData ( const QModelIndexList & indexes ) const override;
#if QT_VERSION >= QT_VERSION_CHECK (5, 0, 0)
virtual Qt::DropActions supportedDragActions() const override;
#endif
#endif
struct EntryIndex {
bool is_file; // false=dir, true=file
uint64_t index;
};
uint64_t totalSize() const { return mDirInfos[0].total_size; }
uint64_t totalSelected() const { return mDirInfos[0].total_count; }
void notifyFilesBeingHashed(const std::list<RsFileHash>& files);
void fileHashingFinished(const RsFileHash& hash);
bool isChecked(EntryIndex);
EntryIndex getIndex(const QModelIndex& i) const;
signals:
void sizesChanged(); // tells that the total size of the top level dir has changed (due to selection)
private:
static bool convertIndexToInternalId(const EntryIndex& e,quintptr& ref);
static bool convertInternalIdToIndex(quintptr ref, EntryIndex& e);
void recursUpdateLocalStructures(RsFileTree::DirIndex dir_index, int depth);
QVariant displayRole(const EntryIndex&,int col) const ;
QVariant sortRole(const EntryIndex&,int col) const ;
QVariant decorationRole(const EntryIndex&,int col) const ;
QVariant checkStateRole(const EntryIndex& i,int col) const;
QVariant textColorRole(const EntryIndex& i,int col) const;
//QVariant filterRole(const DirDetails& details,int coln) const;
void debugDump();
bool mUpdating ;
const RsCollection& mCollection;
enum DirCheckState: uint8_t {
UNSELECTED = 0x00,
PARTIALLY_SELECTED = 0x01,
SELECTED = 0x02,
};
struct ModelDirInfo {
ModelDirInfo() :parent_index(0),parent_row(0),check_state(SELECTED),total_size(0),total_count(0){}
RsFileTree::DirIndex parent_index; // index of the parent
RsFileTree::DirIndex parent_row; // row of that child, in this parent
DirCheckState check_state;
uint64_t total_size;
uint64_t total_count;
};
struct ModelFileInfo {
ModelFileInfo() :parent_index(0),parent_row(0),is_checked(true){}
RsFileTree::DirIndex parent_index; // index of the parent
RsFileTree::DirIndex parent_row; // row of that child, in this parent
bool is_checked;
};
std::vector<ModelFileInfo> mFileInfos;
std::vector<ModelDirInfo> mDirInfos;
std::set<RsFileHash> mFilesBeingHashed;
// std::set<void*> mFilteredPointers ;
};

View File

@ -21,19 +21,13 @@
#include <stdexcept> #include <stdexcept>
#include <QDesktopServices> #include <QDesktopServices>
#include <QUrl> #include <QUrl>
#include "RsCollection.h" #include "RsCollectionDialog.h"
#include "RsUrlHandler.h" #include "RsUrlHandler.h"
bool RsUrlHandler::openUrl(const QUrl& url) bool RsUrlHandler::openUrl(const QUrl& url)
{ {
if(url.scheme() == QString("file") && url.toLocalFile().endsWith("."+RsCollection::ExtensionString)) if(url.scheme() == QString("file") && url.toLocalFile().endsWith("."+RsCollection::ExtensionString))
{ return RsCollectionDialog::openExistingCollection(url.toLocalFile());
RsCollection collection ;
if(collection.load(url.toLocalFile()))
{
collection.downloadFiles() ;
return true;
}
}
return QDesktopServices::openUrl(url) ; return QDesktopServices::openUrl(url) ;
} }

View File

@ -519,6 +519,7 @@ HEADERS += rshare.h \
gui/common/vmessagebox.h \ gui/common/vmessagebox.h \
gui/common/RsUrlHandler.h \ gui/common/RsUrlHandler.h \
gui/common/RsCollectionDialog.h \ gui/common/RsCollectionDialog.h \
gui/common/RsCollectionModel.h \
gui/common/rwindow.h \ gui/common/rwindow.h \
gui/common/rshtml.h \ gui/common/rshtml.h \
gui/common/AvatarDefs.h \ gui/common/AvatarDefs.h \
@ -845,6 +846,7 @@ SOURCES += main.cpp \
gui/common/ElidedLabel.cpp \ gui/common/ElidedLabel.cpp \
gui/common/vmessagebox.cpp \ gui/common/vmessagebox.cpp \
gui/common/RsCollectionDialog.cpp \ gui/common/RsCollectionDialog.cpp \
gui/common/RsCollectionModel.cpp \
gui/common/RsUrlHandler.cpp \ gui/common/RsUrlHandler.cpp \
gui/common/rwindow.cpp \ gui/common/rwindow.cpp \
gui/common/rshtml.cpp \ gui/common/rshtml.cpp \

@ -1 +1 @@
Subproject commit ba0de17f41914f1be6754db5bc5b4507b0deaafd Subproject commit 542a8c07bd02f9bb9082f7aba5aaaed54e643fc1

@ -1 +1 @@
Subproject commit 2226ef0a20a001ec0942be6abe5e909c15447d8e Subproject commit 8623304b62294dafbe477573f321a464fef721dd