-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFileMonitor.js
More file actions
223 lines (190 loc) · 5.71 KB
/
FileMonitor.js
File metadata and controls
223 lines (190 loc) · 5.71 KB
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
// this window is supposed to monitor the state of the
// file requests, caches, loading etc
// its designed for the needs of the WebAPI, but really
// the system of caching & hot reloading is generic,
// we should have a layer on top of the raw filesystem access
// as well as the asset layer on top of that
export default class FileMonitorWindow
{
constructor(WindowRect=[0,0,200,400],FileCache=Pop.WebApi.FileCache)
{
this.FileCache = FileCache;
this.Window = new Pop.Gui.Window("FileMonitor",WindowRect);
this.FilterTextBox = new Pop.Gui.TextBox(this.Window,[2,2,"100%",20])
this.TableGui = new Pop.Gui.Table(this.Window,[4,24,"100%","100%"])
let Filter = Pop.GetExeArguments().FileMonitor;
if (Filter === false || Filter === true)
Filter = null;
this.FilterString = Filter || null;
this.FilterTextBox.SetLabel('Filter');
this.FilterTextBox.OnChanged = this.OnFilterChanged.bind(this);
this.FilterTextBox.SetValue(this.FilterString);
this.SizeMb = true;
this.UpdateLoop();
this.OnChanged();
}
SetMinimised()
{
this.Window.SetMinimised(...arguments);
}
async UpdateLoop()
{
while (true)
{
const ChangedFile = await this.FileCache.WaitForFileChange();
const ChangedFilename = ChangedFile.Filename;
//Pop.Debug(`File changed ${ChangedFilename}`);
this.OnChanged(ChangedFilename);
}
}
OnFilterChanged(NewFilter)
{
this.FilterString = NewFilter;
// validate filter
if (this.FilterString.length == 0)
this.FilterString = null;
this.OnChanged();
}
OnChanged()
{
function FilterRow(Row)
{
if (this.FilterString === null)
return true;
const FilterLower = this.FilterString.toLowerCase();
const FilenameLower = ('' +Row.Filename).toLowerCase();
const TypeLower = ('' +Row.Type).toLowerCase();
const SizeLower = (''+Row.Size).toLowerCase();
if (FilenameLower.includes(FilterLower))
return true;
if (TypeLower.includes(FilterLower))
return true;
if (SizeLower.includes(FilterLower))
return true;
return false;
}
// update display
// generate NxN table of labels
const Table = [];
const Push = function (Filename,Type,Size,LoadingPercent,Style=undefined,InsertAtTopOfArray=null)
{
const SizeBytes = Size;
if (Number.isInteger(Size))
{
if (this.SizeMb)
{
const mb = Size / 1024 / 1024;
Size = `${mb.toFixed(2)} mb`;
}
else
{
const kb = Size / 1024;
Size = `${kb.toFixed(2)} kb`;
}
}
const Row = {};
Row.Size = Size;
//Row.LoadingPercent = LoadingPercent;
Row.Filename = Filename;
Row.Type = Type;
Row.SizeBytes = SizeBytes;
Row.Style = Style;
if (InsertAtTopOfArray)
InsertAtTopOfArray.unshift(Row);
else
Table.push(Row);
}.bind(this);
// todo: retain an order (ideally of age)
for (let [Filename,Contents] of Object.entries(this.FileCache.Cache))
{
const FileMeta = this.FileCache.GetMeta(Filename);
// gr: null === object, so handle special case
let Type = (Contents===null) ? 'null' : typeof Contents;
let Size = undefined;
let Style = {};
if (Contents === false)
{
Type = 'Failed to load';
Style.backgroundColor = 'red';
}
else if (Array.isArray(Contents))
{
Size = Contents.length;
Type = 'Array';
}
else if (typeof Contents == 'string')
{
Size = Contents.length;
}
else if ( Contents === null ) // gr: typeof null == 'object' so below .constructor line will error
{
// if contents are null, we're downloading in chunks and haven't assembled yet
// we could assemble here, but then we're going to cause a drain...
if ( FileMeta.ContentChunks )
{
Size = FileMeta.PendingContentsSize;
Type = 'Streaming Chunks';
/*
// fill in missing data
const FirstChunk = FileMeta.ContentChunks[0];
if ( FirstChunk )
{
// always u8 array I think
Type = FirstChunk.constructor.name;
}
*/
}
}
else if (typeof Contents == 'object' && Contents.constructor)
{
Type = Contents.constructor.name;
// is a typed array
if (Contents.BYTES_PER_ELEMENT !== undefined)
{
// gr: should this be Contents.byteLength
Size = (Contents.length / Contents.BYTES_PER_ELEMENT);
}
if (Contents instanceof Pop.Image)
{
const w = Contents.GetWidth();
const h = Contents.GetHeight();
const Format = Contents.GetFormat();
Type = 'Pop.Image';
Size = `${w}x${h} ${Format}`;
}
}
// calc loading %
let LoadingPercent = undefined;
if ( Number.isInteger(Size) )
{
const TotalSize = FileMeta.Size;
// if undefined, we don't have a known size, or is not streaming
if ( Number.isInteger(TotalSize) )
LoadingPercent = Math.floor((Size / TotalSize)*100);
//else
// LoadingPercent = 100;
// set css style for loading %
}
Style.LoadingPercent = LoadingPercent;
Push(Filename,Type,Size,LoadingPercent,Style);
}
const FilteredTable = Table.filter(FilterRow.bind(this));
// add a total/stats entry for the filtered rows
{
function AddSize(Total,Cell)
{
Total += Number.isInteger(Cell.SizeBytes) ? Cell.SizeBytes : 0;
return Total;
}
const Summary = `Total ${FilteredTable.length}/${Table.length}`;
const TotalSize = FilteredTable.reduce(AddSize,0);
const Type = '';
const Style = {};
Style['font-style'] = 'italic';
const InsertAt0 = true;
const LoadingPercent = null;
Push(Summary,Type,TotalSize,LoadingPercent,Style,FilteredTable);
}
this.TableGui.SetValue(FilteredTable);
}
}