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 : }
|