LCOV - code coverage report
Current view: top level - io/source - io_data_file.c (source / functions) Hit Total Coverage
Test: crystal-facet-uml_v1.62.0_covts Lines: 153 206 74.3 %
Date: 2024-12-21 18:34:41 Functions: 7 9 77.8 %

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

Generated by: LCOV version 1.16