Line data Source code
1 : /* File: io_data_file.c; Copyright and License: see below */
2 :
3 : #include "io_data_file.h"
4 : #include "io_exporter.h"
5 : #include "io_file_format.h"
6 : #include "io_importer.h"
7 : #include "io_import_mode.h"
8 : #include "entity/data_head.h"
9 : #include "entity/data_head_key.h"
10 : #include "storage/data_database_head.h"
11 : #include "u8dir/u8dir_file.h"
12 : #include "u8stream/universal_file_input_stream.h"
13 : #include "u8stream/universal_null_output_stream.h"
14 : #include "u8stream/universal_output_stream.h"
15 : #include "u8/u8_trace.h"
16 : #include "u8/u8_log.h"
17 : #include <assert.h>
18 :
19 : static const char *IO_DATA_FILE_TEMP_EXT = ".tmp-cfu";
20 : static const char *IO_DATA_FILE_JSON_EXT = ".cfuJ";
21 :
22 3 : void io_data_file_init ( io_data_file_t *this_ )
23 : {
24 3 : U8_TRACE_BEGIN();
25 :
26 3 : data_database_init( &((*this_).database) );
27 3 : ctrl_controller_init( &((*this_).controller), &((*this_).database) );
28 :
29 : (*this_).data_file_name
30 3 : = utf8stringbuf_new( (*this_).private_data_file_name_buffer, sizeof((*this_).private_data_file_name_buffer) );
31 3 : utf8stringbuf_clear( &((*this_).data_file_name) );
32 :
33 : (*this_).db_file_name
34 3 : = utf8stringbuf_new( (*this_).private_db_file_name_buffer, sizeof((*this_).private_db_file_name_buffer) );
35 3 : utf8stringbuf_clear( &((*this_).db_file_name) );
36 :
37 3 : (*this_).auto_writeback_to_json = false;
38 3 : (*this_).delete_db_when_finished = false;
39 3 : (*this_).sync_revision = 0;
40 :
41 3 : U8_TRACE_END();
42 3 : }
43 :
44 3 : void io_data_file_destroy ( io_data_file_t *this_ )
45 : {
46 3 : U8_TRACE_BEGIN();
47 :
48 3 : ctrl_controller_destroy( &((*this_).controller) );
49 3 : data_database_destroy( &((*this_).database) );
50 :
51 3 : U8_TRACE_END();
52 3 : }
53 :
54 4 : u8_error_t io_data_file_open ( io_data_file_t *this_,
55 : const char* requested_file_path,
56 : bool read_only,
57 : u8_error_info_t *out_err_info )
58 : {
59 4 : U8_TRACE_BEGIN();
60 4 : assert( requested_file_path != NULL );
61 4 : assert( out_err_info != NULL );
62 4 : u8_error_info_init_void( out_err_info );
63 4 : const utf8stringview_t req_file_path = UTF8STRINGVIEW_STR(requested_file_path);
64 : utf8stringview_t req_file_parent;
65 : utf8stringview_t req_file_name;
66 : utf8stringview_t req_file_basename;
67 : utf8stringview_t req_file_extension;
68 4 : io_data_file_private_split_path( this_, &req_file_path, &req_file_parent, &req_file_name );
69 4 : io_data_file_private_split_extension( this_, &req_file_name, &req_file_basename, &req_file_extension );
70 :
71 4 : const bool temp_requested = utf8string_ends_with_str( requested_file_path, IO_DATA_FILE_TEMP_EXT );
72 : bool is_json;
73 4 : const u8_error_t file_not_readable = io_data_file_private_guess_db_type( this_, requested_file_path, &is_json );
74 4 : u8_error_t err = U8_ERROR_NONE;
75 :
76 4 : if ( file_not_readable != U8_ERROR_NONE )
77 : {
78 2 : if (( temp_requested )||( ! is_json ))
79 : {
80 0 : U8_TRACE_INFO( "CASE: use temp db file that does not exist, is not accessible or has wrong format" );
81 0 : U8_TRACE_INFO( read_only ? "read_only" : "writeable" );
82 : /* This is a strange request, but we can create such an sqlite file. */
83 : /* To be consistent with the case of opening an existing temporary file, also this is exported to json later */
84 0 : (*this_).auto_writeback_to_json = ( ! read_only );
85 0 : (*this_).delete_db_when_finished = ( ! read_only );
86 :
87 0 : err |= utf8stringbuf_copy_view( &((*this_).data_file_name), &req_file_parent );
88 0 : err |= utf8stringbuf_append_view( &((*this_).data_file_name), &req_file_basename );
89 0 : err |= utf8stringbuf_append_str( &((*this_).data_file_name), IO_DATA_FILE_JSON_EXT );
90 :
91 0 : err |= utf8stringbuf_copy_str( &((*this_).db_file_name), requested_file_path );
92 : }
93 : else
94 : {
95 2 : U8_TRACE_INFO( "CASE: use json file that does not exist or is not accessible" );
96 2 : U8_TRACE_INFO( read_only ? "read_only" : "writeable" );
97 : /* A new json file shall be created */
98 2 : (*this_).auto_writeback_to_json = ( ! read_only );
99 2 : (*this_).delete_db_when_finished = ( ! read_only );
100 2 : err |= utf8stringbuf_copy_str( &((*this_).data_file_name), requested_file_path );
101 2 : err |= utf8stringbuf_copy_view( &((*this_).db_file_name), &req_file_parent );
102 2 : err |= utf8stringbuf_append_view( &((*this_).db_file_name), &req_file_basename );
103 2 : err |= utf8stringbuf_append_str( &((*this_).db_file_name), IO_DATA_FILE_TEMP_EXT );
104 2 : u8dir_file_remove( utf8stringbuf_get_string( &((*this_).db_file_name) ) ); /* ignore possible errors */
105 : }
106 : }
107 : else
108 : {
109 2 : if ( temp_requested )
110 : {
111 0 : U8_TRACE_INFO_STR( "CASE: use existing temp file", read_only ? "read_only" : "writeable" );
112 : /* A temporary sqlite file shall be used and later be exported to json */
113 0 : (*this_).auto_writeback_to_json = ( ! read_only );
114 0 : (*this_).delete_db_when_finished = ( ! read_only );
115 0 : err |= utf8stringbuf_copy_view( &((*this_).data_file_name), &req_file_parent );
116 0 : err |= utf8stringbuf_append_view( &((*this_).data_file_name), &req_file_basename );
117 0 : err |= utf8stringbuf_append_str( &((*this_).data_file_name), IO_DATA_FILE_JSON_EXT );
118 0 : err |= utf8stringbuf_copy_str( &((*this_).db_file_name), requested_file_path );
119 : }
120 2 : else if ( is_json )
121 : {
122 2 : U8_TRACE_INFO_STR( "CASE: use existing json file", read_only ? "read_only" : "writeable" );
123 : /* An existing json file shall be used */
124 2 : (*this_).auto_writeback_to_json = ( ! read_only );
125 2 : (*this_).delete_db_when_finished = true;
126 2 : err |= utf8stringbuf_copy_str( &((*this_).data_file_name), requested_file_path );
127 2 : err |= utf8stringbuf_copy_view( &((*this_).db_file_name), &req_file_parent );
128 2 : err |= utf8stringbuf_append_view( &((*this_).db_file_name), &req_file_basename );
129 2 : err |= utf8stringbuf_append_str( &((*this_).db_file_name), IO_DATA_FILE_TEMP_EXT );
130 2 : u8dir_file_remove( utf8stringbuf_get_string( &((*this_).db_file_name) ) ); /* ignore possible errors */
131 :
132 2 : err |= data_database_open( &((*this_).database), utf8stringbuf_get_string( &((*this_).db_file_name) ) );
133 2 : if ( err != U8_ERROR_NONE )
134 : {
135 0 : U8_LOG_ERROR("An error occurred at creating a temporary database file, possibly the parent directory is read-only.")
136 0 : U8_LOG_WARNING("Changes will not be written back to not accidentally overwrite the data source")
137 0 : (*this_).auto_writeback_to_json = false;
138 0 : (*this_).delete_db_when_finished = true; /* do not keep .tmp-cfu files when import was not successful */
139 : }
140 : else
141 : {
142 2 : err |= io_data_file_private_import( this_, utf8stringbuf_get_string( &((*this_).data_file_name) ), out_err_info );
143 2 : err |= data_database_close( &((*this_).database) );
144 :
145 2 : if ( err != U8_ERROR_NONE )
146 : {
147 1 : U8_LOG_ERROR("An error occurred at reading a json data file")
148 1 : u8dir_file_remove( utf8stringbuf_get_string( &((*this_).db_file_name) ) ); /* ignore possible additional errors */
149 1 : U8_LOG_WARNING("Changes will not be written back to not accidentally overwrite the data source")
150 1 : (*this_).auto_writeback_to_json = false;
151 1 : (*this_).delete_db_when_finished = true; /* do not keep .tmp-cfu files when import was not successful */
152 : }
153 : }
154 : }
155 : else
156 : {
157 0 : U8_TRACE_INFO_STR( "CASE: use existing sqlite file", read_only ? "read_only" : "writeable" );
158 : /* An sqlite file shall be used */
159 0 : (*this_).auto_writeback_to_json = false;
160 0 : (*this_).delete_db_when_finished = false;
161 0 : err |= utf8stringbuf_copy_str( &((*this_).data_file_name), requested_file_path );
162 0 : err |= utf8stringbuf_copy_str( &((*this_).db_file_name), requested_file_path );
163 : }
164 : }
165 4 : U8_TRACE_INFO_STR( "data_file_name:", utf8stringbuf_get_string( &((*this_).data_file_name) ) );
166 4 : U8_TRACE_INFO_STR( "db_file_name: ", utf8stringbuf_get_string( &((*this_).db_file_name) ) );
167 :
168 4 : if ( err == U8_ERROR_NONE )
169 : {
170 3 : if ( read_only )
171 : {
172 0 : err |= data_database_open_read_only( &((*this_).database), utf8stringbuf_get_string( &((*this_).db_file_name) ) );
173 : }
174 : else
175 : {
176 3 : err |= data_database_open( &((*this_).database), utf8stringbuf_get_string( &((*this_).db_file_name) ) );
177 : }
178 : }
179 :
180 : /* Reading the DATA_HEAD_KEY_DATA_FILE_NAME from the just opened (*this_).db_file_name */
181 : /* If found, update (*this_).data_file_name; otherwise if there is a json-writeback file, store the name */
182 4 : if ( err == U8_ERROR_NONE )
183 : {
184 : data_database_head_t head_table;
185 3 : data_database_head_init( &head_table, &((*this_).database) );
186 : data_head_t head;
187 3 : u8_error_t key_err = data_database_head_read_value_by_key( &head_table, DATA_HEAD_KEY_DATA_FILE_NAME, &head );
188 3 : if ( key_err == U8_ERROR_NONE )
189 : {
190 0 : U8_TRACE_INFO_STR( "DATA_FILE_NAME:", data_head_get_value_const( &head ) );
191 : /* set the data_file_name to the read head value */
192 0 : err |= utf8stringbuf_copy_view( &((*this_).data_file_name), &req_file_parent );
193 0 : err |= utf8stringbuf_append_str( &((*this_).data_file_name), data_head_get_value_const( &head ) );
194 : }
195 3 : else if ( (*this_).auto_writeback_to_json )
196 : {
197 3 : const char *const requested_file_name = utf8stringview_get_start( &req_file_name ); /* This view is null terminated */
198 3 : data_head_init_new( &head, DATA_HEAD_KEY_DATA_FILE_NAME, requested_file_name );
199 3 : err |= data_database_head_create_value( &head_table, &head, NULL );
200 : }
201 3 : data_database_head_destroy( &head_table );
202 :
203 3 : (*this_).sync_revision = data_database_get_revision( &((*this_).database) );
204 : }
205 :
206 4 : U8_TRACE_END_ERR( err );
207 4 : return err;
208 : }
209 :
210 2 : u8_error_t io_data_file_close ( io_data_file_t *this_ )
211 : {
212 2 : U8_TRACE_BEGIN();
213 :
214 2 : u8_error_t result = U8_ERROR_NONE;
215 :
216 2 : if ( (*this_).auto_writeback_to_json )
217 : {
218 2 : U8_TRACE_INFO( "CASE: auto_writeback_to_json == true" );
219 2 : result |= io_data_file_private_export( this_, utf8stringbuf_get_string( &((*this_).data_file_name) ) );
220 : }
221 :
222 2 : result |= data_database_close( &((*this_).database) );
223 :
224 2 : if ( (*this_).delete_db_when_finished )
225 : {
226 2 : U8_TRACE_INFO( "CASE: delete_db_when_finished == true" );
227 2 : u8dir_file_remove( utf8stringbuf_get_string( &((*this_).db_file_name) ) ); /* ignore possible errors */
228 : }
229 :
230 2 : (*this_).auto_writeback_to_json = false;
231 2 : (*this_).delete_db_when_finished = false;
232 :
233 2 : U8_TRACE_END_ERR( result );
234 2 : return result;
235 : }
236 :
237 0 : u8_error_t io_data_file_sync_to_disk ( io_data_file_t *this_ )
238 : {
239 0 : U8_TRACE_BEGIN();
240 :
241 0 : u8_error_t result = data_database_flush_caches( &((*this_).database) );
242 :
243 0 : if ( (*this_).auto_writeback_to_json )
244 : {
245 0 : result |= io_data_file_private_export( this_, utf8stringbuf_get_string( &((*this_).data_file_name) ) );
246 : }
247 :
248 0 : U8_TRACE_END_ERR( result );
249 0 : return result;
250 : }
251 :
252 0 : u8_error_t io_data_file_trace_stats ( io_data_file_t *this_ )
253 : {
254 0 : U8_TRACE_BEGIN();
255 :
256 0 : U8_TRACE_INFO_STR( "io_data_file_t:", utf8stringbuf_get_string( &((*this_).data_file_name) ) );
257 :
258 0 : const u8_error_t result = data_database_trace_stats( &((*this_).database) );
259 :
260 0 : U8_TRACE_END_ERR( result );
261 0 : return result;
262 : }
263 :
264 4 : u8_error_t io_data_file_private_guess_db_type ( const io_data_file_t *this_, const char *filename, bool *out_json )
265 : {
266 4 : U8_TRACE_BEGIN();
267 4 : assert( filename != NULL );
268 4 : assert( out_json != NULL );
269 4 : u8_error_t scan_head_error = U8_ERROR_NONE;
270 :
271 : /* open file */
272 : universal_file_input_stream_t in_file;
273 4 : universal_file_input_stream_init( &in_file );
274 4 : scan_head_error |= universal_file_input_stream_open( &in_file, filename );
275 :
276 : /* import from stream */
277 4 : if ( scan_head_error == U8_ERROR_NONE )
278 : {
279 : char file_prefix[16];
280 : size_t prefix_size;
281 : assert( sizeof(file_prefix) == sizeof(DATA_DATABASE_SQLITE3_MAGIC) );
282 2 : scan_head_error = universal_file_input_stream_read( &in_file, &file_prefix, sizeof(file_prefix), &prefix_size );
283 2 : if (( scan_head_error == U8_ERROR_NONE )&&( prefix_size == sizeof(file_prefix) )
284 2 : &&( 0 == memcmp( &file_prefix, &DATA_DATABASE_SQLITE3_MAGIC, sizeof(file_prefix) ) ))
285 : {
286 0 : U8_TRACE_INFO_STR("File exists and starts with sqlite3 magic:", filename);
287 0 : *out_json = false;
288 : }
289 : else
290 : {
291 2 : U8_TRACE_INFO_STR("File exists and is not of type sqlite3:", filename);
292 2 : *out_json = true;
293 : }
294 :
295 : /* close file */
296 2 : scan_head_error |= universal_file_input_stream_close( &in_file );
297 : }
298 : else
299 : {
300 2 : U8_TRACE_INFO_STR("File does not exist", filename);
301 2 : *out_json = true;
302 : }
303 :
304 : /* cleanup */
305 4 : scan_head_error |= universal_file_input_stream_destroy( &in_file );
306 :
307 4 : U8_TRACE_END_ERR( scan_head_error );
308 4 : return scan_head_error;
309 : }
310 :
311 2 : u8_error_t io_data_file_private_import ( io_data_file_t *this_, const char *src_file, u8_error_info_t *out_err_info )
312 : {
313 2 : U8_TRACE_BEGIN();
314 2 : assert( src_file != NULL );
315 2 : assert( out_err_info != NULL );
316 2 : u8_error_info_init_void( out_err_info );
317 2 : u8_error_t import_err = U8_ERROR_NONE;
318 : static const io_import_mode_t import_mode = IO_IMPORT_MODE_CREATE|IO_IMPORT_MODE_LINK;
319 : universal_null_output_stream_t dev_null;
320 2 : universal_null_output_stream_init( &dev_null );
321 : utf8stream_writer_t out_null;
322 2 : utf8stream_writer_init( &out_null, universal_null_output_stream_get_output_stream( &dev_null ) );
323 :
324 2 : U8_TRACE_INFO_STR( "importing file:", src_file );
325 2 : if ( io_data_file_is_open( this_ ) )
326 : {
327 : static data_database_reader_t db_reader;
328 2 : data_database_reader_init( &db_reader, &((*this_).database) );
329 : static io_importer_t importer;
330 2 : io_importer_init( &importer, &db_reader, &((*this_).controller) );
331 : {
332 : data_stat_t import_stat;
333 2 : data_stat_init ( &import_stat );
334 2 : import_err = io_importer_import_file( &importer, import_mode, src_file, &import_stat, out_err_info, &out_null );
335 2 : data_stat_trace( &import_stat );
336 2 : data_stat_destroy ( &import_stat );
337 : }
338 2 : io_importer_destroy( &importer );
339 2 : data_database_reader_destroy( &db_reader );
340 : }
341 : else
342 : {
343 0 : import_err = U8_ERROR_NO_DB;
344 : }
345 :
346 2 : utf8stream_writer_destroy( &out_null );
347 2 : universal_null_output_stream_destroy( &dev_null );
348 2 : U8_TRACE_END_ERR( import_err );
349 2 : return import_err;
350 : }
351 :
352 2 : u8_error_t io_data_file_private_export ( io_data_file_t *this_, const char *dst_file )
353 : {
354 2 : U8_TRACE_BEGIN();
355 2 : assert( dst_file != NULL );
356 2 : u8_error_t export_err = U8_ERROR_NONE;
357 :
358 2 : U8_TRACE_INFO_STR( "exporting file:", dst_file );
359 2 : const char *document_filename = io_data_file_get_filename_const( this_ );
360 2 : if ( io_data_file_is_open( this_ ) )
361 : {
362 : static data_database_reader_t db_reader;
363 2 : data_database_reader_init( &db_reader, &((*this_).database) );
364 : static io_exporter_t exporter;
365 2 : io_exporter_init( &exporter, &db_reader );
366 : {
367 : data_stat_t export_stat;
368 2 : data_stat_init ( &export_stat );
369 2 : export_err = io_exporter_export_document_file( &exporter, IO_FILE_FORMAT_JSON, "title", document_filename, &export_stat );
370 2 : data_stat_trace( &export_stat );
371 2 : data_stat_destroy ( &export_stat );
372 : }
373 2 : io_exporter_destroy( &exporter );
374 2 : data_database_reader_destroy( &db_reader );
375 :
376 2 : (*this_).sync_revision = data_database_get_revision( &((*this_).database) );
377 : }
378 : else
379 : {
380 0 : export_err = U8_ERROR_NO_DB;
381 : }
382 :
383 2 : U8_TRACE_END_ERR( export_err );
384 2 : return export_err;
385 : }
386 :
387 :
388 : /*
389 : Copyright 2022-2025 Andreas Warnke
390 :
391 : Licensed under the Apache License, Version 2.0 (the "License");
392 : you may not use this file except in compliance with the License.
393 : You may obtain a copy of the License at
394 :
395 : http://www.apache.org/licenses/LICENSE-2.0
396 :
397 : Unless required by applicable law or agreed to in writing, software
398 : distributed under the License is distributed on an "AS IS" BASIS,
399 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
400 : See the License for the specific language governing permissions and
401 : limitations under the License.
402 : */
|