1// Aseprite
2// Copyright (C) 2019-2020 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifndef APP_DOC_ACCESS_H_INCLUDED
9#define APP_DOC_ACCESS_H_INCLUDED
10#pragma once
11
12#include "app/context.h"
13#include "app/doc.h"
14#include "base/exception.h"
15
16#include <atomic>
17#include <exception>
18
19namespace app {
20
21 // TODO remove exceptions and use "DocAccess::operator bool()"
22 class LockedDocException : public base::Exception {
23 public:
24 LockedDocException(const char* msg) throw()
25 : base::Exception(msg) { }
26 };
27
28 class CannotReadDocException : public LockedDocException {
29 public:
30 CannotReadDocException() throw()
31 : LockedDocException("Cannot read the sprite.\n"
32 "It is being modified by another command.\n"
33 "Try again.") { }
34 };
35
36 class CannotWriteDocException : public LockedDocException {
37 public:
38 CannotWriteDocException() throw()
39 : LockedDocException("Cannot modify the sprite.\n"
40 "It is being used by another command.\n"
41 "Try again.") { }
42 };
43
44 // This class acts like a wrapper for the given document. It's
45 // specialized by DocReader/Writer to handle document read/write
46 // locks.
47 class DocAccess {
48 public:
49 DocAccess() : m_doc(NULL) { }
50 DocAccess(const DocAccess& copy) : m_doc(copy.m_doc) { }
51 explicit DocAccess(Doc* doc) : m_doc(doc) { }
52 ~DocAccess() { }
53
54 DocAccess& operator=(const DocAccess& copy) {
55 m_doc = copy.m_doc;
56 return *this;
57 }
58
59 operator Doc*() { return m_doc; }
60 operator const Doc*() const { return m_doc; }
61
62 Doc* operator->() {
63 ASSERT(m_doc);
64 return m_doc;
65 }
66
67 const Doc* operator->() const {
68 ASSERT(m_doc);
69 return m_doc;
70 }
71
72 protected:
73 Doc* m_doc;
74 };
75
76 // Class to view the document's state. Its constructor request a
77 // reader-lock of the document, or throws an exception in case that
78 // the lock cannot be obtained.
79 class DocReader : public DocAccess {
80 public:
81 DocReader() {
82 }
83
84 explicit DocReader(Doc* doc, int timeout)
85 : DocAccess(doc) {
86 if (m_doc && !m_doc->readLock(timeout))
87 throw CannotReadDocException();
88 }
89
90 explicit DocReader(const DocReader& copy, int timeout)
91 : DocAccess(copy) {
92 if (m_doc && !m_doc->readLock(timeout))
93 throw CannotReadDocException();
94 }
95
96 ~DocReader() {
97 unlock();
98 }
99
100 protected:
101 void unlock() {
102 if (m_doc) {
103 m_doc->unlock();
104 m_doc = nullptr;
105 }
106 }
107
108 private:
109 // Disable operator=
110 DocReader& operator=(const DocReader&);
111 };
112
113 // Class to modify the document's state. Its constructor request a
114 // writer-lock of the document, or throws an exception in case that
115 // the lock cannot be obtained. Also, it contains a special
116 // constructor that receives a DocReader, to elevate the
117 // reader-lock to writer-lock.
118 class DocWriter : public DocAccess {
119 public:
120 DocWriter()
121 : m_from_reader(false)
122 , m_locked(false) {
123 }
124
125 explicit DocWriter(Doc* doc, int timeout)
126 : DocAccess(doc)
127 , m_from_reader(false)
128 , m_locked(false) {
129 if (m_doc) {
130 if (!m_doc->writeLock(timeout))
131 throw CannotWriteDocException();
132
133 m_locked = true;
134 }
135 }
136
137 // Constructor that can be used to elevate the given reader-lock to
138 // writer permission.
139 explicit DocWriter(const DocReader& doc, int timeout)
140 : DocAccess(doc)
141 , m_from_reader(true)
142 , m_locked(false) {
143 if (m_doc) {
144 if (!m_doc->upgradeToWrite(timeout))
145 throw CannotWriteDocException();
146
147 m_locked = true;
148 }
149 }
150
151 ~DocWriter() {
152 unlock();
153 }
154
155 protected:
156 void unlock() {
157 if (m_doc && m_locked) {
158 if (m_from_reader)
159 m_doc->downgradeToRead();
160 else
161 m_doc->unlock();
162
163 m_doc = nullptr;
164 m_locked = false;
165 }
166 }
167
168 private:
169 bool m_from_reader;
170 bool m_locked;
171
172 // Non-copyable
173 DocWriter(const DocWriter&);
174 DocWriter& operator=(const DocWriter&);
175 DocWriter& operator=(const DocReader&);
176 };
177
178 // Used to destroy the active document in the context.
179 class DocDestroyer : public DocWriter {
180 public:
181 explicit DocDestroyer(Context* context, Doc* doc, int timeout)
182 : DocWriter(doc, timeout) {
183 }
184
185 void destroyDocument() {
186 ASSERT(m_doc != nullptr);
187
188 // Don't create a backup for destroyed documents (e.g. documents
189 // are destroyed when they are used internally by Aseprite or by
190 // a script and then closed with Sprite:close())
191 if (m_doc->needsBackup())
192 m_doc->setInhibitBackup(true);
193
194 m_doc->close();
195 Doc* doc = m_doc;
196 unlock();
197
198 delete doc;
199 m_doc = nullptr;
200 }
201
202 void closeDocument() {
203 ASSERT(m_doc != nullptr);
204
205 Context* ctx = (Context*)m_doc->context();
206 m_doc->close();
207 Doc* doc = m_doc;
208 unlock();
209
210 ctx->closeDocument(doc);
211 m_doc = nullptr;
212 }
213
214 };
215
216 class WeakDocReader : public DocAccess {
217 public:
218 WeakDocReader() {
219 }
220
221 explicit WeakDocReader(Doc* doc)
222 : DocAccess(doc)
223 , m_weak_lock(base::RWLock::WeakUnlocked) {
224 if (m_doc)
225 m_doc->weakLock(&m_weak_lock);
226 }
227
228 ~WeakDocReader() {
229 weakUnlock();
230 }
231
232 bool isLocked() const {
233 return (m_weak_lock == base::RWLock::WeakLocked);
234 }
235
236 protected:
237 void weakUnlock() {
238 if (m_doc && m_weak_lock != base::RWLock::WeakUnlocked) {
239 m_doc->weakUnlock();
240 m_doc = nullptr;
241 }
242 }
243
244 private:
245 // Disable operator=
246 WeakDocReader(const WeakDocReader&);
247 WeakDocReader& operator=(const WeakDocReader&);
248
249 std::atomic<base::RWLock::WeakLock> m_weak_lock;
250 };
251
252} // namespace app
253
254#endif
255