Skip to content

Commit 79f5188

Browse files
committed
Merge branch 'mr/tmpfile' into 'master'
Add new functions Load_Index and Save_Index in GNATCOLL.File_Indexes See merge request eng/toolchain/gnatcoll-core!216
2 parents 9714cf6 + 7765be7 commit 79f5188

14 files changed

+375
-95
lines changed

core/src/gnatcoll-file_indexes.adb

Lines changed: 225 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,25 @@
2222
------------------------------------------------------------------------------
2323

2424
with GNAT.OS_Lib;
25+
with GNATCOLL.JSON;
26+
with GNATCOLL.OS.FS;
27+
with Ada.Calendar;
28+
with Ada.Strings.Unbounded;
2529

2630
package body GNATCOLL.File_Indexes is
2731

32+
package JSON renames GNATCOLL.JSON;
33+
package FS renames GNATCOLL.OS.FS;
34+
35+
JSON_INDEX_MIMETYPE : constant String := "text/json+file-index-1.0";
36+
2837
procedure Internal_Hash
29-
(Self : in out File_Index;
30-
Normalized_Path : UTF8.UTF_8_String;
31-
Attrs : Stat.File_Attributes;
32-
State : out Entry_State;
33-
Digest : out File_Index_Digest);
38+
(Self : in out File_Index;
39+
Normalized_Path : UTF8.UTF_8_String;
40+
Attrs : Stat.File_Attributes;
41+
State : out Entry_State;
42+
Digest : out File_Index_Digest;
43+
Trust_Cache : Boolean := False);
3444

3545
-----------------
3646
-- Clear_Cache --
@@ -47,56 +57,71 @@ package body GNATCOLL.File_Indexes is
4757
----------
4858

4959
function Hash
50-
(Self : in out File_Index;
51-
Path : UTF8.UTF_8_String)
60+
(Self : in out File_Index;
61+
Path : UTF8.UTF_8_String;
62+
Trust_Cache : Boolean := False)
5263
return File_Index_Digest
5364
is
5465
State : Entry_State;
5566
Digest : File_Index_Digest;
5667
begin
57-
Hash (Self => Self, Path => Path, State => State, Digest => Digest);
68+
Hash
69+
(Self => Self,
70+
Path => Path,
71+
State => State,
72+
Digest => Digest,
73+
Trust_Cache => Trust_Cache);
5874
return Digest;
5975
end Hash;
6076

6177
procedure Hash
6278
(Self : in out File_Index;
6379
Path : UTF8.UTF_8_String;
6480
State : out Entry_State;
65-
Digest : out File_Index_Digest)
81+
Digest : out File_Index_Digest;
82+
Trust_Cache : Boolean := False)
6683
is
6784
Normalized_Path : constant String := GNAT.OS_Lib.Normalize_Pathname
6885
(Path, Resolve_Links => False);
6986
begin
7087
Internal_Hash
71-
(Self, Normalized_Path, Stat.Stat (Normalized_Path), State, Digest);
88+
(Self,
89+
Normalized_Path,
90+
Stat.Stat (Normalized_Path),
91+
State,
92+
Digest,
93+
Trust_Cache);
7294
end Hash;
7395

7496
procedure Hash
75-
(Self : in out File_Index;
76-
Path : UTF8.UTF_8_String;
77-
Attrs : Stat.File_Attributes;
78-
State : out Entry_State;
79-
Digest : out File_Index_Digest)
97+
(Self : in out File_Index;
98+
Path : UTF8.UTF_8_String;
99+
Attrs : Stat.File_Attributes;
100+
State : out Entry_State;
101+
Digest : out File_Index_Digest;
102+
Trust_Cache : Boolean := False)
80103
is
81104
begin
82105
Internal_Hash
83106
(Self,
84107
GNAT.OS_Lib.Normalize_Pathname (Path, Resolve_Links => False),
85108
Attrs,
86109
State,
87-
Digest);
110+
Digest,
111+
Trust_Cache);
88112
end Hash;
89113

90114
-------------------
91115
-- Internal_Hash --
92116
-------------------
93117

94118
procedure Internal_Hash
95-
(Self : in out File_Index;
96-
Normalized_Path : UTF8.UTF_8_String;
97-
Attrs : Stat.File_Attributes;
98-
State : out Entry_State;
99-
Digest : out File_Index_Digest)
119+
(Self : in out File_Index;
120+
Normalized_Path : UTF8.UTF_8_String;
121+
Attrs : Stat.File_Attributes;
122+
State : out Entry_State;
123+
Digest : out File_Index_Digest;
124+
Trust_Cache : Boolean := False)
100125
is
101126
use File_Maps;
102127
use type Stat.File_Attributes;
@@ -106,40 +131,48 @@ package body GNATCOLL.File_Indexes is
106131
Prev_Cursor : Cursor := Find (Self.DB, Normalized_Path);
107132
Prev_Hash : File_Index_Digest := No_Digest;
108133
New_Hash : File_Index_Digest := No_Digest;
134+
Prev : Index_Element;
109135
Trust_New_Hash : Boolean := True;
110136
begin
111137

