LCOV - code coverage report
Current view: top level - io/source - io_data_file.c (source / functions) Coverage Total Hit
Test: crystal-facet-uml_v1.63.2_covts Lines: 77.0 % 196 151
Test Date: 2025-05-01 10:10:14 Functions: 77.8 % 9 7

            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              : */
        

Generated by: LCOV version 2.0-1