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