-
Notifications
You must be signed in to change notification settings - Fork 0
/
FileHistory.cpp
239 lines (218 loc) · 7.48 KB
/
FileHistory.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/* Copyright 2021 the SumatraPDF project authors (see AUTHORS file).
License: GPLv3 */
#include "utils/BaseUtil.h"
#include "utils/FileUtil.h"
#include "utils/ScopedWin.h"
#include "utils/WinUtil.h"
#include "wingui/TreeModel.h"
#include "DisplayMode.h"
#include "Controller.h"
#include "EngineBase.h"
#include "SettingsStructs.h"
#include "FileHistory.h"
#include "GlobalPrefs.h"
/* Handling of file history list.
We keep a mostly infinite list of all (still existing in the file system)
files that a user has ever opened. For each file we also keep a bunch of
attributes describing the display state at the time the file was closed.
We persist this list inside preferences file to something looking like this:
FileStates [
FilePath = C:\path\to\file.pdf
DisplayMode = single page
PageNo = 1
ZoomVirtual = 123.4567
Window State = 2
...
]
etc...
We deserialize this info at startup and serialize when the application
quits.
*/
// maximum number of files to remember in total
// (to keep the settings file within reasonable bounds)
#define FILE_HISTORY_MAX_FILES 1000
// sorts the most often used files first
static int cmpOpenCount(const void* a, const void* b) {
FileState* dsA = *(FileState**)a;
FileState* dsB = *(FileState**)b;
// sort pinned documents before unpinned ones
if (dsA->isPinned != dsB->isPinned) {
return dsA->isPinned ? -1 : 1;
}
// sort pinned documents alphabetically
if (dsA->isPinned) {
return str::CmpNatural(path::GetBaseNameTemp(dsA->filePath), path::GetBaseNameTemp(dsB->filePath));
}
// sort often opened documents first
if (dsA->openCount != dsB->openCount) {
return dsB->openCount - dsA->openCount;
}
// use recency as the criterion in case of equal open counts
return dsA->index < dsB->index ? -1 : 1;
}
void FileHistory::Append(FileState* fs) const {
CrashIf(!fs->filePath);
states->Append(fs);
}
void FileHistory::Remove(FileState* fs) const {
states->Remove(fs);
}
void FileHistory::UpdateStatesSource(Vec<FileState*>* states) {
this->states = states;
}
void FileHistory::FixPath() {
if (states != nullptr) {
char* exeDir = ToUtf8Temp(GetExeDir());
FileState* fs = nullptr;
for (size_t i = 0; i < states->size(); i++) {
fs = states->at(i);
// is relative path
if(fs->isRelativePath) {
str::ReplaceWithCopy(&fs->filePath, str::JoinTemp(exeDir, fs->fileRelativePath));
}
}
}
}
void FileHistory::Clear(bool keepFavorites) const {
if (!states) {
return;
}
Vec<FileState*> keep;
for (size_t i = 0; i < states->size(); i++) {
if (keepFavorites && states->at(i)->favorites->size() > 0) {
states->at(i)->openCount = 0;
keep.Append(states->at(i));
} else {
DeleteDisplayState(states->at(i));
}
}
*states = keep;
}
FileState* FileHistory::Get(size_t index) const {
if (index < states->size()) {
return states->at(index);
}
return nullptr;
}
FileState* FileHistory::Find(const char* filePath, size_t* idxOut) const {
int idxExact = -1;
int idxFileNameMatch = -1;
const char* fileName = path::GetBaseNameTemp(filePath);
int n = states->isize();
for (int i = 0; i < n; i++) {
FileState* fs = states->at(i);
if (str::EqI(fs->filePath, filePath)) {
idxExact = i;
} else if (str::EndsWithI(fs->filePath, fileName)) {
idxFileNameMatch = i;
}
}
int idFound = idxExact;
if (idFound == -1) {
idFound = idxFileNameMatch;
}
if (idFound == -1) {
return nullptr;
}
if (idxOut) {
*idxOut = (size_t)idFound;
}
return states->at(idFound);
}
FileState* FileHistory::MarkFileLoaded(const char* filePath) const {
CrashIf(!filePath);
// if a history entry with the same name already exists,
// then reuse it. That way we don't have duplicates and
// the file moves to the front of the list
FileState* fs = Find(filePath, nullptr);
if (!fs) {
fs = NewDisplayState(filePath);
fs->useDefaultState = true;
} else {
states->Remove(fs);
fs->isMissing = false;
}
states->InsertAt(0, fs);
fs->openCount++;
return fs;
}
bool FileHistory::MarkFileInexistent(const char* filePath, bool hide) const {
CrashIf(!filePath);
FileState* state = Find(filePath, nullptr);
if (!state) {
return false;
}
// move the file history entry to the end of the list
// of recently opened documents (if it exists at all),
// so that the user could still try opening it again
// and so that we don't completely forget the settings,
// should the file reappear later on
int newIdx = hide ? INT_MAX : kFileHistoryMaxRecent - 1;
int idx = states->Find(state);
if (idx < newIdx && state != states->Last()) {
states->Remove(state);
if (states->size() <= (size_t)newIdx) {
states->Append(state);
} else {
states->InsertAt(newIdx, state);
}
}
// also delete the thumbnail and move the link towards the
// back in the Frequently Read list
delete state->thumbnail;
state->thumbnail = nullptr;
state->openCount >>= 2;
state->isMissing = hide;
return true;
}
// returns a shallow copy of the file history list, sorted
// by open count (which has a pre-multiplied recency factor)
// and with all missing states filtered out
// caller needs to delete the result (but not the contained states)
void FileHistory::GetFrequencyOrder(Vec<FileState*>& list) const {
CrashIf(list.size() > 0);
size_t i = 0;
for (FileState* ds : *states) {
ds->index = i++;
if (!ds->isMissing || ds->isPinned) {
list.Append(ds);
}
}
list.Sort(cmpOpenCount);
}
// removes file history entries which shouldn't be saved anymore
// (see the loop below for the details)
void FileHistory::Purge(bool alwaysUseDefaultState) const {
// minOpenCount is set to the number of times a file must have been
// opened to be kept (provided that there is no other valuable
// information about the file to be remembered)
int minOpenCount = 0;
if (alwaysUseDefaultState) {
Vec<FileState*> frequencyList;
GetFrequencyOrder(frequencyList);
if (frequencyList.size() > kFileHistoryMaxRecent) {
minOpenCount = frequencyList.at(kFileHistoryMaxFrequent)->openCount / 2;
}
}
for (size_t j = states->size(); j > 0; j--) {
FileState* state = states->at(j - 1);
// never forget pinned documents, documents we've remembered a password for and
// documents for which there are favorites
if (state->isPinned || state->decryptionKey != nullptr || state->favorites->size() > 0) {
continue;
}
if (state->isMissing && (alwaysUseDefaultState || state->useDefaultState)) {
// forget about missing documents without valuable state
states->RemoveAt(j - 1);
} else if (j > FILE_HISTORY_MAX_FILES) {
// forget about files last opened longer ago than the last FILE_HISTORY_MAX_FILES ones
states->RemoveAt(j - 1);
} else if (alwaysUseDefaultState && state->openCount < minOpenCount && j > kFileHistoryMaxRecent) {
// forget about files that were hardly used (and without valuable state)
states->RemoveAt(j - 1);
} else {
continue;
}
DeleteDisplayState(state);
}
}