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