1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "OpenViewTask.h"
23 
24 #include <QApplication>
25 #include <QFileInfo>
26 
27 #include <U2Core/AppContext.h>
28 #include <U2Core/AppResources.h>
29 #include <U2Core/BaseDocumentFormats.h>
30 #include <U2Core/DocumentModel.h>
31 #include <U2Core/GHints.h>
32 #include <U2Core/GObject.h>
33 #include <U2Core/GObjectReference.h>
34 #include <U2Core/GObjectRelationRoles.h>
35 #include <U2Core/GObjectSelection.h>
36 #include <U2Core/GObjectTypes.h>
37 #include <U2Core/GObjectUtils.h>
38 #include <U2Core/IOAdapter.h>
39 #include <U2Core/L10n.h>
40 #include <U2Core/LoadDocumentTask.h>
41 #include <U2Core/LoadRemoteDocumentTask.h>
42 #include <U2Core/Log.h>
43 #include <U2Core/ProjectModel.h>
44 #include <U2Core/ResourceTracker.h>
45 #include <U2Core/U2SafePoints.h>
46 
47 #include <U2Gui/ObjectViewModel.h>
48 
49 namespace U2 {
50 
51 /* TRANSLATOR U2::LoadUnloadedDocumentTask */
52 
53 const int OpenViewTask::MAX_DOC_NUMBER_TO_OPEN_VIEWS = 5;
54 
55 //////////////////////////////////////////////////////////////////////////
56 // LoadUnloadedDocumentAndOpenViewTask
57 
LoadUnloadedDocumentAndOpenViewTask(Document * d)58 LoadUnloadedDocumentAndOpenViewTask::LoadUnloadedDocumentAndOpenViewTask(Document *d)
59     : Task("", TaskFlags_NR_FOSCOE | TaskFlag_MinimizeSubtaskErrorText | TaskFlag_CollectChildrenWarnings) {
60     loadUnloadedTask = new LoadUnloadedDocumentTask(d);
61     setUseDescriptionFromSubtask(true);
62 
63     setVerboseLogMode(true);
64     setTaskName(tr("Load document: '%1'").arg(d->getName()));
65 
66     addSubTask(loadUnloadedTask);
67 }
68 
createOpenViewTask(const MultiGSelection & ms)69 static Task *createOpenViewTask(const MultiGSelection &ms) {
70     QList<GObjectViewFactory *> fs = AppContext::getObjectViewFactoryRegistry()->getAllFactories();
71     QList<GObjectViewFactory *> ls;
72 
73     foreach (GObjectViewFactory *f, fs) {
74         // check if new view can be created
75         if (f->canCreateView(ms)) {
76             ls.append(f);
77         }
78     }
79 
80     if (ls.size() > 1) {
81         GObjectViewFactory *f = AppContext::getObjectViewFactoryRegistry()->getFactoryById(GObjectViewFactory::SIMPLE_TEXT_FACTORY);
82         if (ls.contains(f)) {
83             // ignore auxiliary text data
84             ls.removeAll(f);
85         }
86     }
87 
88     if (ls.size() == 1) {
89         GObjectViewFactory *f = ls.first();
90         Task *t = f->createViewTask(ms, true);
91         return t;
92     }
93     return nullptr;
94 }
95 
getDocument()96 Document *LoadUnloadedDocumentAndOpenViewTask::getDocument() {
97     return loadUnloadedTask->getDocument();
98 }
99 
onSubTaskFinished(Task * subTask)100 QList<Task *> LoadUnloadedDocumentAndOpenViewTask::onSubTaskFinished(Task *subTask) {
101     QList<Task *> res;
102     if (subTask != loadUnloadedTask || hasError() || isCanceled()) {
103         return res;
104     }
105 
106     // look if saved state can be loaded
107     Document *doc = loadUnloadedTask->getDocument();
108     assert(doc->isLoaded());
109 
110     res.append(new OpenViewTask(doc));
111     return res;
112 }
113 
114 //////////////////////////////////////////////////////////////////////////
115 // OpenViewTask
116 
OpenViewTask(Document * d)117 OpenViewTask::OpenViewTask(Document *d)
118     : Task("Open view", TaskFlags_NR_FOSCOE | TaskFlag_MinimizeSubtaskErrorText), doc(d) {
119     assert(doc != nullptr);
120     assert(doc->isLoaded());
121 }
122 
prepare()123 void OpenViewTask::prepare() {
124     QList<Task *> res;
125 
126     // if any of existing views has added an object from the document -> do not open new view
127     const QList<GObject *> &docObjects = doc->getObjects();
128     if (!GObjectViewUtils::findViewsWithAnyOfObjects(docObjects).isEmpty()) {
129         return;
130     }
131 
132     // try open new view
133     GObjectSelection os;
134     os.addToSelection(docObjects);
135     MultiGSelection ms;
136     ms.addSelection(&os);
137 
138     QList<GObjectViewState *> sl = GObjectViewUtils::selectStates(ms, AppContext::getProject()->getGObjectViewStates());
139     if (sl.size() == 1) {
140         GObjectViewState *state = sl.first();
141         SAFE_POINT_EXT(state, setError(tr("State is NULL")), );
142         GObjectViewFactory *f = AppContext::getObjectViewFactoryRegistry()->getFactoryById(state->getViewFactoryId());
143         SAFE_POINT_EXT(f, setError(tr("GObject factory is NULL")), );
144         res.append(f->createViewTask(state->getViewName(), state->getStateData()));
145     } else {
146         Task *openViewTask = createOpenViewTask(ms);
147         if (openViewTask != nullptr) {
148             openViewTask->setSubtaskProgressWeight(0);
149             res.append(openViewTask);
150         }
151     }
152 
153     if (res.isEmpty()) {
154         // no view can be opened -> check special case: loaded object contains annotations associated with sequence
155         // -> load sequence and open view for it;
156         foreach (GObject *obj, doc->findGObjectByType(GObjectTypes::ANNOTATION_TABLE)) {
157             QList<GObjectRelation> rels = obj->findRelatedObjectsByRole(ObjectRole_Sequence);
158             if (rels.isEmpty()) {
159                 continue;
160             }
161             const GObjectRelation &rel = rels.first();
162             Document *seqDoc = AppContext::getProject()->findDocumentByURL(rel.ref.docUrl);
163             if (seqDoc != nullptr) {
164                 if (seqDoc->isLoaded()) {  // try open sequence view
165                     GObject *seqObj = seqDoc->findGObjectByName(rel.ref.objName);
166                     if (seqObj != nullptr && seqObj->getGObjectType() == GObjectTypes::SEQUENCE) {
167                         GObjectSelection os2;
168                         os2.addToSelection(seqObj);
169                         MultiGSelection ms2;
170                         ms2.addSelection(&os2);
171                         Task *openViewTask = createOpenViewTask(ms2);
172                         if (openViewTask != nullptr) {
173                             openViewTask->setSubtaskProgressWeight(0);
174                             res.append(openViewTask);
175                         }
176                     }
177                 } else {  // try load doc and open sequence view
178                     AppContext::getTaskScheduler()->registerTopLevelTask(new LoadUnloadedDocumentAndOpenViewTask(seqDoc));
179                 }
180             }
181             if (!res.isEmpty()) {  // one view is ok
182                 break;
183             }
184         }
185 
186         if (res.isEmpty()) {
187             // no view can be opened -> check another special cases: loaded object contains
188             // 1. assemblies with their references
189             // 2. multiple chromatogram alignment with a reference
190             // -> load assemblies/mca and their references and open view for the first object;
191             QList<GObject *> objectsToOpen;
192 
193             objectsToOpen << doc->findGObjectByType(GObjectTypes::ASSEMBLY);
194             if (objectsToOpen.isEmpty()) {
195                 objectsToOpen << doc->findGObjectByType(GObjectTypes::MULTIPLE_CHROMATOGRAM_ALIGNMENT);
196             }
197 
198             if (!objectsToOpen.isEmpty()) {
199                 GObjectSelection os2;
200                 os2.addToSelection(objectsToOpen.first());
201                 MultiGSelection ms2;
202                 ms2.addSelection(&os2);
203 
204                 Task *openViewTask = createOpenViewTask(ms2);
205                 if (openViewTask != nullptr) {
206                     openViewTask->setSubtaskProgressWeight(0);
207                     res.append(openViewTask);
208                 }
209             }
210         }
211     }
212 
213     foreach (Task *task, res) {
214         addSubTask(task);
215     }
216 }
217 
218 //////////////////////////////////////////////////////////////////////////
219 
LoadRemoteDocumentAndAddToProjectTask(const QString & accId,const QString & dbName)220 LoadRemoteDocumentAndAddToProjectTask::LoadRemoteDocumentAndAddToProjectTask(const QString &accId, const QString &dbName)
221     : Task(tr("Load remote document and add to project"), TaskFlags_NR_FOSCOE | TaskFlag_MinimizeSubtaskErrorText),
222       mode(LoadRemoteDocumentMode_OpenView), loadRemoteDocTask(nullptr) {
223     accNumber = accId;
224     databaseName = dbName;
225 }
226 
LoadRemoteDocumentAndAddToProjectTask(const GUrl & url)227 LoadRemoteDocumentAndAddToProjectTask::LoadRemoteDocumentAndAddToProjectTask(const GUrl &url)
228     : Task(tr("Load remote document and add to project"), TaskFlags_NR_FOSCOE | TaskFlag_MinimizeSubtaskErrorText),
229       mode(LoadRemoteDocumentMode_OpenView), loadRemoteDocTask(nullptr) {
230     docUrl = url;
231 }
232 
LoadRemoteDocumentAndAddToProjectTask(const QString & accId,const QString & dbName,const QString & fp,const QString & format,const QVariantMap & hints,LoadRemoteDocumentMode mode)233 LoadRemoteDocumentAndAddToProjectTask::LoadRemoteDocumentAndAddToProjectTask(const QString &accId, const QString &dbName, const QString &fp, const QString &format, const QVariantMap &hints, LoadRemoteDocumentMode mode)
234     : Task(tr("Load remote document and add to project"), TaskFlags_NR_FOSCOE | TaskFlag_MinimizeSubtaskErrorText),
235       accNumber(accId), databaseName(dbName), fileFormat(format), fullpath(fp), hints(hints), mode(mode), loadRemoteDocTask(nullptr) {
236     if (mode == LoadRemoteDocumentMode_LoadOnly) {
237         setReportingSupported(true);
238         setReportingEnabled(true);
239         setTaskName(tr("Load remote document"));
240     }
241 }
242 
prepare()243 void LoadRemoteDocumentAndAddToProjectTask::prepare() {
244     if (docUrl.isEmpty()) {
245         loadRemoteDocTask = new LoadRemoteDocumentTask(accNumber, databaseName, fullpath, fileFormat, hints);
246     } else {
247         loadRemoteDocTask = new LoadRemoteDocumentTask(docUrl);
248     }
249     addSubTask(loadRemoteDocTask);
250 }
251 
252 namespace {
createLoadedDocTask(Document * loadedDoc,bool openView)253 Task *createLoadedDocTask(Document *loadedDoc, bool openView) {
254     if (loadedDoc->isLoaded() && openView) {
255         return new OpenViewTask(loadedDoc);
256     }
257     if (!loadedDoc->isLoaded() && openView) {
258         return new LoadUnloadedDocumentAndOpenViewTask(loadedDoc);
259     }
260     if (!loadedDoc->isLoaded() && !openView) {
261         return new LoadUnloadedDocumentTask(loadedDoc);
262     }
263     return nullptr;
264 }
265 }  // namespace
266 
onSubTaskFinished(Task * subTask)267 QList<Task *> LoadRemoteDocumentAndAddToProjectTask::onSubTaskFinished(Task *subTask) {
268     QList<Task *> subTasks;
269     if (subTask->hasError()) {
270         return subTasks;
271     }
272 
273     if (subTask->isCanceled()) {
274         return subTasks;
275     }
276 
277     if (subTask == loadRemoteDocTask) {
278         if (mode == LoadRemoteDocumentMode_LoadOnly) {
279             return subTasks;
280         }
281         // hack for handling errors with http requests with bad resource id
282         Document *d = loadRemoteDocTask->getDocument();
283         if (d->getDocumentFormatId() == BaseDocumentFormats::PLAIN_TEXT) {
284             setError(tr("Cannot find %1 in %2 database").arg(accNumber).arg(databaseName));
285             // try to delete file with response that was created
286             QFile::remove(d->getURLString());
287             // and remove it from downloaded cache
288             RecentlyDownloadedCache *cache = AppContext::getRecentlyDownloadedCache();
289             if (cache != nullptr) {
290                 cache->remove(d->getURLString());
291             }
292             return subTasks;
293         }
294 
295         QString fullPath = loadRemoteDocTask->getLocalUrl();
296         Project *proj = AppContext::getProject();
297         if (proj == nullptr) {
298             QVariantMap hints;
299             hints[ProjectLoaderHint_LoadWithoutView] = mode != LoadRemoteDocumentMode_OpenView;
300             Task *openWithProjectTask = AppContext::getProjectLoader()->openWithProjectTask(fullPath, hints);
301             if (openWithProjectTask != nullptr) {
302                 subTasks.append(openWithProjectTask);
303             }
304         } else {
305             Document *doc = loadRemoteDocTask->getDocument();
306             SAFE_POINT(doc != nullptr, "loadRemoteDocTask->takeDocument() returns NULL!", subTasks);
307             QString url = doc->getURLString();
308             Document *loadedDoc = proj->findDocumentByURL(url);
309             if (loadedDoc != nullptr) {
310                 Task *task = createLoadedDocTask(loadedDoc, mode == LoadRemoteDocumentMode_OpenView);
311                 if (nullptr != task) {
312                     subTasks.append(task);
313                 }
314             } else {
315                 // Add document to project
316                 doc = loadRemoteDocTask->takeDocument();
317                 SAFE_POINT(doc != nullptr, "loadRemoteDocTask->takeDocument() returns NULL!", subTasks);
318                 subTasks.append(new AddDocumentTask(doc));
319                 if (mode == LoadRemoteDocumentMode_OpenView) {
320                     subTasks.append(new LoadUnloadedDocumentAndOpenViewTask(doc));
321                 } else {
322                     subTasks.append(new LoadUnloadedDocumentTask(doc));
323                 }
324             }
325         }
326     }
327 
328     return subTasks;
329 }
330 
generateReport() const331 QString LoadRemoteDocumentAndAddToProjectTask::generateReport() const {
332     // Note: reporting is enabled only for db + accession mode.
333     if (hasError()) {
334         return tr("Failed to download %1 from %2. Error: %3").arg(accNumber).arg(databaseName).arg(getError());
335     }
336     if (isCanceled()) {
337         return QString();
338     }
339     QString url = loadRemoteDocTask->getLocalUrl();
340     return tr("Document was successfully downloaded: [%1, %2] -> <a href='%3'>%4</a>")
341         .arg(databaseName)
342         .arg(accNumber)
343         .arg(url)
344         .arg(url);
345 }
346 
AddDocumentAndOpenViewTask(Document * doc,const AddDocumentTaskConfig & conf)347 AddDocumentAndOpenViewTask::AddDocumentAndOpenViewTask(Document *doc, const AddDocumentTaskConfig &conf)
348     : Task(tr("Opening view for document: 'NONAME'"), TaskFlags_NR_FOSE_COSC | TaskFlag_CollectChildrenWarnings) {
349     if (doc != nullptr) {
350         GUrl url = doc->getURL();
351         setTaskName(tr("Opening view for document: %1").arg(url.fileName()));
352     } else {
353         setError(tr("Provided document is NULL"));
354         return;
355     }
356     setMaxParallelSubtasks(1);
357     addSubTask(new AddDocumentTask(doc, conf));
358 }
359 
AddDocumentAndOpenViewTask(DocumentProviderTask * dp,const AddDocumentTaskConfig & conf)360 AddDocumentAndOpenViewTask::AddDocumentAndOpenViewTask(DocumentProviderTask *dp, const AddDocumentTaskConfig &conf)
361     : Task(tr("Opening view for document: 'NONAME'"), TaskFlags_NR_FOSE_COSC | TaskFlag_CollectChildrenWarnings) {
362     if (dp != nullptr) {
363         setTaskName(tr("Opening view for document: %1").arg(dp->getDocumentDescription()));
364     } else {
365         setError(tr("Document provider is NULL"));
366         return;
367     }
368     setMaxParallelSubtasks(1);
369     addSubTask(new AddDocumentTask(dp, conf));
370 }
371 
onSubTaskFinished(Task * t)372 QList<Task *> AddDocumentAndOpenViewTask::onSubTaskFinished(Task *t) {
373     QList<Task *> res;
374     AddDocumentTask *addTask = qobject_cast<AddDocumentTask *>(t);
375     if (addTask != nullptr && !addTask->getStateInfo().isCoR()) {
376         Document *doc = addTask->getDocument();
377         assert(doc != nullptr);
378         res << new LoadUnloadedDocumentAndOpenViewTask(doc);
379     }
380     return res;
381 }
382 
383 }  // namespace U2
384