112138
if Prev_Cursor /= No_Element then
113-
declare
114-
Prev : constant Index_Element := Element (Prev_Cursor);
115-
begin
116-
if Prev.Trust_Hash and then Attrs = Prev.Attrs then
117-
State := UNCHANGED_FILE;
118-
Digest := Prev.Hash_Digest;
119-
return;
120-
end if;
139+
Prev := Element (Prev_Cursor);
121140

122-
-- Two possibilities:
123-
-- - Hash is going to be recomputed
124-
-- - File does not exist anymore
125-
-- In both cases, previous file length must be removed
126-
-- from the total length.
141+
if Prev.Trust_Hash and then Attrs = Prev.Attrs then
142+
State := UNCHANGED_FILE;
143+
Digest := Prev.Hash_Digest;
127144

128-
Self.Total_Size := Self.Total_Size - Stat.Length (Prev.Attrs);
145+
-- Using a pre-existing element, so mark it as still in use so
146+
-- requiring to be saved on disk.
129147

130-
if not Stat.Exists (Attrs) then
131-
Delete (Self.DB, Prev_Cursor);
132-
State := REMOVED_FILE;
133-
Digest := No_Digest;
134-
return;
148+
if not Prev.Save_On_Disk then
149+
Prev.Save_On_Disk := True;
150+
Self.DB.Replace_Element (Prev_Cursor, Prev);
135151
end if;
136152

137-
-- default state is now UPDATED_FILE
138-
State := UPDATED_FILE;
153+
return;
154+
end if;
155+
156+
-- Two possibilities:
157+
-- - Hash is going to be recomputed
158+
-- - File does not exist anymore
159+
-- In both cases, previous file length must be removed
160+
-- from the total length.
161+
162+
Self.Total_Size := Self.Total_Size - Stat.Length (Prev.Attrs);
139163

140-
-- Keep track of prev hash
141-
Prev_Hash := Prev.Hash_Digest;
142-
end;
164+
if not Stat.Exists (Attrs) then
165+
Delete (Self.DB, Prev_Cursor);
166+
State := REMOVED_FILE;
167+
Digest := No_Digest;
168+
return;
169+
end if;
170+
171+
-- default state is now UPDATED_FILE
172+
State := UPDATED_FILE;
173+
174+
-- Keep track of prev hash
175+
Prev_Hash := Prev.Hash_Digest;
143176
else
144177
-- This is a new file
145178
State := NEW_FILE;
@@ -161,11 +194,12 @@ package body GNATCOLL.File_Indexes is
161194
-- modified less than 1s ago, there is a possible race condition in
162195
-- which the file is modified again in the same second after we updated
163196
-- the File_Index DB. In those cases don't trust the hash (i.e: always
164-
-- recompute it in the next query).
165-
Trust_New_Hash :=
166-
(Ada.Calendar.Clock - Stat.Modification_Time (Attrs)) > 1.0;
197+
-- recompute it in the next query), unless the caller explicitly specify
198+
-- that the value is to be trusted.
199+
Trust_New_Hash := Trust_Cache
200+
or else (Ada.Calendar.Clock - Stat.Modification_Time (Attrs)) > 1.0;
167201

