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