1 //------------------------------------------------------------------------------
2 // emFileModel.cpp
3 //
4 // Copyright (C) 2005-2008,2014,2016,2018-2019 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 #include <emCore/emFileModel.h>
22 
23 
24 //==============================================================================
25 //================================ emFileModel =================================
26 //==============================================================================
27 
GetFilePath() const28 const emString & emFileModel::GetFilePath() const
29 {
30 	return GetName();
31 }
32 
33 
Update()34 void emFileModel::Update()
35 {
36 	switch (State) {
37 		case FS_WAITING:
38 			if (MemoryNeed>1) {
39 				MemoryNeed=1;
40 				Signal(FileStateSignal);
41 			}
42 			break;
43 		case FS_LOADED:
44 			if (IsOutOfDate()) {
45 				ResetData();
46 				State=FS_TOO_COSTLY;
47 				MemoryNeed=1;
48 				FileProgress=0.0;
49 				if (ClientList) {
50 					State=FS_WAITING;
51 					StartPSAgent();
52 				}
53 				Signal(FileStateSignal);
54 			}
55 			break;
56 		case FS_TOO_COSTLY:
57 			if (MemoryNeed>1) {
58 				MemoryNeed=1;
59 				if (ClientList) {
60 					State=FS_WAITING;
61 					StartPSAgent();
62 				}
63 				Signal(FileStateSignal);
64 			}
65 			break;
66 		case FS_LOAD_ERROR:
67 			State=FS_TOO_COSTLY;
68 			ErrorText.Clear();
69 			MemoryNeed=1;
70 			if (ClientList) {
71 				State=FS_WAITING;
72 				StartPSAgent();
73 			}
74 			Signal(FileStateSignal);
75 			break;
76 		default:
77 			break;
78 	}
79 }
80 
81 
AcquireUpdateSignalModel(emRootContext & rootContext)82 emRef<emSigModel> emFileModel::AcquireUpdateSignalModel(
83 	emRootContext & rootContext
84 )
85 {
86 	return emSigModel::Acquire(rootContext,"emFileModel::UpdateSignal");
87 }
88 
89 
SetIgnoreUpdateSignal(bool ignore)90 void emFileModel::SetIgnoreUpdateSignal(bool ignore)
91 {
92 	if (ignore) {
93 		if (UpdateSignalModel) {
94 			RemoveWakeUpSignal(UpdateSignalModel->Sig);
95 			UpdateSignalModel=NULL;
96 		}
97 	}
98 	else {
99 		if (!UpdateSignalModel) {
100 			UpdateSignalModel=AcquireUpdateSignalModel(GetRootContext());
101 			AddWakeUpSignal(UpdateSignalModel->Sig);
102 		}
103 	}
104 }
105 
106 
Load(bool immediately)107 void emFileModel::Load(bool immediately)
108 {
109 	bool stateChanged;
110 
111 	if (State==FS_WAITING || State==FS_LOADING) {
112 		stateChanged=StepLoading();
113 		if (immediately) {
114 			while (State==FS_LOADING) {
115 				if (StepLoading()) stateChanged=true;
116 			}
117 		}
118 		if (UpdateFileProgress()) stateChanged=true;
119 		if (stateChanged) Signal(FileStateSignal);
120 		if (State==FS_LOADING) WakeUp();
121 	}
122 }
123 
124 
Save(bool immediately)125 void emFileModel::Save(bool immediately)
126 {
127 	bool stateChanged;
128 
129 	if (State==FS_SAVING || State==FS_UNSAVED) {
130 		stateChanged=StepSaving();
131 		if (immediately) {
132 			while (State==FS_SAVING) {
133 				if (StepSaving()) stateChanged=true;
134 			}
135 		}
136 		if (UpdateFileProgress()) stateChanged=true;
137 		if (stateChanged) Signal(FileStateSignal);
138 		if (State==FS_SAVING) WakeUp();
139 	}
140 }
141 
142 
ClearSaveError()143 void emFileModel::ClearSaveError()
144 {
145 	if (State==FS_SAVE_ERROR) {
146 		State=FS_UNSAVED;
147 		ErrorText.Clear();
148 		Signal(FileStateSignal);
149 	}
150 }
151 
152 
HardResetFileState()153 void emFileModel::HardResetFileState()
154 {
155 	EndPSAgent();
156 	switch (State) {
157 		case FS_LOADING:
158 			QuitLoading();
159 			ResetData();
160 			break;
161 		case FS_SAVING:
162 			QuitSaving();
163 			ResetData();
164 			break;
165 		case FS_LOADED:
166 		case FS_UNSAVED:
167 		case FS_SAVE_ERROR:
168 			ResetData();
169 			break;
170 		default:
171 			break;
172 	}
173 	State=FS_TOO_COSTLY;
174 	MemoryNeed=1;
175 	FileProgress=0.0;
176 	ErrorText.Clear();
177 	if (ClientList && MemoryLimit>=MemoryNeed) {
178 		State=FS_WAITING;
179 		StartPSAgent();
180 	}
181 	Signal(FileStateSignal);
182 }
183 
184 
emFileModel(emContext & context,const emString & name)185 emFileModel::emFileModel(
186 	emContext & context, const emString & name
187 )
188 	: emModel(context,name)
189 {
190 	State=FS_TOO_COSTLY;
191 	MemoryNeed=1;
192 	FileProgress=0.0;
193 	FileProgressClock=0;
194 	ClientList=NULL;
195 	MemoryLimit=0;
196 	LastMTime=0;
197 	LastCTime=0;
198 	LastFSize=0;
199 	LastINode=0;
200 	PSAgent=NULL;
201 	UpdateSignalModel=NULL;
202 	SetIgnoreUpdateSignal(false);
203 }
204 
205 
~emFileModel()206 emFileModel::~emFileModel()
207 {
208 	EndPSAgent();
209 }
210 
211 
Cycle()212 bool emFileModel::Cycle()
213 {
214 	bool stateChanged;
215 
216 	if (UpdateSignalModel && IsSignaled(UpdateSignalModel->Sig)) {
217 		Update();
218 	}
219 
220 	switch (State) {
221 		case FS_WAITING:
222 			if (!PSAgent) StartPSAgent();
223 			if (!PSAgent->HasAccess()) return false;
224 			if (IsTimeSliceAtEnd()) return true;
225 			// no break
226 		case FS_LOADING:
227 			stateChanged=false;
228 			do {
229 				if (StepLoading()) stateChanged=true;
230 			} while (State==FS_LOADING && !IsTimeSliceAtEnd());
231 			if (UpdateFileProgress()) stateChanged=true;
232 			if (stateChanged) Signal(FileStateSignal);
233 			return State==FS_LOADING;
234 		case FS_SAVING:
235 			stateChanged=false;
236 			do {
237 				if (StepSaving()) stateChanged=true;
238 			} while (State==FS_SAVING && !IsTimeSliceAtEnd());
239 			if (UpdateFileProgress()) stateChanged=true;
240 			if (stateChanged) Signal(FileStateSignal);
241 			return State==FS_SAVING;
242 		default:
243 			return false;
244 	}
245 }
246 
247 
SetUnsavedState()248 void emFileModel::SetUnsavedState()
249 {
250 	if (State!=FS_UNSAVED) {
251 		EndPSAgent();
252 		switch (State) {
253 			case FS_LOADING:
254 				QuitLoading();
255 				break;
256 			case FS_SAVING:
257 				QuitSaving();
258 				break;
259 			default:
260 				break;
261 		}
262 		State=FS_UNSAVED;
263 		FileProgress=0.0;
264 		ErrorText.Clear();
265 		Signal(FileStateSignal);
266 	}
267 }
268 
269 
TryFetchDate()270 void emFileModel::TryFetchDate()
271 {
272 	struct em_stat st;
273 
274 	if (em_stat(GetFilePath().Get(),&st)!=0) {
275 		throw emException(
276 			"Failed to get info of \"%s\": %s",
277 			GetFilePath().Get(),
278 			emGetErrorText(errno).Get()
279 		);
280 	}
281 	LastMTime=st.st_mtime;
282 	LastCTime=st.st_ctime;
283 	LastFSize=st.st_size;
284 	LastINode=st.st_ino;
285 }
286 
287 
IsOutOfDate()288 bool emFileModel::IsOutOfDate()
289 {
290 	struct em_stat st;
291 
292 	if (em_stat(GetFilePath().Get(),&st)!=0) {
293 		return true;
294 	}
295 	return
296 		LastMTime!=st.st_mtime ||
297 		LastCTime!=st.st_ctime ||
298 		LastFSize!=(emUInt64)st.st_size ||
299 		LastINode!=(emUInt64)st.st_ino
300 	;
301 }
302 
303 
ClientsChanged()304 void emFileModel::ClientsChanged()
305 {
306 	emFileModelClient * c;
307 	emUInt64 m;
308 	double pri;
309 
310 	for (m=0, c=ClientList; c; c=c->NextInList) {
311 		if (m<c->MemoryLimit) m=c->MemoryLimit;
312 	}
313 	MemoryLimit=m;
314 
315 	if (PSAgent) {
316 		c=ClientList;
317 		if (c) {
318 			pri=c->Priority;
319 			for (c=c->NextInList; c; c=c->NextInList) {
320 				if (pri<c->Priority) pri=c->Priority;
321 			}
322 			PSAgent->SetAccessPriority(pri);
323 		}
324 	}
325 
326 	switch (State) {
327 		case FS_WAITING:
328 			if (!ClientList || MemoryLimit<MemoryNeed) {
329 				EndPSAgent();
330 				State=FS_TOO_COSTLY;
331 				Signal(FileStateSignal);
332 			}
333 			break;
334 		case FS_LOADING:
335 			if (!ClientList || MemoryLimit<MemoryNeed) {
336 				EndPSAgent();
337 				QuitLoading();
338 				ResetData();
339 				State=FS_TOO_COSTLY;
340 				FileProgress=0.0;
341 				Signal(FileStateSignal);
342 			}
343 			break;
344 		case FS_LOADED:
345 			if (!ClientList || MemoryLimit<MemoryNeed) {
346 				ResetData();
347 				State=FS_TOO_COSTLY;
348 				FileProgress=0.0;
349 				Signal(FileStateSignal);
350 			}
351 			break;
352 		case FS_TOO_COSTLY:
353 			if (ClientList && MemoryLimit>=MemoryNeed) {
354 				State=FS_WAITING;
355 				StartPSAgent();
356 				Signal(FileStateSignal);
357 			}
358 			break;
359 		default:
360 			break;
361 	}
362 }
363 
364 
StepLoading()365 bool emFileModel::StepLoading()
366 {
367 	bool ready, stateChanged;
368 
369 	if (State==FS_LOADING) {
370 		try {
371 			ready=TryContinueLoading();
372 		}
373 		catch (const emException & exception) {
374 			EndPSAgent();
375 			QuitLoading();
376 			ResetData();
377 			State=FS_LOAD_ERROR;
378 			ErrorText=exception.GetText();
379 			return true;
380 		}
381 		stateChanged=false;
382 	}
383 	else if (State==FS_WAITING) {
384 		try {
385 			TryFetchDate();
386 		}
387 		catch (const emException & e) {
388 			EndPSAgent();
389 			State=FS_LOAD_ERROR;
390 			ErrorText=e.GetText();
391 			return true;
392 		}
393 		ResetData();
394 		State=FS_LOADING;
395 		try {
396 			TryStartLoading();
397 		}
398 		catch (const emException & exception) {
399 			EndPSAgent();
400 			QuitLoading();
401 			ResetData();
402 			State=FS_LOAD_ERROR;
403 			ErrorText=exception.GetText();
404 			return true;
405 		}
406 		ready=false;
407 		stateChanged=true;
408 	}
409 	else {
410 		return false;
411 	}
412 
413 	MemoryNeed=CalcMemoryNeed();
414 	if (MemoryNeed<1) MemoryNeed=1;
415 	if (!ClientList || MemoryNeed>MemoryLimit) {
416 		EndPSAgent();
417 		QuitLoading();
418 		ResetData();
419 		State=FS_TOO_COSTLY;
420 		return true;
421 	}
422 	if (!ready) return stateChanged;
423 	EndPSAgent();
424 	QuitLoading();
425 	State=FS_LOADED;
426 	return true;
427 }
428 
429 
StepSaving()430 bool emFileModel::StepSaving()
431 {
432 	bool ready;
433 
434 	if (State==FS_SAVING) {
435 		try {
436 			ready=TryContinueSaving();
437 		}
438 		catch (const emException & exception) {
439 			EndPSAgent();
440 			QuitSaving();
441 			State=FS_SAVE_ERROR;
442 			ErrorText=exception.GetText();
443 			return true;
444 		}
445 		if (!ready) return false;
446 	}
447 	else if (State==FS_UNSAVED) {
448 		State=FS_SAVING;
449 		ErrorText.Clear();
450 		try {
451 			TryStartSaving();
452 		}
453 		catch (const emException & exception) {
454 			EndPSAgent();
455 			QuitSaving();
456 			State=FS_SAVE_ERROR;
457 			ErrorText=exception.GetText();
458 			return true;
459 		}
460 		return true;
461 	}
462 	else {
463 		return false;
464 	}
465 
466 	EndPSAgent();
467 	QuitSaving();
468 	try {
469 		TryFetchDate();
470 	}
471 	catch (const emException & e) {
472 		State=FS_SAVE_ERROR;
473 		ErrorText=e.GetText();
474 		return true;
475 	}
476 	State=FS_LOADED;
477 	MemoryNeed=CalcMemoryNeed();
478 	if (MemoryNeed<1) MemoryNeed=1;
479 	if (!ClientList || MemoryNeed>MemoryLimit) {
480 		ResetData();
481 		State=FS_TOO_COSTLY;
482 	}
483 	return true;
484 }
485 
486 
UpdateFileProgress()487 bool emFileModel::UpdateFileProgress()
488 {
489 	double pg;
490 	emUInt64 clk;
491 
492 	switch (State) {
493 		case FS_LOADING:
494 		case FS_SAVING:
495 			clk=emGetClockMS();
496 			if (clk-FileProgressClock>=250) {
497 				FileProgressClock=clk;
498 				pg=CalcFileProgress();
499 			}
500 			else {
501 				return false;
502 			}
503 			break;
504 		case FS_LOADED:
505 		case FS_UNSAVED:
506 			pg=100.0;
507 			break;
508 		default:
509 			pg=0.0;
510 			break;
511 	}
512 	if (FileProgress>pg-0.01 && FileProgress<pg+0.01) return false;
513 	FileProgress=pg;
514 	return true;
515 }
516 
517 
StartPSAgent()518 void emFileModel::StartPSAgent()
519 {
520 	emFileModelClient * c;
521 	double pri;
522 
523 	if (!PSAgent) PSAgent=new PSAgentClass(*this);
524 	c=ClientList;
525 	if (c) {
526 		pri=c->Priority;
527 		for (c=c->NextInList; c; c=c->NextInList) {
528 			if (pri<c->Priority) pri=c->Priority;
529 		}
530 		PSAgent->SetAccessPriority(pri);
531 	}
532 	PSAgent->RequestAccess();
533 }
534 
535 
EndPSAgent()536 void emFileModel::EndPSAgent()
537 {
538 	if (PSAgent) {
539 		delete PSAgent;
540 		PSAgent=NULL;
541 	}
542 }
543 
544 
PSAgentClass(emFileModel & fileModel)545 emFileModel::PSAgentClass::PSAgentClass(
546 	emFileModel & fileModel
547 ) :
548 	emPriSchedAgent(fileModel.GetRootContext(),"cpu"),
549 	FileModel(fileModel)
550 {
551 }
552 
553 
GotAccess()554 void emFileModel::PSAgentClass::GotAccess()
555 {
556 	FileModel.WakeUp();
557 }
558 
559 
560 //==============================================================================
561 //============================= emFileModelClient ==============================
562 //==============================================================================
563 
emFileModelClient(emFileModel * model,emUInt64 memoryLimit,double priority)564 emFileModelClient::emFileModelClient(
565 	emFileModel * model, emUInt64 memoryLimit, double priority
566 )
567 {
568 	MemoryLimit=memoryLimit;
569 	Priority=priority;
570 	ThisPtrInList=NULL;
571 	NextInList=NULL;
572 	if (model) SetModel(model);
573 }
574 
575 
~emFileModelClient()576 emFileModelClient::~emFileModelClient()
577 {
578 	SetModel(NULL);
579 }
580 
581 
SetModel(emFileModel * model)582 void emFileModelClient::SetModel(emFileModel * model)
583 {
584 	if (Model==model) return;
585 	if (Model) {
586 		*ThisPtrInList=NextInList;
587 		if (NextInList) NextInList->ThisPtrInList=ThisPtrInList;
588 		ThisPtrInList=NULL;
589 		NextInList=NULL;
590 		Model->ClientsChanged();
591 		Model=NULL;
592 	}
593 	if (model) {
594 		Model=model;
595 		NextInList=Model->ClientList;
596 		if (NextInList) NextInList->ThisPtrInList=&NextInList;
597 		Model->ClientList=this;
598 		ThisPtrInList=&Model->ClientList;
599 		Model->ClientsChanged();
600 	}
601 }
602 
603 
SetMemoryLimit(emUInt64 bytes)604 void emFileModelClient::SetMemoryLimit(emUInt64 bytes)
605 {
606 	if (MemoryLimit!=bytes) {
607 		MemoryLimit=bytes;
608 		if (Model) Model->ClientsChanged();
609 	}
610 }
611 
612 
SetPriority(double priority)613 void emFileModelClient::SetPriority(double priority)
614 {
615 	if (Priority!=priority) {
616 		Priority=priority;
617 		if (Model) Model->ClientsChanged();
618 	}
619 }
620