1 //------------------------------------------------------------------------------
2 // emFileModel.h
3 //
4 // Copyright (C) 2005-2008,2014,2016,2018 Oliver Hamann.
5 //
6 // Homepage: http://eaglemode.sourceforge.net/
7 //
8 // This program is free software: you can redistribute it and/or modify it under
9 // the terms of the GNU General Public License version 3 as published by the
10 // Free Software Foundation.
11 //
12 // This program is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 // FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
15 // more details.
16 //
17 // You should have received a copy of the GNU General Public License version 3
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 //------------------------------------------------------------------------------
20 
21 #ifndef emFileModel_h
22 #define emFileModel_h
23 
24 #ifndef emPriSchedAgent_h
25 #include <emCore/emPriSchedAgent.h>
26 #endif
27 
28 #ifndef emSigModel_h
29 #include <emCore/emSigModel.h>
30 #endif
31 
32 class emFileModelClient;
33 
34 
35 //==============================================================================
36 //================================ emFileModel =================================
37 //==============================================================================
38 
39 class emFileModel : public emModel {
40 
41 public:
42 
43 	// Abstract base class for a shared file model. Such a file model can
44 	// represent a file of a certain file format in memory. The most
45 	// important features are:
46 	//
47 	//  - File models can have clients (=> class emFileModelClient). The
48 	//    clients tell how much memory the model may allocate at most. If
49 	//    the model would need more, the file is not loaded. Without any
50 	//    client, the file is never loaded. Thus, if you want a file to be
51 	//    loaded by a file model, you will have to create and manage at
52 	//    least one emFileModelClient.
53 	//
54 	//  - Loading and unloading is performed automatically, depending on the
55 	//    the state of the clients.
56 	//
57 	//  - Saving is not performed automatically, except a derived class
58 	//    implements such an automatism.
59 	//
60 	//  - Loading and saving are performed step by step in the Cycle method,
61 	//    so that it does not block the other engines of the program.
62 	//
63 	//  - Normally, only one file model is loading at a time (just to avoid
64 	//    heavy seeking of the hard drive). The order of loading multiple
65 	//    file models is determined by a priority which can be set at the
66 	//    clients.
67 
68 	virtual const emString & GetFilePath() const;
69 		// Path name of the file. Returns the model name by default.
70 
71 	const emSignal & GetFileStateSignal() const;
72 		// This signal is sent on any change in the results of
73 		// GetFileState(), GetMemoryNeed(), GetFileProgress() and
74 		// GetErrorText().
75 
76 	enum FileState {
77 		// Possibilities for the state of the file model.
78 
79 		FS_WAITING = 0,
80 			// The file model wants to load the file, but it is
81 			// waiting until there are no other file models in a
82 			// loading state.
83 
84 		FS_LOADING = 1,
85 			// The file model is currently reading the file.
86 
87 		FS_LOADED = 2,
88 			// The file model is loaded.
89 
90 		FS_UNSAVED = 3,
91 			// The file model is loaded and there are unsaved
92 			// changes in the model data. This state prevents from
93 			// unloading and reloading.
94 
95 		FS_SAVING = 4,
96 			// The file model is currently writing the file.
97 
98 		FS_TOO_COSTLY = 5,
99 			// The file model is not loaded, because there is no
100 			// file model client which accepts the memory need.
101 
102 		FS_LOAD_ERROR = 6,
103 			// The file model is not loaded, because there was an
104 			// error in reading the file.
105 
106 		FS_SAVE_ERROR = 7,
107 			// Like FS_UNSAVED, but there was an error in writing
108 			// the file.
109 
110 		FS_MAX_VAL = 7
111 			// Just the maximum possible integer value for the
112 			// state.
113 	};
114 
115 	FileState GetFileState() const;
116 		// Get current state of this file model.
117 
118 	emUInt64 GetMemoryNeed() const;
119 		// Get best known number of memory bytes, which are allocated,
120 		// or which will be allocated, or which would be allocated, in
121 		// loaded state. In addition to the memory need of the file
122 		// model itself, this should also include the memory need of one
123 		// typical client (e.g. emFilePanel), if not negligible. The
124 		// result of GetMemoryNeed() is never zero. In unsaved or
125 		// errored state, it could be nonsense.
126 
127 	double GetFileProgress() const;
128 		// Get progress of loading or saving in percent.
129 
130 	const emString & GetErrorText() const;
131 		// Get the error description when in state FS_LOAD_ERROR or
132 		// FS_SAVE_ERROR.
133 
134 	void Update();
135 		// Perform an update: If the state is FS_LOAD_ERROR, the error
136 		// text is cleared and the loading will be tried again. If the
137 		// state is FS_TOO_COSTLY with a last known memory need greater
138 		// than one, the loading will be tried again. If the state is
139 		// FS_LOADED, and if the model is out-of-date (checks file time
140 		// by default), the file is unloaded for loading it again.
141 		// Hints:
142 		//  - A good place to call this method is when creating a new
143 		//    emFileModelClient.
144 		//  - Do not call this too often, because some file models are
145 		//    always reloading.
146 
147 	static emRef<emSigModel> AcquireUpdateSignalModel(
148 		emRootContext & rootContext
149 	);
150 		// This functions acquires a global signal model. When that
151 		// signal is signaled, all file models are updated (see method
152 		// Update()), except for those which have
153 		// GetIgnoreUpdateSignal()==true. The signal could be used as an
154 		// application-wide update signal even for reloading files which
155 		// are not interfaced through emFileModel.
156 
157 	bool GetIgnoreUpdateSignal() const;
158 	void SetIgnoreUpdateSignal(bool ignore);
159 		// If true, this file model does not listen to the global update
160 		// signal. Should be set at most for private file models.
161 
162 	void Load(bool immediately);
163 		// Normally, there is no need to call this method, because
164 		// loading is performed automatically. If the state is
165 		// FS_WAITING, Load(false) starts the loading right away,
166 		// ignoring the priority. If the state is FS_LOADING,
167 		// Load(false) performs one more step in loading the file.
168 		// Load(true) blocks until the state is not FS_WAITING and not
169 		// FS_LOADING. For other states, this method has no effect.
170 
171 	void Save(bool immediately);
172 		// This is similar to Load, but remember that saving is not
173 		// started automatically. If the state is FS_UNSAVED,
174 		// Save(false) starts the saving (which continues
175 		// automatically). If the state is FS_SAVING, Save(false)
176 		// performs one more step in saving the file. Save(true) blocks
177 		// until the state is not FS_UNSAVED and not FS_SAVING. For
178 		// other states, this method has no effect.
179 
180 	void ClearSaveError();
181 		// Resets the state to FS_UNSAVED, if it was FS_SAVE_ERROR.
182 
183 	void HardResetFileState();
184 		// Unload the file and restart the loading logics. This works
185 		// even in unsaved or errored state, and it is the only way to
186 		// take back unsaved changes.
187 
188 protected:
189 
190 	emFileModel(emContext & context, const emString & name);
191 		// Constructor.
192 		// Arguments:
193 		//   context - Normally, this should be the root context.
194 		//   name - Normally, this is the path name of the file
195 		//          (otherwise GetFilePath has to be overloaded).
196 
197 	virtual ~emFileModel();
198 		// Destructor.
199 
200 	virtual bool Cycle();
201 		// See emEngine::Cycle. This one performs loading and saving.
202 
203 	void SetUnsavedState();
204 		// This must be called before or immediately after modifying the
205 		// data (except through ResetData, TryStartLoading and
206 		// TryContinueLoading). Hereby, any loading or saving is
207 		// aborted, and the file state is set to FS_UNSAVED. The caller
208 		// should take care: if the state was FS_SAVING, the file will
209 		// be corrupted, and if the state was FS_LOADING, the data may
210 		// be corrupted.
211 
212 	virtual void ResetData() = 0;
213 		// Called for unloading the file. The implementation may free
214 		// allocated memory or set the data to a default state.
215 
216 	virtual void TryStartLoading() = 0;
217 	virtual bool TryContinueLoading() = 0;
218 	virtual void QuitLoading() = 0;
219 		// Called for loading the file. First, TryStartLoading is
220 		// called, and then TryContinueLoading is called again and again
221 		// until true is returned. For aborting by an error, an
222 		// exception with a user-readable error message can be thrown.
223 		// Returning true from TryContinueLoading means to have finished
224 		// with loading. No call should waist more than about 10
225 		// milliseconds (if possible somehow). There's no problem if
226 		// each call waists much fewer time, but if multiple calls
227 		// cannot really continue because of waiting for a child process
228 		// or so, they should do an emSleepMS(10) or something similar,
229 		// otherwise we would end up in busy waiting. Before allocating
230 		// a worth meaning amount of memory, a call should return and
231 		// the amount of memory should be reported through the result of
232 		// CalcMemoryNeed(), so that there is a chance to abort the
233 		// loading and to enter the state FS_TOO_COSTLY before the
234 		// memory would be allocated. Best is to determine the whole
235 		// memory need in TryStartLoading (e.g. through reading just a
236 		// file header), and to allocate that memory in
237 		// TryContinueLoading. It is guaranteed that ResetData is called
238 		// before TryStartLoading. And it is guaranteed that QuitLoading
239 		// is called at the end, either on success, or for aborting, or
240 		// after an error - but remember that it can never be called
241 		// through the destructor of emFileModel (=> prepare the
242 		// destructor of the derived class accordingly).
243 
244 	virtual void TryStartSaving() = 0;
245 	virtual bool TryContinueSaving() = 0;
246 	virtual void QuitSaving() = 0;
247 		// This is just like above, but for saving. The memory need is
248 		// not relevant here.
249 
250 	virtual emUInt64 CalcMemoryNeed() = 0;
251 		// While loading, this method is called again and again to
252 		// calculate the number of memory bytes, which will be allocated
253 		// by this file model and by one typical file model client in
254 		// loaded state. If it cannot be calculated, it should be a good
255 		// approximation (e.g. from file size).
256 
257 	virtual double CalcFileProgress() = 0;
258 		// While loading or saving, this method is called to determine
259 		// the progress in percent. It's just an information for the
260 		// user.
261 
262 	virtual void TryFetchDate();
263 		// Get and remember file information for IsOutOfDate(). The default
264 		// implementation is for the default implementation of IsOutOfDate().
265 
266 	virtual bool IsOutOfDate();
267 		// Check whether the loaded file should be reloaded. The default
268 		// implementation checks by file times, size and inode.
269 
270 private: friend class emFileModelClient;
271 
272 	void ClientsChanged();
273 	bool StepLoading();
274 	bool StepSaving();
275 	bool UpdateFileProgress();
276 	void StartPSAgent();
277 	void EndPSAgent();
278 
279 	class PSAgentClass : public emPriSchedAgent {
280 	public:
281 		PSAgentClass(emFileModel & fileModel);
282 	protected:
283 		virtual void GotAccess();
284 	private:
285 		emFileModel & FileModel;
286 	};
287 
288 	friend class PSAgentClass;
289 
290 	emSignal FileStateSignal;
291 	FileState State;
292 	emUInt64 MemoryNeed;
293 	double FileProgress;
294 	emUInt64 FileProgressClock;
295 	emString ErrorText;
296 	emFileModelClient * ClientList;
297 	emUInt64 MemoryLimit;
298 	time_t LastMTime;
299 	time_t LastCTime;
300 	emUInt64 LastFSize;
301 	emUInt64 LastINode;
302 	PSAgentClass * PSAgent;
303 	emRef<emSigModel> UpdateSignalModel; // NULL if ignored
304 };
305 
GetFileStateSignal()306 inline const emSignal & emFileModel::GetFileStateSignal() const
307 {
308 	return FileStateSignal;
309 }
310 
GetFileState()311 inline emFileModel::FileState emFileModel::GetFileState() const
312 {
313 	return State;
314 }
315 
GetMemoryNeed()316 inline emUInt64 emFileModel::GetMemoryNeed() const
317 {
318 	return MemoryNeed;
319 }
320 
GetFileProgress()321 inline double emFileModel::GetFileProgress() const
322 {
323 	return FileProgress;
324 }
325 
GetErrorText()326 inline const emString & emFileModel::GetErrorText() const
327 {
328 	return ErrorText;
329 }
330 
GetIgnoreUpdateSignal()331 inline bool emFileModel::GetIgnoreUpdateSignal() const
332 {
333 	return UpdateSignalModel==NULL;
334 }
335 
336 
337 //==============================================================================
338 //============================= emFileModelClient ==============================
339 //==============================================================================
340 
341 class emFileModelClient : public emUncopyable {
342 
343 public:
344 
345 	// Class for a client on an emFileModel. Multiple clients can connect to
346 	// the same file model. And:
347 	//
348 	// - Each client tells about the maximum memory which may be allocated
349 	//   by the file model and one typical client (e.g. emFilePanel). If no
350 	//   client accepts the memory need this way, the file is not loaded or
351 	//   it is unloaded.
352 	//
353 	// - Each client tells about the priority which is used for determining
354 	//   the order of loading file models. The maximum priority of all
355 	//   clients is taken.
356 	//
357 	// On each modification of a memory limit or a priority, the logics of
358 	// emFileModel reacts automatically.
359 	//
360 	// Hint: Even have a look at the class emFilePanel. It's a base class
361 	// for panels which want to be file mode clients.
362 
363 	emFileModelClient(emFileModel * model=NULL, emUInt64 memoryLimit=0,
364 	                  double priority=0.0);
365 		// Constructor.
366 		// Arguments:
367 		//   model        - See SetModel below.
368 		//   memoryLimit  - See SetMemoryLimit below.
369 		//   priority     - See SetPriority below.
370 
371 	virtual ~emFileModelClient();
372 		// Destructor.
373 
374 	emFileModel * GetModel() const;
375 	void SetModel(emFileModel * model=NULL);
376 		// The file model this client is connected to. NULL means to
377 		// have disconnected state.
378 
379 	void SetMemoryLimit(emUInt64 bytes);
380 	emUInt64 GetMemoryLimit() const;
381 		// Maximum memory need accepted for loading the file, from sight
382 		// of this client. Usually, this should be set from
383 		// emPanel::GetMemoryLimit().
384 
385 	double GetPriority() const;
386 	void SetPriority(double priority);
387 		// Priority in loading the file, from sight of this client.
388 		// Usually, this should be set from
389 		// emPanel::GetUpdatePriority().
390 
391 private: friend class emFileModel;
392 
393 	emRef<emFileModel> Model;
394 	emUInt64 MemoryLimit;
395 	double Priority;
396 	emFileModelClient * * ThisPtrInList;
397 	emFileModelClient * NextInList;
398 };
399 
GetModel()400 inline emFileModel * emFileModelClient::GetModel() const
401 {
402 	return Model;
403 }
404 
GetMemoryLimit()405 inline emUInt64 emFileModelClient::GetMemoryLimit() const
406 {
407 	return MemoryLimit;
408 }
409 
GetPriority()410 inline double emFileModelClient::GetPriority() const
411 {
412 	return Priority;
413 }
414 
415 
416 #endif
417