Mantid
Loading...
Searching...
No Matches
RepoModel.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8
10#include <utility>
11#include <vector>
12
14#include "MantidKernel/Logger.h"
15#include "MantidQtIcons/Icon.h"
16#include <QIcon>
17#include <QPixmap>
18
19#include <QMessageBox>
20#include <QtConcurrentRun>
21#include <stdexcept>
22
23using namespace MantidQt::API;
26
27namespace {
29Mantid::Kernel::Logger g_log("RepoModel");
30} // namespace
31
32const char *nofile_flag = "nofile";
33
36static QString download_thread(Mantid::API::ScriptRepository_sptr &pt, const std::string &path) {
37 QString result("");
38 try {
39 pt->download(path);
41 QString info = QString::fromStdString(ex.what());
42 // make the exception a nice html message
43 info.replace("\n", "</p><p>");
44 return info;
45 }
46 return result;
47}
48
49/*
50 An auxiliary nested class to help RepoModel to rebuild the hierarchical
51 tree of ScriptRepository.
52
53 It will keep track of the path that is the main key to access metadata
54 on ScriptRepository and an auxiliary label to easy the display on a
55 user nicer way.
56
57*/
58RepoModel::RepoItem::RepoItem(QString label, QString path, RepoItem *parent)
59 : m_label(std::move(label)), keypath(std::move(path)), parentItem(parent) {}
61RepoModel::RepoItem::~RepoItem() { qDeleteAll(childItems); }
65void RepoModel::RepoItem::appendChild(RepoItem *child) { childItems.append(child); }
66
78RepoModel::RepoItem *RepoModel::RepoItem::child(int row) const { return childItems.value(row); }
79
83int RepoModel::RepoItem::childCount() const { return static_cast<int>(childItems.count()); }
88 if (parentItem)
89 return static_cast<int>(parentItem->childItems.indexOf(const_cast<RepoItem *>(this)));
90 return 0;
91}
92
94// MODEL
96
103RepoModel::RepoModel(QObject *parent) : QAbstractItemModel(parent) {
104 const ConfigServiceImpl &config = ConfigService::Instance();
105 repo_path = QString::fromStdString(config.getString("ScriptLocalRepository"));
106 rootItem = new RepoItem("/");
110 repo_ptr = ScriptRepositoryFactory::Instance().create("ScriptRepositoryImpl");
111 connect(&download_watcher, SIGNAL(finished()), this, SLOT(downloadFinished()));
114}
115
153QVariant RepoModel::data(const QModelIndex &index, int role) const {
154 using namespace Mantid::API;
155 if (!index.isValid())
156 return QVariant();
157 const auto *item = static_cast<RepoItem *>(index.internalPointer());
158 try {
159 const QString &path = item->path();
162 // return the data for the display role
163 if (role == Qt::DisplayRole) {
164 switch (index.column()) {
165 case 0: // return the label (the path of the file/folder)
166 return item->label();
167 break;
168 case 1: // ask for the status
169 if (isDownloading(index))
170 return downloadSt();
171 status = repo_ptr->fileStatus(path.toStdString());
172 return fromStatus(status);
173 break;
174 case 2: // autoupdate option
175 status = repo_ptr->fileStatus(path.toStdString());
176 if (status == REMOTE_ONLY || status == LOCAL_ONLY)
177 return QVariant();
178 inf = repo_ptr->fileInfo(path.toStdString());
179 return inf.auto_update ? QString("true") : QString("false");
180 break;
181 }
182 }
183
184 // return the data for the DecorationRole
185 if (role == Qt::DecorationRole) {
186 if (index.column() == 0) {
187 inf = repo_ptr->fileInfo(path.toStdString());
188 if (inf.directory) {
189 status = repo_ptr->fileStatus(path.toStdString());
190 if (status == Mantid::API::REMOTE_ONLY) {
191 return Icons::getIcon("mdi.folder-network-outline", "black", 1.2);
192 } else
193 return Icons::getIcon("mdi.folder-open-outline", "black", 1.2);
194 } else {
195 int pos = static_cast<int>(QString(path).lastIndexOf('.'));
196 if (pos < 0)
197 return Icons::getIcon("mdi.file-question", "black", 1.2);
198 if (path.contains("readme", Qt::CaseInsensitive))
199 return Icons::getIcon("mdi.file-document-outline", "black", 1.2);
200
201 QString extension = QString(path).remove(0, pos);
202 if (extension == ".py" || extension == ".PY")
203 return Icons::getIcon("mdi.language-python", "black", 1.2);
204 else if (extension == ".ui")
205 return Icons::getIcon("mdi.file-document-box-outline", "black", 1.2);
206 else if (extension == ".docx" || extension == ".doc" || extension == ".odf")
207 return Icons::getIcon("mdi.file-outline", "black", 1.2);
208 else if (extension == ".pdf")
209 return Icons::getIcon("mdi.file-pdf-outline", "black", 1.2);
210 else
211 return Icons::getIcon("mdi.file-question", "black", 1.2);
212 }
213 }
214 } // end decorationRole
215
216 // tool tip role
217 if (role == Qt::ToolTipRole) {
218 if (index.column() == 1) {
219 if (isDownloading(index))
220 return "Downloading... Be patient.";
221 status = repo_ptr->fileStatus(path.toStdString());
222 inf = repo_ptr->fileInfo(path.toStdString());
223 switch (status) {
224
225 case REMOTE_ONLY:
226 return (inf.directory) ? "Click here to download this folder and all its files"
227 : "Click here to download this file";
228 break;
229 case BOTH_UNCHANGED:
230 return (inf.directory) ? "This folder is up-to-date" : "This file is up-to-date";
231 break;
232 case LOCAL_CHANGED:
233 return "This file has been modified locally";
234 case REMOTE_CHANGED:
235 return (inf.directory) ? "There is a new version of the files inside "
236 "this folder. Click here to install them."
237 : "There is a new version of this file "
238 "available. Click here to install it.";
239 case BOTH_CHANGED:
240 return (inf.directory) ? "Files in this folder may have changed both locally and "
241 "remotely.\nClick here to install the remote version, "
242 "a backup of the local version will also be created."
243 : "This file may have changed both locally and "
244 "remotely.\nClick here to install the remote version, "
245 "a backup of the local version will also be created.";
246 break;
247 case LOCAL_ONLY:
248 return "This file only exists in your local folder";
249 }
250 } else if (index.column() == 2) {
251 return "Enable or disable this item to be downloaded automatically "
252 "when new versions will be available";
253 }
254 } // end tool tip
256 handleExceptions(ex, "", false);
257 }
258 return QVariant();
259}
260
267 using namespace Mantid::API;
268 switch (status) {
269 case BOTH_UNCHANGED:
270 return updatedSt();
271 case REMOTE_ONLY:
272 return remoteOnlySt();
273 case LOCAL_ONLY:
274 return localOnlySt();
275 case REMOTE_CHANGED:
276 return remoteChangedSt();
277 case LOCAL_CHANGED:
278 return localChangedSt();
279 case BOTH_CHANGED:
280 return bothChangedSt();
281 }
282 return bothChangedSt();
283}
284
302bool RepoModel::setData(const QModelIndex &index, const QVariant &value, int role) {
303 if (!index.isValid())
304 return false;
305 if (role != Qt::EditRole)
306 // only EditRole is acceptable for the role
307 return false;
308 if (index.column() == 0)
309 // the path can not be changed
310 return false;
311 int count_changed = 0;
312 const auto *item = static_cast<RepoItem *>(index.internalPointer());
313 std::string path = item->path().toStdString();
314
315 bool ret = false;
316 // get the action
317 QString action = value.toString();
318 if (index.column() == 2) { // set auto update
319 bool option;
320 if (action == "setTrue")
321 option = true;
322 else if (action == "setFalse")
323 option = false;
324 else
325 return false; // only setTrue and setFalse are allowed values for set auto
326 // update.
327 count_changed = repo_ptr->setAutoUpdate(path, option);
328 ret = true;
329 }
330
331 if (index.column() == 1) { // trigger actions: Download
332 if (action == "Download") {
333 if (!download_threads.isFinished()) {
334 QWidget *father = qobject_cast<QWidget *>(QObject::parent());
335 QMessageBox::information(father, "Wait", "Downloading... ");
336 return false;
337 }
338 downloading_path = QString::fromStdString(path);
340 emit executingThread(true);
341 // Wrap in a lambda: Qt6 QtConcurrent::run mis-deduces a bare function pointer with
342 // by-reference args as the QPromise overload. The lambda works for both Qt5 and Qt6.
343 // repo_ptr is a member, so copy it to a local for capture.
344 auto repo = repo_ptr;
345 download_threads = QtConcurrent::run([repo, path]() mutable { return download_thread(repo, path); });
347 ret = true;
348 }
349 }
350
351 if (ret)
352 emit dataChanged(index, this->index(count_changed, 0, index));
353
354 return ret;
355}
356
364Qt::ItemFlags RepoModel::flags(const QModelIndex &index) const {
365 if (!index.isValid())
366 return {};
367 if (index.column() == 0)
368 return QAbstractItemModel::flags(index);
369 // define that setData will accept the EditRole.
370 return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
371}
372
386QVariant RepoModel::headerData(int section, Qt::Orientation orientation, int role) const {
387 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
388 switch (section) {
389 case 0:
390 return "Path";
391 case 1:
392 return "Status";
393 case 2:
394 return "AutoUpdate";
395 default:
396 return QVariant();
397 }
398 }
399 return QVariant();
400}
401
416QModelIndex RepoModel::index(int row, int column, const QModelIndex &parent) const {
417 // check if the row and column are allowed,
418 // for example, it will not accept column == 3, or row = 1
419 // for parent that refers to file and not to folder.
420 if (!hasIndex(row, column, parent))
421 return QModelIndex();
422 // retrieve the pointer ot the RepoItem from the parent
423 const RepoItem *parentItem;
424 if (!parent.isValid())
425 parentItem = rootItem;
426 else
427 parentItem = static_cast<RepoItem *>(parent.internalPointer());
428
429 // given the row, we can find the childItem from the RepoItem::child method.
430 RepoItem *childItem = parentItem->child(row);
431
432 if (childItem)
433 return createIndex(row, column, childItem);
434 else
435 return QModelIndex();
436}
437
443QModelIndex RepoModel::parent(const QModelIndex &index) const {
444 if (!index.isValid())
445 return QModelIndex();
446 // the child is the RepoItem pointed by the index.
447 auto *childItem = static_cast<RepoItem *>(index.internalPointer());
448 // the parent is the parent of the RepoItem.
449 RepoItem *parentItem = childItem->parent();
450 // the root item does not have a parent
451 if (parentItem == rootItem)
452 return QModelIndex();
453 // create the index and return
454 return createIndex(parentItem->row(), 0, parentItem);
455}
462int RepoModel::rowCount(const QModelIndex &parent) const {
463 const RepoItem *parentItem;
464
465 if (parent.column() > 0)
466 return 0; // there are rows defined only of the column 0
467
468 if (!parent.isValid())
469 parentItem = rootItem;
470 else
471 parentItem = static_cast<RepoItem *>(parent.internalPointer());
472 // return the number of children
473 return parentItem->childCount();
474}
475
482int RepoModel::columnCount(const QModelIndex & /*parent*/) const { return 3; }
483
486QString RepoModel::fileDescription(const QModelIndex &index) {
487 const auto *item = static_cast<RepoItem *>(index.internalPointer());
488 if (!item)
489 return "";
490 QString desc;
491 try {
492 desc = QString::fromStdString(repo_ptr->description(item->path().toStdString()));
493 } catch (...) {
494 // just ignore
495 }
496 return desc;
497}
498
499QString RepoModel::author(const QModelIndex &index) {
500 const auto *item = static_cast<RepoItem *>(index.internalPointer());
501 QString author = "Not defined";
502 if (!item)
503 return author;
504 try {
505 author = QString::fromStdString(repo_ptr->info(item->path().toStdString()).author);
506 } catch (...) {
507 // just ignore
508 }
509 return author;
510}
516QString RepoModel::filePath(const QModelIndex &index) {
517 const auto *item = static_cast<RepoItem *>(index.internalPointer());
518 // qDebug() << "Get file path from : " << item->path()<< '\n';
519 Mantid::API::SCRIPTSTATUS state = repo_ptr->fileStatus(item->path().toStdString());
520
521 if (state == Mantid::API::REMOTE_ONLY)
522 return "";
523 Mantid::API::ScriptInfo info = repo_ptr->fileInfo(item->path().toStdString());
524 if (info.directory)
525 return "";
526 QString path = repo_path + "/" + item->path();
527 return path;
528}
529
550 // in order to speed the algorithm, check if the
551 // folder is the same of the last folder.
552 if (parents.last()->path() == folder)
553 return parents.last();
554
555 // try to find this
556 if (folder.isEmpty())
557 return parents.first(); // the parents first will always contain the root
558
559 // it will iterate through all the parents of the given folder, in order to
560 // create any folder that has not been created.
561 QStringList folder_parts = folder.split("/");
562 QString aux_folder;
563 RepoItem *father = parents.first();
564 // there is no reason to try to find entry A/B/C if the entry A/B was not
565 // found
566 bool try_to_find = true;
567
568 for (int i = 0; i < folder_parts.size(); i++) {
569 if (i == 0)
570 aux_folder = folder_parts[i];
571 else
572 aux_folder += "/" + folder_parts[i];
573
574 bool found = false;
575
576 if (try_to_find) {
577 // this means that the previous folders were found
578 const auto it = std::find_if(parents.cbegin(), parents.cend(),
579 [&aux_folder](const auto &parent) { return parent->path() == aux_folder; });
580 if (it != parents.cend()) {
581 found = true;
582 father = *it;
583 }
584 }
585 // there is not RepoItem related to the current folder,
586 // create it
587 if (!found) {
588 RepoItem *m = new RepoItem(folder_parts[i], aux_folder, father);
589 father->appendChild(m);
590 parents.append(m);
591 father = m;
592 try_to_find = false;
593 }
594 }
595 return father;
596}
597
610 // check server for updates to repository
611 repo_ptr->check4Update();
612 // get the list of entries inside the scriptrepository
613 std::vector<std::string> list = repo_ptr->listFiles();
614
615 // auxiliary list of pointers to repoitem that are related to folders
616 QList<RepoItem *> parents;
617 // the first one will always be the root
618 parents << root;
619
620 // FOREACH entry in LISTFILES
621 for (const auto &number : list) {
622 // folder or file inside the repository
623 QString lineData = QString::fromStdString(number);
624
625 // Read the column data from the rest of the line.
626 QStringList pathStrings = lineData.split("/");
627 // separate the folder and the current entry (folder or file)
628 QString current_file = pathStrings.last();
629 QString folder = "";
630 pathStrings.removeLast();
631 if (pathStrings.size() > 0)
632 folder = pathStrings.join("/");
633
634 // get parent for this entry
635 RepoItem *parentOfFolder = getParent(folder, parents);
636 // a new folder has started
637 if (parentOfFolder == root) {
638 // this test is just for the sake of performance, to reduce the numbers of
639 // parents
640 parents.clear();
641 parents << root;
642 }
643
644 // check if the current entry is a directory
645 if (repo_ptr->info(lineData.toStdString()).directory) {
646 // directories will be appended to parents list
647 RepoItem *aux = new RepoItem(current_file, lineData, parentOfFolder);
648 parentOfFolder->appendChild(aux);
649 parents << aux;
650 } else {
651 // files will just be created and appended to the parent
652 parentOfFolder->appendChild(new RepoItem(current_file, lineData, parentOfFolder));
653 }
654 }
655}
656
658 bool showWarning) const {
659 g_log.information() << "Download failed " << ex.what() << "\n Detail: " << ex.systemError() << '\n';
660 if (showWarning) {
661
662 QWidget *father = qobject_cast<QWidget *>(QObject::parent());
663 QString info = QString::fromStdString(ex.what());
664 // make the exception a nice html message
665 info.replace("\n", "</p><p>");
666 QMessageBox::warning(father, title, QString("<html><body><p>%1</p></body></html>").arg(info));
667 }
668}
669
671 QString info = download_threads.result();
672 if (!info.isEmpty()) {
673 QMessageBox::warning(qobject_cast<QWidget *>(QObject::parent()), "Download Failed",
674 QString("<html><body><p>%1</p></body></html>").arg(info));
675 }
677 auto *repo_item = static_cast<RepoItem *>(download_index.internalPointer());
678 QModelIndex top_left = createIndex(0, 0, repo_item);
679 QModelIndex bottom_right = createIndex(0, 2, repo_item);
680 emit dataChanged(top_left, bottom_right);
681 emit executingThread(false);
682}
683
684bool RepoModel::isDownloading(const QModelIndex &index) const {
685 const auto *item = static_cast<RepoItem *>(index.internalPointer());
686 if (item)
687 return item->path() == downloading_path;
688 return false;
689}
690
692const QString &RepoModel::localOnlySt() { return LOCALONLY; }
694const QString &RepoModel::remoteOnlySt() { return REMOTEONLY; }
696const QString &RepoModel::localChangedSt() { return LOCALCHANGED; }
698const QString &RepoModel::remoteChangedSt() { return REMOTECHANGED; }
700const QString &RepoModel::updatedSt() { return BOTHUNCHANGED; }
702const QString &RepoModel::bothChangedSt() { return BOTHCHANGED; }
704const QString &RepoModel::downloadSt() { return DOWNLOADST; }
double value
The value of the point.
Definition FitMW.cpp:51
std::map< DeltaEMode::Type, std::string > index
static QString download_thread(Mantid::API::ScriptRepository_sptr &pt, const std::string &path)
Executes the download from ScriptRepository.
Definition RepoModel.cpp:36
const char * nofile_flag
Definition RepoModel.cpp:32
A nested class to help RepoModel to implement the QAbstractItemModel.
Definition RepoModel.h:80
RepoItem * parent() const
access to the parent of this entry
Definition RepoModel.h:102
void appendChild(RepoItem *child)
This method is the very responsible to allow the reconstruction of the hierarchical tree,...
Definition RepoModel.cpp:65
int row() const
To which row this repoItem belongs?
Definition RepoModel.cpp:87
RepoItem(QString label, QString path="/", RepoItem *parent=nullptr)
Definition RepoModel.cpp:58
int childCount() const
Return the number of children that this entry may find.
Definition RepoModel.cpp:83
const QString & path() const
access to the script repository path
Definition RepoModel.h:96
RepoItem * child(int row) const
Gives access to the row_th children of RepoItem.
Definition RepoModel.cpp:78
~RepoItem()
destruct all the childItems.
Definition RepoModel.cpp:61
RepoModel(QObject *parent=nullptr)
constructor
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Count how many file/folders are direct children of the given folder, through the abstraction of QMode...
Qt::ItemFlags flags(const QModelIndex &index) const override
information on the available interaction
static const QString & localChangedSt()
Mantid::API::ScriptRepository_sptr repo_ptr
pointer to the ScriptRepository
Definition RepoModel.h:166
QString filePath(const QModelIndex &index)
Return the operative system file path if it exists.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
header strings
QModelIndex download_index
Definition RepoModel.h:181
QFutureWatcher< QString > download_watcher
Definition RepoModel.h:180
QString author(const QModelIndex &index)
bool isDownloading(const QModelIndex &index) const
static const QString & remoteChangedSt()
QString repo_path
ScriptLocalRepository path, to be able to retrieve the absolute path.
Definition RepoModel.h:168
int columnCount(const QModelIndex &parent=QModelIndex()) const override
provide the number of the columns
const QString & fromStatus(Mantid::API::SCRIPTSTATUS status) const
auxiliary method to match the ScriptStatus to string
static const QString & updatedSt()
static const QString & bothChangedSt()
RepoItem * rootItem
pointer to the RepoItem root
Definition RepoModel.h:164
RepoItem * getParent(const QString &folder, QList< RepoItem * > &parents)
auxiliary method to help populating the model
QVariant data(const QModelIndex &index, int role) const override
access to the ScriptRepository data
void handleExceptions(const Mantid::API::ScriptRepoException &ex, const QString &title, bool showWarning=true) const
auxiliary method to deal with exceptions
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
change data
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
access to the index
QFuture< QString > download_threads
Definition RepoModel.h:179
QModelIndex parent(const QModelIndex &index) const override
access to parent
QString fileDescription(const QModelIndex &index)
Return the description of the file for a defined entry.
static const QString & localOnlySt()
void setupModelData(RepoItem *parent)
auxiliary method to populate the model
static const QString & downloadSt()
static const QString & remoteOnlySt()
~RepoModel() override
destructor
The ScriptRepository class is intended to be used mainly by users, who will want to share and downloa...
const char * what() const noexcept override
Returns the message string.
const std::string & systemError() const
Returns the error description with technical details on the origin and cause.
The ConfigService class provides a simple facade to access the Configuration functionality of the Man...
std::string getString(const std::string &keyName, bool pathAbsolute=true) const
Searches for a configuration property.
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
const QString LOCALCHANGED
Definition RepoModel.h:25
const QString REMOTECHANGED
Definition RepoModel.h:26
const QString BOTHUNCHANGED
Definition RepoModel.h:27
const QString REMOTEONLY
Definition RepoModel.h:23
const QString LOCALONLY
Definition RepoModel.h:24
const QString BOTHCHANGED
Definition RepoModel.h:28
const QString DOWNLOADST
Definition RepoModel.h:29
std::shared_ptr< ScriptRepository > ScriptRepository_sptr
shared pointer to the ScriptRepository base class
Mantid::Kernel::SingletonHolder< ScriptRepositoryFactoryImpl > ScriptRepositoryFactory
SCRIPTSTATUS
Represent the possible states for a given file:
Kernel::Logger g_log("DetermineSpinStateOrder")
Mantid::Kernel::SingletonHolder< ConfigServiceImpl > ConfigService
STL namespace.
Information about the files inside the repository.
bool auto_update
Whether the file is marked for auto-update.
bool directory
Flag to indicate whether the entry is a directory.