2222-- ----------------------------------------------------------------------------
2323
2424with GNAT.OS_Lib ;
25+ with GNATCOLL.JSON ;
26+ with GNATCOLL.OS.FS ;
27+ with Ada.Calendar ;
28+ with Ada.Strings.Unbounded ;
2529
2630package 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+
197373end GNATCOLL.File_Indexes ;
0 commit comments