LCOV - code coverage report
Current view: directory - frmts/pcidsk/sdk/segment - sysblockmap.cpp (source / functions) Found Hit Coverage
Test: gdal_filtered.info Lines: 173 158 91.3 %
Date: 2012-04-28 Functions: 16 13 81.2 %

       1                 : /******************************************************************************
       2                 :  *
       3                 :  * Purpose:  Implementation of the SysBlockMap class.
       4                 :  *
       5                 :  * This class is used to manage access to the SYS virtual block map segment
       6                 :  * (named SysBMDir).  This segment is used to keep track of one or more 
       7                 :  * virtual files stored in SysBData segments.  These virtual files are normally
       8                 :  * used to hold tiled images for primary bands or overviews.  
       9                 :  *
      10                 :  * This class is closely partnered with the SysVirtualFile class, and the
      11                 :  * primary client is the CTiledChannel class. 
      12                 :  * 
      13                 :  ******************************************************************************
      14                 :  * Copyright (c) 2009
      15                 :  * PCI Geomatics, 50 West Wilmot Street, Richmond Hill, Ont, Canada
      16                 :  *
      17                 :  * Permission is hereby granted, free of charge, to any person obtaining a
      18                 :  * copy of this software and associated documentation files (the "Software"),
      19                 :  * to deal in the Software without restriction, including without limitation
      20                 :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      21                 :  * and/or sell copies of the Software, and to permit persons to whom the
      22                 :  * Software is furnished to do so, subject to the following conditions:
      23                 :  *
      24                 :  * The above copyright notice and this permission notice shall be included
      25                 :  * in all copies or substantial portions of the Software.
      26                 :  *
      27                 :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      28                 :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      29                 :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      30                 :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      31                 :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      32                 :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      33                 :  * DEALINGS IN THE SOFTWARE.
      34                 :  ****************************************************************************/
      35                 : 
      36                 : #include "pcidsk_exception.h"
      37                 : #include "pcidsk_file.h"
      38                 : #include "core/sysvirtualfile.h"
      39                 : #include "segment/sysblockmap.h"
      40                 : #include "core/cpcidskfile.h"
      41                 : 
      42                 : #include <cassert>
      43                 : #include <vector>
      44                 : #include <cstring>
      45                 : #include <cstdlib>
      46                 : 
      47                 : using namespace PCIDSK;
      48                 : 
      49                 : /************************************************************************/
      50                 : /*                            SysBlockMap()                             */
      51                 : /************************************************************************/
      52                 : 
      53              28 : SysBlockMap::SysBlockMap( PCIDSKFile *file, int segment,
      54                 :                               const char *segment_pointer )
      55              28 :         : CPCIDSKSegment( file, segment, segment_pointer )
      56                 : 
      57                 : {
      58              28 :     partial_loaded = false;
      59              28 :     full_loaded = false;
      60              28 :     dirty = false;
      61              28 :     growing_segment = 0;
      62              28 : }
      63                 : 
      64                 : /************************************************************************/
      65                 : /*                            ~SysBlockMap()                            */
      66                 : /************************************************************************/
      67                 : 
      68              28 : SysBlockMap::~SysBlockMap()
      69                 : 
      70                 : {
      71                 :     size_t i;
      72                 :     
      73              56 :     for( i = 0; i < virtual_files.size(); i++ )
      74                 :     {
      75              28 :         delete virtual_files[i];
      76              28 :         virtual_files[i] = NULL;
      77                 :     }
      78                 : 
      79              28 :     Synchronize();
      80              28 : }
      81                 : 
      82                 : /************************************************************************/
      83                 : /*                             Initialize()                             */
      84                 : /*                                                                      */
      85                 : /*      This method is used after creation of the SysBMDir segment      */
      86                 : /*      to fill in valid contents.  Prepares bare minimum contents.     */
      87                 : /************************************************************************/
      88                 : 
      89              10 : void SysBlockMap::Initialize()
      90                 : 
      91                 : {
      92              10 :     PCIDSKBuffer init_data(512);
      93                 : 
      94              10 :     init_data.Put( "VERSION  1", 0, 10 );
      95              10 :     init_data.Put( 0, 10, 8 );
      96              10 :     init_data.Put( 0, 18, 8 );
      97              10 :     init_data.Put( -1, 26, 8 );
      98              10 :     init_data.Put( "", 34, 512-34 );
      99                 : 
     100              10 :     WriteToFile( init_data.buffer, 0, init_data.buffer_size );
     101                 : #ifdef notdef
     102                 :     // arbitrarily grow the segment a bit to avoid having to move it too soon.
     103                 :     WriteToFile( "\0", 8191, 1 );              
     104                 : #endif
     105              10 : }
     106                 : 
     107                 : /************************************************************************/
     108                 : /*                            PartialLoad()                             */
     109                 : /*                                                                      */
     110                 : /*      Load the header and some per-layer information.                 */
     111                 : /************************************************************************/
     112                 : 
     113              86 : void SysBlockMap::PartialLoad()
     114                 : 
     115                 : {
     116              86 :     if( partial_loaded )
     117              58 :         return;
     118                 : 
     119                 : //    printf( "<PartialLoad>" );
     120                 : //    fflush( stdout );
     121                 : 
     122                 : /* -------------------------------------------------------------------- */
     123                 : /*      Load the 512 byte count section of the blockmap.                */
     124                 : /* -------------------------------------------------------------------- */
     125              28 :     PCIDSKBuffer count_data;
     126                 : 
     127              28 :     count_data.SetSize( 512 );
     128              28 :     ReadFromFile( count_data.buffer, 0, 512 );
     129                 : 
     130              28 :     if( strncmp(count_data.buffer,"VERSION",7) != 0 )
     131               0 :         ThrowPCIDSKException( "SysBlockMap::PartialLoad() - block map corrupt." );
     132                 : 
     133              28 :     if( count_data.GetInt( 7, 3 ) != 1 )
     134               0 :         ThrowPCIDSKException( "SysBlockMap::PartialLoad() - unsupported version." );
     135                 : 
     136                 : /* -------------------------------------------------------------------- */
     137                 : /*      Establish our SysVirtualFile array based on the number of       */
     138                 : /*      images listed in the image list.                                */
     139                 : /* -------------------------------------------------------------------- */
     140              28 :     int layer_count = count_data.GetInt( 10, 8 );
     141                 : 
     142              28 :     virtual_files.resize( layer_count );
     143                 : 
     144              28 :     block_count = count_data.GetInt( 18, 8 );
     145              28 :     first_free_block = count_data.GetInt( 26, 8 );
     146                 : 
     147                 : /* -------------------------------------------------------------------- */
     148                 : /*      Load the layer list definitions.  These are fairly small.       */
     149                 : /* -------------------------------------------------------------------- */
     150              28 :     layer_data.SetSize( 24 * layer_count );
     151                 :     ReadFromFile( layer_data.buffer, 
     152                 :                   512 + 28 * block_count, 
     153              28 :                   layer_data.buffer_size);
     154                 : 
     155              28 :     partial_loaded = true;
     156                 : 
     157                 : //    FullLoad();
     158                 : }
     159                 : 
     160                 : /************************************************************************/
     161                 : /*                              FullLoad()                              */
     162                 : /*                                                                      */
     163                 : /*      Load the blockmap data (can be large) into blockmap_data.       */
     164                 : /************************************************************************/
     165                 : 
     166              50 : void SysBlockMap::FullLoad()
     167                 : 
     168                 : {
     169              50 :     PartialLoad();
     170                 : 
     171              50 :     if( full_loaded )
     172              42 :         return;
     173                 : 
     174                 : //    printf( "<FullLoad>" );
     175                 : //    fflush( stdout );
     176                 : 
     177                 :     // TODO: this should likely be protected by a mutex. 
     178                 : 
     179                 : /* -------------------------------------------------------------------- */
     180                 : /*      Load the segment contents into a buffer.                        */
     181                 : /* -------------------------------------------------------------------- */
     182               8 :     blockmap_data.SetSize( block_count * 28 );
     183               8 :     ReadFromFile( blockmap_data.buffer, 512, blockmap_data.buffer_size );
     184                 : 
     185               8 :     full_loaded = true;
     186                 : }
     187                 : 
     188                 : /************************************************************************/
     189                 : /*                            Synchronize()                             */
     190                 : /************************************************************************/
     191                 : 
     192              44 : void SysBlockMap::Synchronize()
     193                 : 
     194                 : {
     195              44 :     if( !full_loaded || !dirty )
     196              36 :         return;
     197                 : 
     198               8 :     PCIDSKBuffer init_data(512);
     199                 : 
     200               8 :     init_data.Put( "VERSION  1", 0, 10 );
     201               8 :     init_data.Put( (int) virtual_files.size(), 10, 8 );
     202               8 :     init_data.Put( block_count, 18, 8 );
     203               8 :     init_data.Put( first_free_block, 26, 8 );
     204               8 :     init_data.Put( "", 34, 512-34 );
     205                 : 
     206               8 :     WriteToFile( init_data.buffer, 0, init_data.buffer_size );
     207                 : 
     208               8 :     WriteToFile( blockmap_data.buffer, 512, blockmap_data.buffer_size );
     209                 :     WriteToFile( layer_data.buffer, 512 + blockmap_data.buffer_size, 
     210               8 :                  layer_data.buffer_size );
     211                 : 
     212               8 :     dirty = false;
     213                 : }
     214                 : 
     215                 : /************************************************************************/
     216                 : /*                           AllocateBlocks()                           */
     217                 : /*                                                                      */
     218                 : /*      Allocate a bunch of new blocks and attach to the free list.     */
     219                 : /************************************************************************/
     220                 : 
     221               8 : void SysBlockMap::AllocateBlocks()
     222                 : 
     223                 : {
     224               8 :     FullLoad();
     225                 : 
     226                 : /* -------------------------------------------------------------------- */
     227                 : /*      Find a segment we can extend.  We consider any SYS segments     */
     228                 : /*      with a name of SysBData.                                        */
     229                 : /* -------------------------------------------------------------------- */
     230                 :     PCIDSKSegment *seg;
     231                 : 
     232               8 :     if( growing_segment > 0 )
     233                 :     {
     234               0 :         seg = file->GetSegment( growing_segment );
     235               0 :         if( !seg->IsAtEOF() )
     236               0 :             growing_segment = 0;
     237                 :     }
     238                 : 
     239               8 :     if( growing_segment == 0 )
     240                 :     {
     241                 :         PCIDSKSegment *seg;
     242               8 :         int  previous = 0;
     243                 : 
     244              16 :         while( (seg=file->GetSegment( SEG_SYS, "SysBData", previous )) != NULL )
     245                 :         {
     246               0 :             previous = seg->GetSegmentNumber();
     247                 :             
     248               0 :             if( seg->IsAtEOF() )
     249                 :             {
     250               0 :                 growing_segment = previous;
     251               0 :                 break;
     252                 :             }
     253                 :         }
     254                 :     }
     255                 : 
     256                 : /* -------------------------------------------------------------------- */
     257                 : /*      If we didn't find one, then create a new segment.               */
     258                 : /* -------------------------------------------------------------------- */
     259               8 :     if( growing_segment == 0 )
     260                 :     {
     261                 :         growing_segment = 
     262                 :             file->CreateSegment( "SysBData", 
     263                 :                                  "System Block Data for Tiles and Overviews "
     264                 :                                  "- Do not modify",
     265               8 :                                  SEG_SYS, 0L );
     266                 :     }
     267                 : 
     268                 : /* -------------------------------------------------------------------- */
     269                 : /*      Allocate another set of space.                                  */
     270                 : /* -------------------------------------------------------------------- */
     271               8 :     uint64 new_big_blocks = 16;
     272               8 :     uint64 new_bytes = new_big_blocks * SysVirtualFile::block_size;
     273               8 :     seg = file->GetSegment( growing_segment );
     274                 :     int block_index_in_segment = (int) 
     275               8 :         (seg->GetContentSize() / SysVirtualFile::block_size);
     276                 : 
     277               8 :     seg->WriteToFile( "\0", seg->GetContentSize() + new_bytes - 1, 1 );
     278                 :     
     279                 : /* -------------------------------------------------------------------- */
     280                 : /*      Resize the memory image of the blockmap.                        */
     281                 : /* -------------------------------------------------------------------- */
     282               8 :     if( 28 * (block_count + new_big_blocks) 
     283                 :         > (unsigned int) blockmap_data.buffer_size )
     284               8 :         blockmap_data.SetSize( (int) (28 * (block_count + new_big_blocks)) );
     285                 : 
     286                 : /* -------------------------------------------------------------------- */
     287                 : /*      Fill in info on the new blocks.                                 */
     288                 : /* -------------------------------------------------------------------- */
     289                 :     uint64 block_index;
     290                 : 
     291             136 :     for( block_index = block_count; 
     292                 :          block_index < block_count + new_big_blocks;
     293                 :          block_index++ )
     294                 :     {
     295             128 :         int bi_offset = (int) (block_index * 28);
     296                 : 
     297             128 :         blockmap_data.Put( growing_segment, bi_offset, 4 );
     298             128 :         blockmap_data.Put( block_index_in_segment++, bi_offset+4, 8 );
     299             128 :         blockmap_data.Put( -1, bi_offset+12, 8 );
     300                 : 
     301             128 :         if( block_index == block_count + new_big_blocks - 1 )
     302               8 :             blockmap_data.Put( -1, bi_offset+20, 8 );
     303                 :         else
     304             120 :             blockmap_data.Put( block_index+1, bi_offset+20, 8 );
     305                 :     }
     306                 : 
     307               8 :     first_free_block = block_count;
     308                 : 
     309               8 :     block_count += (int) new_big_blocks;
     310                 : 
     311               8 :     dirty = true;
     312               8 : }
     313                 : 
     314                 : /************************************************************************/
     315                 : /*                          GrowVirtualFile()                           */
     316                 : /*                                                                      */
     317                 : /*      Get one more block for this virtual file.                       */
     318                 : /************************************************************************/
     319                 : 
     320              10 : int SysBlockMap::GrowVirtualFile( int image, int &last_block, 
     321                 :                                   int &block_segment_ret )
     322                 : 
     323                 : {
     324              10 :     FullLoad();
     325                 : 
     326                 : /* -------------------------------------------------------------------- */
     327                 : /*      Do we need to create new free blocks?                           */
     328                 : /* -------------------------------------------------------------------- */
     329              10 :     if( first_free_block == -1 )
     330               8 :         AllocateBlocks();
     331                 : 
     332                 : /* -------------------------------------------------------------------- */
     333                 : /*      Return the first free block, and update the next pointer of     */
     334                 : /*      the previous block.                                             */
     335                 : /* -------------------------------------------------------------------- */
     336              10 :     int alloc_block = first_free_block;
     337                 : 
     338                 :     // update first free block to point to the next free block.
     339              10 :     first_free_block = blockmap_data.GetInt( alloc_block*28+20, 8);
     340                 : 
     341                 :     // mark block as owned by this layer/image. 
     342              10 :     blockmap_data.Put( image, alloc_block*28 + 12, 8 );
     343                 : 
     344                 :     // clear next free block on allocated block - it is the last in the chain
     345              10 :     blockmap_data.Put( -1, alloc_block*28 + 20, 8 );
     346                 : 
     347                 :     // point the previous "last block" for this virtual file to this new block
     348              10 :     if( last_block != -1 )
     349               2 :         blockmap_data.Put( alloc_block, last_block*28 + 20, 8 );
     350                 :     else
     351               8 :         layer_data.Put( alloc_block, image*24 + 4, 8 );
     352                 : 
     353              10 :     dirty = true;
     354                 : 
     355              10 :     block_segment_ret = blockmap_data.GetInt( alloc_block*28, 4 );
     356              10 :     last_block = alloc_block;
     357                 : 
     358              10 :     return blockmap_data.GetInt( alloc_block*28+4, 8 );
     359                 : }
     360                 : 
     361                 : /************************************************************************/
     362                 : /*                         SetVirtualFileSize()                         */
     363                 : /************************************************************************/
     364                 : 
     365              24 : void SysBlockMap::SetVirtualFileSize( int image_index, uint64 file_length )
     366                 : 
     367                 : {
     368              24 :     FullLoad();
     369                 : 
     370              24 :     layer_data.Put( file_length, 24*image_index + 12, 12 );
     371              24 :     dirty = true;
     372              24 : }
     373                 : 
     374                 : /************************************************************************/
     375                 : /*                           GetVirtualFile()                           */
     376                 : /************************************************************************/
     377                 : 
     378              36 : SysVirtualFile *SysBlockMap::GetVirtualFile( int image )
     379                 : 
     380                 : {
     381              36 :     PartialLoad();
     382                 : 
     383              36 :     if( image < 0 || image >= (int) virtual_files.size() )
     384                 :         ThrowPCIDSKException( "GetImageSysFile(%d): invalid image index",
     385               0 :                               image );
     386                 : 
     387              36 :     if( virtual_files[image] != NULL )
     388               8 :         return virtual_files[image];
     389                 : 
     390              28 :     uint64  vfile_length = layer_data.GetUInt64( 24*image + 12, 12 );
     391              28 :     int  start_block = layer_data.GetInt( 24*image + 4, 8 );
     392                 : 
     393                 :     virtual_files[image] = 
     394                 :         new SysVirtualFile( dynamic_cast<CPCIDSKFile *>(file), 
     395                 :                             start_block, vfile_length,
     396              28 :                             this, image );
     397                 : 
     398              28 :     return virtual_files[image];
     399                 : }
     400                 : 
     401                 : /************************************************************************/
     402                 : /*                         CreateVirtualFile()                          */
     403                 : /************************************************************************/
     404                 : 
     405               8 : int SysBlockMap::CreateVirtualFile()
     406                 : 
     407                 : {
     408               8 :     FullLoad();
     409                 : 
     410                 : /* -------------------------------------------------------------------- */
     411                 : /*      Is there an existing dead layer we can reuse?                   */
     412                 : /* -------------------------------------------------------------------- */
     413                 :     unsigned int layer_index;
     414                 : 
     415               8 :     for( layer_index = 0; layer_index < virtual_files.size(); layer_index++ )
     416                 :     {
     417               0 :         if( layer_data.GetInt( 24*layer_index + 0, 4 ) == 1 /* dead */ )
     418                 :         {
     419               0 :             break;
     420                 :         }
     421                 :     }
     422                 : 
     423                 : /* -------------------------------------------------------------------- */
     424                 : /*      If not, extend the layer table.                                 */
     425                 : /* -------------------------------------------------------------------- */
     426               8 :     if( layer_index == virtual_files.size() )
     427                 :     {
     428               8 :         layer_index = virtual_files.size();
     429               8 :         layer_data.SetSize( (layer_index+1) * 24 );
     430               8 :         virtual_files.push_back( NULL );
     431                 :     }
     432                 : 
     433                 : /* -------------------------------------------------------------------- */
     434                 : /*      Set all the entries for this layer.                             */
     435                 : /* -------------------------------------------------------------------- */
     436               8 :     dirty = true;
     437                 : 
     438               8 :     layer_data.Put( 2, 24*layer_index + 0, 4 );
     439               8 :     layer_data.Put( -1, 24*layer_index + 4, 8 );
     440               8 :     layer_data.Put( 0, 24*layer_index + 12, 12 );
     441                 : 
     442               8 :     return layer_index;
     443                 : }
     444                 : 
     445                 : /************************************************************************/
     446                 : /*                       CreateVirtualImageFile()                       */
     447                 : /************************************************************************/
     448                 : 
     449               8 : int SysBlockMap::CreateVirtualImageFile( int width, int height, 
     450                 :                                          int block_width, int block_height,
     451                 :                                          eChanType chan_type,
     452                 :                                          std::string compression )
     453                 : 
     454                 : {
     455               8 :     if( compression == "" )
     456               0 :         compression = "NONE";
     457                 : 
     458                 : /* -------------------------------------------------------------------- */
     459                 : /*      Create the underlying virtual file.                             */
     460                 : /* -------------------------------------------------------------------- */
     461               8 :     int img_index = CreateVirtualFile();
     462               8 :     SysVirtualFile *vfile = GetVirtualFile( img_index );
     463                 : 
     464                 : /* -------------------------------------------------------------------- */
     465                 : /*      Set up the image header.                                        */
     466                 : /* -------------------------------------------------------------------- */
     467               8 :     PCIDSKBuffer theader(128);
     468                 : 
     469               8 :     theader.Put( "", 0, 128 );
     470                 : 
     471               8 :     theader.Put( width, 0, 8 );
     472               8 :     theader.Put( height, 8, 8 );
     473               8 :     theader.Put( block_width, 16, 8 );
     474               8 :     theader.Put( block_height, 24, 8 );
     475               8 :     theader.Put( DataTypeName(chan_type).c_str(), 32, 4 );
     476               8 :     theader.Put( compression.c_str(), 54, 8 );
     477                 : 
     478               8 :     vfile->WriteToFile( theader.buffer, 0, 128 );
     479                 : 
     480                 : /* -------------------------------------------------------------------- */
     481                 : /*      Setup the tile map - initially with no tiles referenced.        */
     482                 : /* -------------------------------------------------------------------- */
     483               8 :     int tiles_per_row = (width + block_width - 1) / block_width;
     484               8 :     int tiles_per_col = (height + block_height - 1) / block_height;
     485               8 :     int tile_count = tiles_per_row * tiles_per_col;
     486                 :     int i;
     487                 : 
     488               8 :     PCIDSKBuffer tmap( tile_count * 20 );
     489                 : 
     490              16 :     for( i = 0; i < tile_count; i++ )
     491                 :     {
     492               8 :         tmap.Put( -1, i*12, 12 );
     493               8 :         tmap.Put( 0, tile_count*12 + i*8, 8 );
     494                 :     }
     495                 : 
     496               8 :     vfile->WriteToFile( tmap.buffer, 128, tile_count*20 );
     497                 : 
     498               8 :     return img_index;
     499                 : }
     500                 : 
     501                 : /************************************************************************/
     502                 : /*                        GetNextBlockMapEntry()                        */
     503                 : /*                                                                      */
     504                 : /*      SysVirtualFile's call this method to find the next block in     */
     505                 : /*      the blockmap which belongs to them.  This allows them to        */
     506                 : /*      fill their blockmap "as needed" without necessarily forcing     */
     507                 : /*      a full load of the blockmap.                                    */
     508                 : /************************************************************************/
     509                 : 
     510              20 : int SysBlockMap::GetNextBlockMapEntry( int bm_index,
     511                 :                                        uint16 &segment,
     512                 :                                        int &block_in_segment )
     513                 : 
     514                 : {
     515              20 :     if( !partial_loaded )
     516               0 :         PartialLoad();
     517                 : 
     518                 : /* -------------------------------------------------------------------- */
     519                 : /*      If the full blockmap is already loaded, just fetch it from      */
     520                 : /*      there to avoid extra IO or confusion between what is disk       */
     521                 : /*      and what is in memory.                                          */
     522                 : /*                                                                      */
     523                 : /*      Otherwise we read from disk and hope the io level buffering     */
     524                 : /*      is pretty good.                                                 */
     525                 : /* -------------------------------------------------------------------- */
     526                 :     char bm_entry[29];
     527                 : 
     528              20 :     if( full_loaded )
     529                 :     {
     530               0 :         memcpy( bm_entry, blockmap_data.buffer + bm_index * 28, 28 );
     531                 :     }
     532                 :     else
     533                 :     {
     534              20 :         ReadFromFile( bm_entry, bm_index * 28 + 512, 28 );
     535                 :     }
     536                 :     
     537                 : /* -------------------------------------------------------------------- */
     538                 : /*      Parse the values as efficiently as we can.                      */
     539                 : /* -------------------------------------------------------------------- */
     540              20 :     bm_entry[28] = '\0';
     541                 : 
     542              20 :     int next_block = atoi( bm_entry+20 );
     543                 : 
     544              20 :     bm_entry[12] = '\0';
     545              20 :     block_in_segment = atoi(bm_entry+4);
     546                 : 
     547              20 :     bm_entry[4] = '\0';
     548              20 :     segment = atoi(bm_entry);
     549                 :     
     550              20 :     return next_block;
     551                 : }

Generated by: LCOV version 1.7