168-
-- Compute Hash
202+
-- Add the new entry
169203
Include
170204
(Self.DB,
171205
Normalized_Path,
@@ -194,4 +228,146 @@ package body GNATCOLL.File_Indexes is
194228
return Self.Total_Size;
195229
end Indexed_Content_Size;
196230

231+
----------------
232+
-- Save_Index --
233+
----------------
234+
235+
procedure Save_Index (Self : File_Index; Filename : UTF8.UTF_8_String)
236+
is
237+
use File_Maps;
238+
Result : constant JSON.JSON_Value := JSON.Create_Object;
239+
JSON_DB : constant JSON.JSON_Value := JSON.Create_Object;
240+
Result_Str : Ada.Strings.Unbounded.Unbounded_String;
241+
FD : FS.File_Descriptor;
242+
243+
begin
244+
-- The mime field is only used by the Load_Index function and ensures
245+
-- we don't try to load a file a distinct format
246+
Result.Set_Field ("mimetype", JSON_INDEX_MIMETYPE);
247+
248+
-- Dump global data
249+
Result.Set_Field ("total_size", JSON.Create (Self.Total_Size));
250+
251+
-- Iterate over the database
252+
declare
253+
C : Cursor := First (Self.DB);
254+
begin
255+
while C /= No_Element loop
256+
-- For each element the following data structure is created:
257+
--
258+
-- {
259+
-- "trust": bool,
260+
-- "hash": str,
261+
-- "stat": [...] # stat information
262+
-- }
263+
declare
264+
El : constant Index_Element := Element (C);
265+
JSON_El : constant JSON.JSON_Value := JSON.Create_Object;
266+
Stat_Data : constant JSON.JSON_Value := JSON.Create
267+
(JSON.Empty_Array);
268+
begin
269+
if El.Save_On_Disk then
270+
JSON_El.Set_Field ("trust", El.Trust_Hash);
271+
JSON_El.Set_Field ("hash", El.Hash_Digest);
272+
273+
Stat_Data.Append (JSON.Create (Stat.Exists (El.Attrs)));
274+
Stat_Data.Append (JSON.Create (Stat.Is_Writable (El.Attrs)));
275+
Stat_Data.Append (JSON.Create (Stat.Is_Readable (El.Attrs)));
276+
Stat_Data.Append
277+
(JSON.Create (Stat.Is_Executable (El.Attrs)));
278+
Stat_Data.Append
279+
(JSON.Create (Stat.Is_Symbolic_Link (El.Attrs)));
280+
Stat_Data.Append (JSON.Create (Stat.Is_File (El.Attrs)));
281+
Stat_Data.Append
282+
(JSON.Create (Stat.Is_Directory (El.Attrs)));
283+
Stat_Data.Append
284+
(JSON.Create (Stat.Modification_Stamp (El.Attrs)));
285+
Stat_Data.Append (JSON.Create (Stat.Length (El.Attrs)));
286+
JSON_El.Set_Field ("stat", Stat_Data);
287+
288+
JSON_DB.Set_Field (Key (C), JSON_El);
289+
end if;
290+
end;
291+
292+
C := Next (C);
293+
end loop;
294+
end;
295+
Result.Set_Field ("db", JSON_DB);
296+
297+
-- Write the final JSON
298+
Result_Str := JSON.Write (Result, Compact => False);
299+
FD := FS.Open (Filename, Mode => FS.Write_Mode);
300+
FS.Write_Unbounded (FD, Result_Str);
301+
FS.Close (FD);
302+
end Save_Index;
303+
304+
----------------
305+
-- Load_Index --
306+
----------------
307+
308+
function Load_Index (Filename : UTF8.UTF_8_String) return File_Index
309+
is
310+
JSON_Result : JSON.Read_Result;
311+
JSON_Data : JSON.JSON_Value;
312+
Result : File_Index;
313+
314+
procedure Process_Entry
315+
(Name : JSON.UTF8_String; Value : JSON.JSON_Value);
316+
-- Function called on each file entry
317+
318+
procedure Process_Entry
319+
(Name : JSON.UTF8_String; Value : JSON.JSON_Value)
320+
is
321+
V : Index_Element;
322+
JSON_Stat : constant JSON.JSON_Array := JSON.Get (Value, "stat");
323+
begin
324+
V :=
325+
(Attrs => Stat.New_File_Attributes
326+
(Exists => JSON.Get (JSON.Get (JSON_Stat, 1)),
327+
Writable => JSON.Get (JSON.Get (JSON_Stat, 2)),
328+
Readable => JSON.Get (JSON.Get (JSON_Stat, 3)),
329+
Executable => JSON.Get (JSON.Get (JSON_Stat, 4)),
330+
Symbolic_Link => JSON.Get (JSON.Get (JSON_Stat, 5)),
331+
Regular => JSON.Get (JSON.Get (JSON_Stat, 6)),
332+
Directory => JSON.Get (JSON.Get (JSON_Stat, 7)),
333+
Stamp => JSON.Get (JSON.Get (JSON_Stat, 8)),
334+
Length => JSON.Get (JSON.Get (JSON_Stat, 9))),
335+
Hash_Digest => JSON.Get (Value, "hash"),
336+
Trust_Hash => JSON.Get (Value, "trust"),
337+
Save_On_Disk => False);
338+
Result.DB.Include (Name, V);
339+
end Process_Entry;
340+
341+
begin
342+
JSON_Result := JSON.Read_File (Filename);
343+
344+
if not JSON_Result.Success then
345+
-- Silently ignore index errors
346+
return Result;
347+
end if;
348+
349+
JSON_Data := JSON_Result.Value;
350+
351+
if not JSON.Has_Field (JSON_Data, "mimetype") or else
352+
not JSON.Has_Field (JSON_Data, "total_size")
353+
then
354+
return Result;
355+
end if;
356+
357+
declare
358+
Mimetype : constant String := JSON.Get (JSON_Data, "mimetype");
359+
begin
360+
if Mimetype /= JSON_INDEX_MIMETYPE then
361+
return Result;
362+
end if;
363+
364+
Result.Total_Size :=
365+
JSON.Get (JSON.Get (JSON_Data, "total_size"));
366+
367+
JSON.Map_JSON_Object
368+
(JSON.Get (JSON_Data, "db"), Process_Entry'Unrestricted_Access);
369+
end;
370+
return Result;
371+
end Load_Index;
372+
197373
end GNATCOLL.File_Indexes;

0 commit comments

Comments
 (0)