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