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 "DocumentModel.h"
23 
24 #include <QCoreApplication>
25 #include <QFileInfo>
26 #include <QScopedPointer>
27 #include <QThread>
28 
29 #include <U2Core/AppContext.h>
30 #include <U2Core/BaseDocumentFormats.h>
31 #include <U2Core/DeleteObjectsTask.h>
32 #include <U2Core/GHints.h>
33 #include <U2Core/GObject.h>
34 #include <U2Core/GObjectUtils.h>
35 #include <U2Core/GUrlUtils.h>
36 #include <U2Core/IOAdapter.h>
37 #include <U2Core/L10n.h>
38 #include <U2Core/Log.h>
39 #include <U2Core/ScriptEngine.h>
40 #include <U2Core/U2DbiUtils.h>
41 #include <U2Core/U2ObjectDbi.h>
42 #include <U2Core/U2OpStatusUtils.h>
43 #include <U2Core/U2SafePoints.h>
44 #include <U2Core/UnloadedObject.h>
45 
46 namespace U2 {
47 
48 const QString DocumentFormat::DBI_REF_HINT("dbi_alias");
49 const QString DocumentFormat::DBI_FOLDER_HINT("dbi_folder");
50 const QString DocumentFormat::DEEP_COPY_OBJECT("deep_copy_object");
51 const QString DocumentFormat::STRONG_FORMAT_ACCORDANCE("strong_format_accordance");
52 const QString DocumentMimeData::MIME_TYPE("application/x-ugene-document-mime");
53 
54 const int DocumentFormat::READ_BUFF_SIZE = 4194304;  // 4Mb optimal buffer size for reading from network drives
55 
DocumentFormat(QObject * p,const DocumentFormatId & _id,DocumentFormatFlags _flags,const QStringList & fileExts)56 DocumentFormat::DocumentFormat(QObject *p, const DocumentFormatId &_id, DocumentFormatFlags _flags, const QStringList &fileExts)
57     : QObject(p),
58       id(_id),
59       formatFlags(_flags),
60       fileExtensions(fileExts) {
61 }
62 
createNewLoadedDocument(IOAdapterFactory * iof,const GUrl & url,U2OpStatus & os,const QVariantMap & hints)63 Document *DocumentFormat::createNewLoadedDocument(IOAdapterFactory *iof, const GUrl &url, U2OpStatus &os, const QVariantMap &hints) {
64     U2DbiRef tmpDbiRef = fetchDbiRef(hints, os);
65     CHECK_OP(os, nullptr);
66 
67     Document *doc = new Document(this, iof, url, tmpDbiRef, QList<UnloadedObjectInfo>(), hints, QString());
68     doc->setLoaded(true);
69     doc->setDocumentOwnsDbiResources(true);
70     doc->setModificationTrack(!checkFlags(DocumentFormatFlag_DirectWriteOperations));
71     return doc;
72 }
73 
createNewUnloadedDocument(IOAdapterFactory * iof,const GUrl & url,U2OpStatus & os,const QVariantMap & hints,const QList<UnloadedObjectInfo> & info,const QString & instanceModLockDesc)74 Document *DocumentFormat::createNewUnloadedDocument(IOAdapterFactory *iof, const GUrl &url, U2OpStatus &os, const QVariantMap &hints, const QList<UnloadedObjectInfo> &info, const QString &instanceModLockDesc) {
75     Q_UNUSED(os);
76     U2DbiRef dbiRef = (hints[DocumentFormat::DBI_REF_HINT]).value<U2DbiRef>();
77     Document *doc = new Document(this, iof, url, dbiRef, info, hints, instanceModLockDesc);
78     doc->setModificationTrack(!checkFlags(DocumentFormatFlag_DirectWriteOperations));
79     return doc;
80 }
81 
loadDocument(IOAdapterFactory * iof,const GUrl & url,const QVariantMap & hints,U2OpStatus & os)82 Document *DocumentFormat::loadDocument(IOAdapterFactory *iof, const GUrl &url, const QVariantMap &hints, U2OpStatus &os) {
83     QScopedPointer<IOAdapter> io(iof->createIOAdapter());
84     if (!io->open(url, IOAdapterMode_Read)) {
85         os.setError(L10N::errorOpeningFileRead(url));
86         return nullptr;
87     }
88 
89     Document *res = nullptr;
90 
91     U2DbiRef dbiRef = fetchDbiRef(hints, os);
92     CHECK_OP(os, nullptr);
93 
94     if (dbiRef.isValid()) {
95         DbiConnection con(dbiRef, os);
96         CHECK_OP(os, nullptr);
97 
98         res = loadDocument(io.data(), dbiRef, hints, os);
99         CHECK_OP(os, nullptr);
100     } else {
101         res = loadDocument(io.data(), U2DbiRef(), hints, os);
102     }
103     return res;
104 }
105 
fetchDbiRef(const QVariantMap & hints,U2OpStatus & os) const106 U2DbiRef DocumentFormat::fetchDbiRef(const QVariantMap &hints, U2OpStatus &os) const {
107     if (hints.contains(DBI_REF_HINT)) {
108         return hints.value(DBI_REF_HINT).value<U2DbiRef>();
109     } else {
110         return AppContext::getDbiRegistry()->getSessionTmpDbiRef(os);
111     }
112 }
113 
loadSequence(IOAdapter *,U2OpStatus & os)114 DNASequence *DocumentFormat::loadSequence(IOAdapter *, U2OpStatus &os) {
115     os.setError("This document format does not support streaming reading mode");
116     return nullptr;
117 }
118 
storeDocument(Document *,IOAdapter *,U2OpStatus & os)119 void DocumentFormat::storeDocument(Document *, IOAdapter *, U2OpStatus &os) {
120     assert(0);
121     os.setError(tr("Writing is not supported for this format (%1). Feel free to send a feature request though.").arg(formatName));
122 }
123 
storeDocument(Document * doc,U2OpStatus & os,IOAdapterFactory * iof,const GUrl & newDocURL)124 void DocumentFormat::storeDocument(Document *doc, U2OpStatus &os, IOAdapterFactory *iof, const GUrl &newDocURL) {
125     SAFE_POINT_EXT(formatFlags.testFlag(DocumentFormatFlag_SupportWriting),
126                    os.setError(tr("Writing is not supported for this format (%1). Feel free to send a feature request though.").arg(formatName)), );
127 
128     assert(doc->getDocumentModLock(DocumentModLock_FORMAT_AS_INSTANCE) == nullptr);
129     if (iof == nullptr) {
130         iof = doc->getIOAdapterFactory();
131     }
132 
133     // prepare URL
134     GUrl url = newDocURL.isEmpty() ? doc->getURL() : newDocURL;
135     if (url.isLocalFile()) {
136         QString error;
137         QString res = GUrlUtils::prepareFileLocation(url.getURLString(), os);
138         CHECK_OP(os, );
139         Q_UNUSED(res);
140         assert(res == url.getURLString());  // ensure that GUrls are always canonical
141     }
142 
143     QScopedPointer<IOAdapter> io(iof->createIOAdapter());
144     if (!io->open(url, IOAdapterMode_Write)) {
145         os.setError(L10N::errorOpeningFileWrite(url));
146         return;
147     }
148 
149     storeDocument(doc, io.data(), os);
150 }
151 
checkConstraints(const DocumentFormatConstraints & c) const152 bool DocumentFormat::checkConstraints(const DocumentFormatConstraints &c) const {
153     assert(!supportedObjectTypes.isEmpty());  // extra check for DF state validation
154 
155     if (!checkFlags(c.flagsToSupport)) {
156         return false;  // requested to support writing or streaming but doesn't
157     }
158 
159     if ((int(c.flagsToExclude) & int(formatFlags)) != 0) {
160         return false;  // filtered by exclude flags
161     }
162 
163     if (c.formatsToExclude.contains(id)) {
164         return false;  // format is explicetely excluded
165     }
166 
167     if (c.checkRawData && checkRawData(c.rawData).score < c.minDataCheckResult) {
168         return false;  // raw data is not matched
169     }
170 
171     bool areTypesSatisfied = !c.allowPartialTypeMapping;
172     foreach (const GObjectType &objType, c.supportedObjectTypes) {
173         if (c.allowPartialTypeMapping && supportedObjectTypes.contains(objType)) {  // at least one type is supported
174             areTypesSatisfied = true;
175             break;
176         } else if (!c.allowPartialTypeMapping && !supportedObjectTypes.contains(objType)) {  // the object type is not in the supported list
177             areTypesSatisfied = false;
178             break;
179         }
180     }
181 
182     return areTypesSatisfied;
183 }
184 
storeEntry(IOAdapter *,const QMap<GObjectType,QList<GObject * >> &,U2OpStatus & os)185 void DocumentFormat::storeEntry(IOAdapter *, const QMap<GObjectType, QList<GObject *>> &, U2OpStatus &os) {
186     os.setError("This document format does not support streaming mode");
187 }
188 
getRadioButtonText() const189 QString DocumentFormat::getRadioButtonText() const {
190     return QString();
191 }
192 
isObjectOpSupported(const Document * d,DocObjectOp op,GObjectType t) const193 bool DocumentFormat::isObjectOpSupported(const Document *d, DocObjectOp op, GObjectType t) const {
194     assert(d->getDocumentFormat() == this);
195 
196     if (!supportedObjectTypes.contains(t)) {
197         return false;
198     }
199 
200     if (!checkFlags(DocumentFormatFlag_SupportWriting)) {
201         return false;
202     }
203 
204     if (op == DocObjectOp_Add) {
205         int nObjects = d->getObjects().size();
206         if (nObjects != 0 && checkFlags(DocumentFormatFlag_SingleObjectFormat)) {
207             return false;
208         }
209     }
210     return true;
211 }
212 
213 //////////////////////////////////////////////////////////////////////////
214 /// Document
215 const QString Document::UNLOAD_LOCK_NAME = "unload_document_lock";
216 
Document(DocumentFormat * _df,IOAdapterFactory * _io,const GUrl & _url,const U2DbiRef & _dbiRef,const QList<UnloadedObjectInfo> & unloadedObjects,const QVariantMap & hints,const QString & instanceModLockDesc)217 Document::Document(DocumentFormat *_df, IOAdapterFactory *_io, const GUrl &_url, const U2DbiRef &_dbiRef, const QList<UnloadedObjectInfo> &unloadedObjects, const QVariantMap &hints, const QString &instanceModLockDesc)
218     : StateLockableTreeItem(), df(_df), io(_io), url(_url), dbiRef(_dbiRef) {
219     documentOwnsDbiResources = false;
220 
221     ctxState = new GHintsDefaultImpl(hints);
222     name = url.fileName();
223 
224     std::fill(modLocks, modLocks + DocumentModLock_NUM_LOCKS, (StateLock *)nullptr);
225 
226     loadStateChangeMode = true;
227     addUnloadedObjects(unloadedObjects);
228     loadStateChangeMode = false;
229 
230     initModLocks(instanceModLockDesc, false);
231     checkUnloadedState();
232     //    assert(!isModified());
233 }
234 
Document(DocumentFormat * _df,IOAdapterFactory * _io,const GUrl & _url,const U2DbiRef & _dbiRef,const QList<GObject * > & _objects,const QVariantMap & hints,const QString & instanceModLockDesc)235 Document::Document(DocumentFormat *_df, IOAdapterFactory *_io, const GUrl &_url, const U2DbiRef &_dbiRef, const QList<GObject *> &_objects, const QVariantMap &hints, const QString &instanceModLockDesc)
236     : StateLockableTreeItem(), df(_df), io(_io), url(_url), dbiRef(_dbiRef) {
237     documentOwnsDbiResources = true;
238 
239     ctxState = new GHintsDefaultImpl(hints);
240     name = url.fileName();
241 
242     loadStateChangeMode = true;
243     std::fill(modLocks, modLocks + DocumentModLock_NUM_LOCKS, (StateLock *)nullptr);
244     foreach (GObject *o, _objects) {
245         _addObject(o);
246     }
247     loadStateChangeMode = false;
248 
249     initModLocks(instanceModLockDesc, true);
250 
251     checkLoadedState();
252     assert(!isModified());
253 }
254 
getSimpleCopy(DocumentFormat * df,IOAdapterFactory * io,const GUrl & url) const255 Document *Document::getSimpleCopy(DocumentFormat *df, IOAdapterFactory *io, const GUrl &url) const {
256     Document *result = new Document(df, io, url, this->dbiRef, QList<GObject *>(), this->getGHintsMap());
257     result->objects = this->objects;
258     result->documentOwnsDbiResources = false;
259 
260     return result;
261 }
262 
~Document()263 Document::~Document() {
264     for (int i = 0; i < DocumentModLock_NUM_LOCKS; i++) {
265         StateLock *sl = modLocks[i];
266         if (sl != nullptr) {
267             unlockState(sl);
268             delete sl;
269         }
270     }
271 
272     if (isDocumentOwnsDbiResources() && dbiRef.isValid()) {
273         removeObjectsDataFromDbi(objects);
274     }
275 
276     delete ctxState;
277 }
278 
getObjectById(const U2DataId & id) const279 GObject *Document::getObjectById(const U2DataId &id) const {
280     return id2Object.value(id, nullptr);
281 }
282 
setObjectsInUse(const QSet<U2DataId> & objs)283 void Document::setObjectsInUse(const QSet<U2DataId> &objs) {
284     objectsInUse = objs;
285 }
286 
addObject(GObject * obj)287 void Document::addObject(GObject *obj) {
288     SAFE_POINT(obj != nullptr, "Object is NULL", );
289     SAFE_POINT(obj->getDocument() == nullptr, "Object already belongs to some document", );
290     SAFE_POINT(df->isObjectOpSupported(this, DocumentFormat::DocObjectOp_Add, obj->getGObjectType()), "Document format doesn't support new objects adding", );
291     SAFE_POINT(isLoaded(), "The destination document is not loaded", );
292     SAFE_POINT(obj->getGObjectType() != GObjectTypes::UNLOADED, "Object is not loaded", );
293 
294     _addObject(obj);
295 }
296 
_addObjectToHierarchy(GObject * obj)297 void Document::_addObjectToHierarchy(GObject *obj) {
298     SAFE_POINT(obj != nullptr, "Object is NULL", );
299     obj->setParentStateLockItem(this);
300     obj->setGHints(new ModTrackHints(this, obj->getGHintsMap(), true));
301     obj->setModified(false);
302     objects.append(obj);
303     id2Object.insert(obj->getEntityRef().entityId, obj);
304 }
305 
_addObject(GObject * obj)306 void Document::_addObject(GObject *obj) {
307     SAFE_POINT(obj != nullptr, "Object is NULL", );
308     _addObjectToHierarchy(obj);
309     assert(objects.size() == getChildItems().size());
310     emit si_objectAdded(obj);
311 }
312 
removeObject(GObject * obj,DocumentObjectRemovalMode removalMode)313 bool Document::removeObject(GObject *obj, DocumentObjectRemovalMode removalMode) {
314     SAFE_POINT(df->isObjectOpSupported(this, DocumentFormat::DocObjectOp_Remove, obj->getGObjectType()), "Unsupported format operation", false);
315 
316     switch (removalMode) {
317         case DocumentObjectRemovalMode_Deallocate:
318             return _removeObject(obj, true);
319         case DocumentObjectRemovalMode_OnlyNotify:
320             emit si_objectRemoved(obj);
321             break;
322         case DocumentObjectRemovalMode_Release:
323             return _removeObject(obj, false);
324     }
325 
326     return true;
327 }
328 
_removeObject(GObject * obj,bool deleteObjects)329 bool Document::_removeObject(GObject *obj, bool deleteObjects) {
330     SAFE_POINT(obj->getParentStateLockItem() == this, "Invalid parent document!", false);
331 
332     if (obj->entityRef.isValid() && objectsInUse.contains(obj->getEntityRef().entityId)) {
333         return false;
334     }
335 
336     obj->setModified(false);
337 
338     obj->setParentStateLockItem(nullptr);
339     objects.removeOne(obj);
340     id2Object.remove(obj->getEntityRef().entityId);
341     obj->setGHints(new GHintsDefaultImpl(obj->getGHintsMap()));
342 
343     SAFE_POINT(objects.size() == getChildItems().size(), "Invalid child object count!", false);
344 
345     emit si_objectRemoved(obj);
346 
347     if (deleteObjects) {
348         removeObjectsDataFromDbi(QList<GObject *>() << obj);
349         delete obj;
350     }
351     return true;
352 }
353 
makeClean()354 void Document::makeClean() {
355     if (!isTreeItemModified()) {
356         return;
357     }
358     setModified(false);
359     foreach (GObject *obj, objects) {
360         obj->setModified(false);
361     }
362 }
363 
setModificationTrack(bool track)364 void Document::setModificationTrack(bool track) {
365     if (df != nullptr && df->checkFlags(DocumentFormatFlag_DirectWriteOperations)) {
366         StateLockableTreeItem::setModificationTrack(false);
367     } else {
368         StateLockableTreeItem::setModificationTrack(track);
369     }
370 }
371 
findGObjectByNameInDb(const QString & name) const372 GObject *Document::findGObjectByNameInDb(const QString &name) const {
373     U2OpStatusImpl os;
374     DbiConnection con(dbiRef, os);
375     SAFE_POINT_OP(os, nullptr);
376 
377     U2ObjectDbi *oDbi = con.dbi->getObjectDbi();
378     SAFE_POINT(nullptr != oDbi, "Invalid database connection", nullptr);
379 
380     QScopedPointer<U2DbiIterator<U2DataId>> iter(oDbi->getObjectsByVisualName(name, U2Type::Unknown, os));
381     SAFE_POINT_OP(os, nullptr);
382 
383     while (iter->hasNext()) {
384         const U2DataId objId = iter->next();
385         GObject *obj = getObjectById(objId);
386         if (nullptr != obj) {
387             return obj;
388         }
389     }
390     return nullptr;
391 }
392 
findGObjectByNameInMem(const QString & name) const393 GObject *Document::findGObjectByNameInMem(const QString &name) const {
394     foreach (GObject *obj, objects) {
395         if (obj->getGObjectName() == name) {
396             return obj;
397         }
398     }
399     return nullptr;
400 }
401 
findGObjectByName(const QString & name) const402 GObject *Document::findGObjectByName(const QString &name) const {
403     return isLoaded() ? findGObjectByNameInDb(name) : findGObjectByNameInMem(name);
404 }
405 
findGObjectByType(GObjectType t,UnloadedObjectFilter f) const406 QList<GObject *> Document::findGObjectByType(GObjectType t, UnloadedObjectFilter f) const {
407     return GObjectUtils::select(objects, t, f);
408 }
409 
checkUnloadedState() const410 void Document::checkUnloadedState() const {
411 #ifdef _DEBUG
412     assert(!isLoaded());
413     bool hasNoLoadedObjects = findGObjectByType(GObjectTypes::UNLOADED, UOF_LoadedAndUnloaded).count() == objects.count();
414     assert(hasNoLoadedObjects);
415     if (!df->checkFlags(DocumentFormatFlag_AllowDuplicateNames)) {
416         checkUniqueObjectNames();
417     }
418 #endif
419 }
420 
checkUniqueObjectNames() const421 void Document::checkUniqueObjectNames() const {
422 #ifdef _DEBUG
423     QVariantMap hints = getGHintsMap();
424     bool dontCheckUniqueNames = hints.value(DocumentReadingMode_DontMakeUniqueNames, false).toBool();
425     if (dontCheckUniqueNames) {
426         return;
427     }
428     QSet<QString> names;
429     foreach (GObject *o, objects) {
430         const QString &name = o->getGObjectName();
431         assert(!names.contains(name));
432         names.insert(name);
433     }
434 #endif
435 }
checkLoadedState() const436 void Document::checkLoadedState() const {
437 #ifdef _DEBUG
438     assert(isLoaded());
439     bool hasNoUnloadedObjects = findGObjectByType(GObjectTypes::UNLOADED, UOF_LoadedAndUnloaded).isEmpty();
440     assert(hasNoUnloadedObjects);
441     if (!df->checkFlags(DocumentFormatFlag_AllowDuplicateNames)) {
442         checkUniqueObjectNames();
443     }
444 #endif
445 }
446 
447 class DocumentChildEventsHelper {
448 public:
DocumentChildEventsHelper(Document * doc)449     DocumentChildEventsHelper(Document *doc)
450         : doc(doc) {
451         if (nullptr != doc) {
452             doc->d_ptr->receiveChildEvents = false;
453         }
454     }
455 
~DocumentChildEventsHelper()456     ~DocumentChildEventsHelper() {
457         if (nullptr != doc) {
458             doc->d_ptr->receiveChildEvents = true;
459         }
460     }
461 
462 private:
463     Document *doc;
464 };
465 
loadFrom(Document * sourceDoc)466 void Document::loadFrom(Document *sourceDoc) {
467     SAFE_POINT(!isLoaded(), QString("Document is already loaded: ").arg(getURLString()), )
468 
469     DocumentChildEventsHelper eventsHelper(this);
470     Q_UNUSED(eventsHelper);
471 
472     sourceDoc->checkLoadedState();
473     checkUnloadedState();
474 
475     loadStateChangeMode = true;
476 
477     QMap<QString, UnloadedObjectInfo> unloadedInfo;
478 
479     foreach (GObject *obj, objects) {  // remove all unloaded objects but save hints
480         unloadedInfo.insert(obj->getGObjectName(), UnloadedObjectInfo(obj));
481         _removeObject(obj, documentOwnsDbiResources);
482     }
483 
484     ctxState->setAll(sourceDoc->getGHints()->getMap());
485 
486     lastUpdateTime = sourceDoc->getLastUpdateTime();
487 
488     // copy instance mod-locks if any
489     StateLock *mLock = modLocks[DocumentModLock_FORMAT_AS_INSTANCE];
490     StateLock *dLock = sourceDoc->modLocks[DocumentModLock_FORMAT_AS_INSTANCE];
491     if (mLock != nullptr) {
492         if (dLock == nullptr) {
493             unlockState(mLock);
494             delete mLock;
495             modLocks[DocumentModLock_FORMAT_AS_INSTANCE] = nullptr;
496         } else {
497             mLock->setUserDesc(dLock->getUserDesc());
498         }
499     } else if (dLock != nullptr) {
500         modLocks[DocumentModLock_FORMAT_AS_INSTANCE] = new StateLock(dLock->getUserDesc());
501         lockState(modLocks[DocumentModLock_FORMAT_AS_INSTANCE]);
502     }
503 
504     dbiRef = sourceDoc->dbiRef;
505     documentOwnsDbiResources = sourceDoc->isDocumentOwnsDbiResources();
506     trackModifications = sourceDoc->isModificationTracked();
507     sourceDoc->dbiRef = U2DbiRef();
508     sourceDoc->setDocumentOwnsDbiResources(false);
509 
510     QList<GObject *> sourceObjects = sourceDoc->getObjects();
511     sourceDoc->unload(false);
512     for (GObject *obj: qAsConst(sourceObjects)) {
513         // TODO: add constrains to ObjectRelations!!
514         UnloadedObjectInfo info = unloadedInfo.value(obj->getGObjectName());
515         if (info.type == obj->getGObjectType()) {
516             QVariantMap mergedHints = obj->getGHintsMap();
517             foreach (const QString &k, info.hints.keys()) {
518                 if (!mergedHints.contains(k)) {
519                     mergedHints.insert(k, info.hints.value(k));
520                 } else if (k == GObjectHint_RelatedObjects) {
521                     mergedHints[k] = info.hints[k];
522                 }
523             }
524             obj->getGHints()->setMap(mergedHints);
525         }
526         _addObject(obj);
527     }
528     setLoaded(true);
529 
530     // TODO: rebind local objects relations if url!=d.url
531 
532     loadStateChangeMode = false;
533 
534     checkLoadedState();
535 }
536 
setLoaded(bool v)537 void Document::setLoaded(bool v) {
538     if (v == isLoaded()) {
539         return;
540     }
541     StateLock *l = modLocks[DocumentModLock_UNLOADED_STATE];
542     if (v) {
543         unlockState(l);
544         modLocks[DocumentModLock_UNLOADED_STATE] = nullptr;
545         delete l;
546         checkLoadedState();
547     } else {
548         assert(l == nullptr);
549         l = new StateLock(tr("Document is not loaded"));
550         modLocks[DocumentModLock_UNLOADED_STATE] = l;
551         lockState(l);
552         checkUnloadedState();
553     }
554     emit si_loadedStateChanged();
555 }
556 
initModLocks(const QString & instanceModLockDesc,bool loaded)557 void Document::initModLocks(const QString &instanceModLockDesc, bool loaded) {
558     setLoaded(loaded);
559 
560     // must be locked for modifications if io-adapter does not support writes
561     if (!io->isIOModeSupported(IOAdapterMode_Write)) {
562         modLocks[DocumentModLock_IO] = new StateLock(tr("IO adapter does not support write operation"));
563         lockState(modLocks[DocumentModLock_IO]);
564     }
565 
566     // must be locked for modifications if not document format does not support writes
567     if (!df->checkFlags(DocumentFormatFlag_SupportWriting)) {
568         modLocks[DocumentModLock_FORMAT_AS_CLASS] = new StateLock(tr("No write support for document format"));
569         lockState(modLocks[DocumentModLock_FORMAT_AS_CLASS]);
570     }
571 
572     if (!instanceModLockDesc.isEmpty()) {
573         modLocks[DocumentModLock_FORMAT_AS_INSTANCE] = new StateLock(instanceModLockDesc);
574         lockState(modLocks[DocumentModLock_FORMAT_AS_INSTANCE]);
575     }
576 }
577 
setName(const QString & newName)578 void Document::setName(const QString &newName) {
579     if (name == newName) {
580         return;
581     }
582     name = newName;
583     emit si_nameChanged();
584 }
585 
setURL(const GUrl & newUrl)586 void Document::setURL(const GUrl &newUrl) {
587     assert(!isLoaded() || !isStateLocked());
588     if (url == newUrl) {
589         return;
590     }
591     url = newUrl;
592     emit si_urlChanged();
593 }
594 
checkConstraints(const Document::Constraints & c) const595 bool Document::checkConstraints(const Document::Constraints &c) const {
596     if (c.stateLocked != TriState_Unknown) {
597         if (c.stateLocked == TriState_No && isStateLocked()) {
598             return false;
599         }
600         if (c.stateLocked == TriState_Yes && !isStateLocked()) {
601             return false;
602         }
603     }
604 
605     if (!c.formats.isEmpty()) {
606         bool found = false;
607         foreach (DocumentFormatId f, c.formats) {
608             if (df->getFormatId() == f) {
609                 found = true;
610                 break;
611             }
612         }
613         if (!found) {
614             return false;
615         }
616     }
617 
618     foreach (DocumentModLock l, c.notAllowedStateLocks) {
619         if (modLocks[l] != nullptr) {
620             return false;
621         }
622     }
623 
624     if (!c.objectTypeToAdd.isNull() && !df->isObjectOpSupported(this, DocumentFormat::DocObjectOp_Add, c.objectTypeToAdd)) {
625         return false;
626     }
627 
628     return true;
629 }
630 
setUserModLock(bool v)631 void Document::setUserModLock(bool v) {
632     if (hasUserModLock() == v) {
633         return;
634     }
635     if (v) {
636         StateLock *sl = new StateLock(tr("Locked by user"));
637         modLocks[DocumentModLock_USER] = sl;
638         lockState(sl);
639     } else {
640         StateLock *sl = modLocks[DocumentModLock_USER];
641         modLocks[DocumentModLock_USER] = nullptr;
642         unlockState(sl);
643         delete sl;
644     }
645 
646     // hack: readonly settings are stored in project, so if document is in project -> mark project as modified
647     if (getParentStateLockItem() != nullptr) {
648         getParentStateLockItem()->setModified(true);
649     }
650 }
651 
unload(bool deleteObjects)652 bool Document::unload(bool deleteObjects) {
653     assert(isLoaded());
654     DocumentChildEventsHelper eventsHelper(this);
655     Q_UNUSED(eventsHelper);
656 
657     QList<StateLock *> locks = findLocks(StateLockableTreeFlags_ItemAndChildren, StateLockFlag_LiveLock);
658     bool liveLocked = (locks.size() > 1);
659     if (locks.size() == 1 && !liveLocked) {
660         SAFE_POINT(locks.first() != nullptr, tr("Lock is NULL"), false);
661         liveLocked &= (locks.first()->getUserDesc() == UNLOAD_LOCK_NAME);
662     }
663     if (liveLocked) {
664         assert(0);
665         return false;
666     }
667 
668     loadStateChangeMode = true;
669 
670     QList<UnloadedObjectInfo> unloadedInfo;
671     QList<GObject *> tmpObjects;
672     foreach (GObject *obj, objects) {
673         unloadedInfo.append(UnloadedObjectInfo(obj));
674         // exclude objects from the document
675         tmpObjects.append(obj);
676         _removeObject(obj, false);
677     }
678     addUnloadedObjects(unloadedInfo);
679 
680     // deallocate objects
681     if (deleteObjects) {
682         if (isDocumentOwnsDbiResources()) {
683             removeObjectsDataFromDbi(tmpObjects);
684         }
685         qDeleteAll(tmpObjects);
686     }
687 
688     StateLock *fl = modLocks[DocumentModLock_FORMAT_AS_INSTANCE];
689     if (fl != nullptr) {
690         unlockState(fl);
691         modLocks[DocumentModLock_FORMAT_AS_INSTANCE] = nullptr;
692     }
693 
694     dbiRef = U2DbiRef();
695     documentOwnsDbiResources = false;
696 
697     setLoaded(false);
698 
699     loadStateChangeMode = false;
700 
701     return true;
702 }
703 
getDbiRef() const704 const U2DbiRef &Document::getDbiRef() const {
705     return dbiRef;
706 }
707 
isDatabaseConnection() const708 bool Document::isDatabaseConnection() const {
709     return BaseDocumentFormats::DATABASE_CONNECTION == df->getFormatId();
710 }
711 
setModified(bool modified,const QString & modType)712 void Document::setModified(bool modified, const QString &modType) {
713     CHECK(!df->checkFlags(DocumentFormatFlag_DirectWriteOperations), );
714     if (loadStateChangeMode && modified && modType == StateLockModType_AddChild) {  // ignore modification events during loading/unloading
715         return;
716     }
717     StateLockableTreeItem::setModified(modified, modType);
718 }
719 
isModificationAllowed(const QString & modType)720 bool Document::isModificationAllowed(const QString &modType) {
721     bool ok = loadStateChangeMode && modType == StateLockModType_AddChild;
722     ok = ok || StateLockableTreeItem::isModificationAllowed(modType);
723     return ok;
724 }
725 
setGHints(GHints * newHints)726 void Document::setGHints(GHints *newHints) {
727     assert(newHints != nullptr);
728     // gobjects in document keep states in parent document map -> preserve gobject hints
729     if (newHints == ctxState) {
730         return;
731     }
732     QList<QVariantMap> objectHints;
733     for (int i = 0; i < objects.size(); i++) {
734         GObject *obj = objects[i];
735         objectHints.append(obj->getGHintsMap());
736     }
737 
738     delete ctxState;
739     ctxState = newHints;
740 
741     for (int i = 0; i < objects.size(); i++) {
742         const QVariantMap &hints = objectHints[i];
743         GObject *obj = objects[i];
744         obj->getGHints()->setMap(hints);
745     }
746 }
747 
addUnloadedObjects(const QList<UnloadedObjectInfo> & info)748 void Document::addUnloadedObjects(const QList<UnloadedObjectInfo> &info) {
749     foreach (const UnloadedObjectInfo &oi, info) {
750         UnloadedObject *obj = new UnloadedObject(oi);
751         obj->moveToThread(thread());
752         _addObjectToHierarchy(obj);
753         assert(obj->getDocument() == this);
754         emit si_objectAdded(obj);
755     }
756 }
757 
getGHintsMap() const758 QVariantMap Document::getGHintsMap() const {
759     return ctxState->getMap();
760 }
761 
setupToEngine(QScriptEngine * engine)762 void Document::setupToEngine(QScriptEngine *engine) {
763     qScriptRegisterMetaType(engine, toScriptValue, fromScriptValue);
764 }
765 
toScriptValue(QScriptEngine * engine,Document * const & in)766 QScriptValue Document::toScriptValue(QScriptEngine *engine, Document *const &in) {
767     return engine->newQObject(in);
768 }
769 
fromScriptValue(const QScriptValue & object,Document * & out)770 void Document::fromScriptValue(const QScriptValue &object, Document *&out) {
771     out = qobject_cast<Document *>(object.toQObject());
772 }
773 
removeObjectsDataFromDbi(QList<GObject * > objects)774 void Document::removeObjectsDataFromDbi(QList<GObject *> objects) {
775     const bool removeAsynchronously = AppContext::isGUIMode() && QCoreApplication::instance()->thread() == QThread::currentThread() && !ctxState->getMap().contains(DocumentRemovalMode_Synchronous);
776     if (removeAsynchronously) {
777         // Do not remove objects in the main thread to prevent GUI hanging
778         DeleteObjectsTask *deleteTask = new DeleteObjectsTask(objects);
779         AppContext::getTaskScheduler()->registerTopLevelTask(deleteTask);
780     } else {
781         U2OpStatus2Log os;
782         DbiOperationsBlock opBlock(dbiRef, os);
783         CHECK_OP(os, );
784 
785         DbiConnection con(dbiRef, os);
786         CHECK_OP(os, );
787         CHECK(con.dbi->getFeatures().contains(U2DbiFeature_RemoveObjects), );
788 
789         foreach (GObject *object, objects) {
790             U2OpStatus2Log osLog;
791             SAFE_POINT(object != nullptr, "NULL object was provided", );
792             con.dbi->getObjectDbi()->removeObject(object->getEntityRef().entityId, true, osLog);
793         }
794     }
795 }
796 
setLastUpdateTime()797 void Document::setLastUpdateTime() {
798     QFileInfo fi(getURLString());
799     if (fi.exists()) {
800         lastUpdateTime = fi.lastModified();
801     }
802 }
803 
propagateModLocks(Document * doc) const804 void Document::propagateModLocks(Document *doc) const {
805     for (int i = 0; i < DocumentModLock_NUM_LOCKS; i++) {
806         StateLock *lock = modLocks[i];
807         if (lock != nullptr && doc->modLocks[i] != nullptr) {
808             StateLock *newLock = new StateLock(lock->getUserDesc(), lock->getFlags());
809             doc->modLocks[i] = newLock;
810             doc->lockState(newLock);
811         }
812     }
813 }
814 
setIOAdapterFactory(IOAdapterFactory * iof)815 void Document::setIOAdapterFactory(IOAdapterFactory *iof) {
816     io = iof;
817 }
818 
DocumentMimeData(Document * obj)819 DocumentMimeData::DocumentMimeData(Document *obj)
820     : objPtr(obj) {
821     setUrls(QList<QUrl>() << QUrl(GUrlUtils::gUrl2qUrl(obj->getURL())));
822 }
823 
824 }  // namespace U2
825