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 14 : SysBlockMap::SysBlockMap( PCIDSKFile *file, int segment,
54 : const char *segment_pointer )
55 14 : : CPCIDSKSegment( file, segment, segment_pointer )
56 :
57 : {
58 14 : partial_loaded = false;
59 14 : full_loaded = false;
60 14 : dirty = false;
61 14 : growing_segment = 0;
62 14 : }
63 :
64 : /************************************************************************/
65 : /* ~SysBlockMap() */
66 : /************************************************************************/
67 :
68 14 : SysBlockMap::~SysBlockMap()
69 :
70 : {
71 : size_t i;
72 :
73 28 : for( i = 0; i < virtual_files.size(); i++ )
74 : {
75 14 : delete virtual_files[i];
76 14 : virtual_files[i] = NULL;
77 : }
78 :
79 14 : Synchronize();
80 14 : }
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 5 : void SysBlockMap::Initialize()
90 :
91 : {
92 5 : PCIDSKBuffer init_data(512);
93 :
94 5 : init_data.Put( "VERSION 1", 0, 10 );
95 5 : init_data.Put( 0, 10, 8 );
96 5 : init_data.Put( 0, 18, 8 );
97 5 : init_data.Put( -1, 26, 8 );
98 5 : init_data.Put( "", 34, 512-34 );
99 :
100 5 : 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 5 : }
106 :
107 : /************************************************************************/
108 : /* PartialLoad() */
109 : /* */
110 : /* Load the header and some per-layer information. */
111 : /************************************************************************/
112 :
113 43 : void SysBlockMap::PartialLoad()
114 :
115 : {
116 43 : if( partial_loaded )
117 29 : return;
118 :
119 : // printf( "<PartialLoad>" );
120 : // fflush( stdout );
121 :
122 : /* -------------------------------------------------------------------- */
123 : /* Load the 512 byte count section of the blockmap. */
124 : /* -------------------------------------------------------------------- */
125 14 : PCIDSKBuffer count_data;
126 :
127 14 : count_data.SetSize( 512 );
128 14 : ReadFromFile( count_data.buffer, 0, 512 );
129 :
130 14 : if( strncmp(count_data.buffer,"VERSION",7) != 0 )
131 0 : ThrowPCIDSKException( "SysBlockMap::PartialLoad() - block map corrupt." );
132 :
133 14 : 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 14 : int layer_count = count_data.GetInt( 10, 8 );
141 :
142 14 : virtual_files.resize( layer_count );
143 :
144 14 : block_count = count_data.GetInt( 18, 8 );
145 14 : first_free_block = count_data.GetInt( 26, 8 );
146 :
147 : /* -------------------------------------------------------------------- */
148 : /* Load the layer list definitions. These are fairly small. */
149 : /* -------------------------------------------------------------------- */
150 14 : layer_data.SetSize( 24 * layer_count );
151 : ReadFromFile( layer_data.buffer,
152 : 512 + 28 * block_count,
153 14 : layer_data.buffer_size);
154 :
155 14 : 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 25 : void SysBlockMap::FullLoad()
167 :
168 : {
169 25 : PartialLoad();
170 :
171 25 : if( full_loaded )
172 21 : 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 4 : blockmap_data.SetSize( block_count * 28 );
183 4 : ReadFromFile( blockmap_data.buffer, 512, blockmap_data.buffer_size );
184 :
185 4 : full_loaded = true;
186 : }
187 :
188 : /************************************************************************/
189 : /* Synchronize() */
190 : /************************************************************************/
191 :
192 22 : void SysBlockMap::Synchronize()
193 :
194 : {
195 22 : if( !full_loaded || !dirty )
196 18 : return;
197 :
198 4 : PCIDSKBuffer init_data(512);
199 :
200 4 : init_data.Put( "VERSION 1", 0, 10 );
201 4 : init_data.Put( (int) virtual_files.size(), 10, 8 );
202 4 : init_data.Put( block_count, 18, 8 );
203 4 : init_data.Put( first_free_block, 26, 8 );
204 4 : init_data.Put( "", 34, 512-34 );
205 :
206 4 : WriteToFile( init_data.buffer, 0, init_data.buffer_size );
207 :
208 4 : WriteToFile( blockmap_data.buffer, 512, blockmap_data.buffer_size );
209 : WriteToFile( layer_data.buffer, 512 + blockmap_data.buffer_size,
210 4 : layer_data.buffer_size );
211 :
212 4 : dirty = false;
213 : }
214 :
215 : /************************************************************************/
216 : /* AllocateBlocks() */
217 : /* */
218 : /* Allocate a bunch of new blocks and attach to the free list. */
219 : /************************************************************************/
220 :
221 4 : void SysBlockMap::AllocateBlocks()
222 :
223 : {
224 4 : 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 4 : 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 4 : if( growing_segment == 0 )
240 : {
241 : PCIDSKSegment *seg;
242 4 : int previous = 0;
243 :
244 8 : 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 4 : 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 4 : SEG_SYS, 0L );
266 : }
267 :
268 : /* -------------------------------------------------------------------- */
269 : /* Allocate another set of space. */
270 : /* -------------------------------------------------------------------- */
271 4 : uint64 new_big_blocks = 16;
272 4 : uint64 new_bytes = new_big_blocks * SysVirtualFile::block_size;
273 4 : seg = file->GetSegment( growing_segment );
274 : int block_index_in_segment = (int)
275 4 : (seg->GetContentSize() / SysVirtualFile::block_size);
276 :
277 4 : seg->WriteToFile( "\0", seg->GetContentSize() + new_bytes - 1, 1 );
278 :
279 : /* -------------------------------------------------------------------- */
280 : /* Resize the memory image of the blockmap. */
281 : /* -------------------------------------------------------------------- */
282 4 : if( 28 * (block_count + new_big_blocks)
283 : > (unsigned int) blockmap_data.buffer_size )
284 4 : 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 68 : for( block_index = block_count;
292 : block_index < block_count + new_big_blocks;
293 : block_index++ )
294 : {
295 64 : int bi_offset = (int) (block_index * 28);
296 :
297 64 : blockmap_data.Put( growing_segment, bi_offset, 4 );
298 64 : blockmap_data.Put( block_index_in_segment++, bi_offset+4, 8 );
299 64 : blockmap_data.Put( -1, bi_offset+12, 8 );
300 :
301 64 : if( block_index == block_count + new_big_blocks - 1 )
302 4 : blockmap_data.Put( -1, bi_offset+20, 8 );
303 : else
304 60 : blockmap_data.Put( block_index+1, bi_offset+20, 8 );
305 : }
306 :
307 4 : first_free_block = block_count;
308 :
309 4 : block_count += (int) new_big_blocks;
310 :
311 4 : dirty = true;
312 4 : }
313 :
314 : /************************************************************************/
315 : /* GrowVirtualFile() */
316 : /* */
317 : /* Get one more block for this virtual file. */
318 : /************************************************************************/
319 :
320 5 : int SysBlockMap::GrowVirtualFile( int image, int &last_block,
321 : int &block_segment_ret )
322 :
323 : {
324 5 : FullLoad();
325 :
326 : /* -------------------------------------------------------------------- */
327 : /* Do we need to create new free blocks? */
328 : /* -------------------------------------------------------------------- */
329 5 : if( first_free_block == -1 )
330 4 : AllocateBlocks();
331 :
332 : /* -------------------------------------------------------------------- */
333 : /* Return the first free block, and update the next pointer of */
334 : /* the previous block. */
335 : /* -------------------------------------------------------------------- */
336 5 : int alloc_block = first_free_block;
337 :
338 : // update first free block to point to the next free block.
339 5 : first_free_block = blockmap_data.GetInt( alloc_block*28+20, 8);
340 :
341 : // mark block as owned by this layer/image.
342 5 : 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 5 : 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 5 : if( last_block != -1 )
349 1 : blockmap_data.Put( alloc_block, last_block*28 + 20, 8 );
350 : else
351 4 : layer_data.Put( alloc_block, image*24 + 4, 8 );
352 :
353 5 : dirty = true;
354 :
355 5 : block_segment_ret = blockmap_data.GetInt( alloc_block*28, 4 );
356 5 : last_block = alloc_block;
357 :
358 5 : return blockmap_data.GetInt( alloc_block*28+4, 8 );
359 : }
360 :
361 : /************************************************************************/
362 : /* SetVirtualFileSize() */
363 : /************************************************************************/
364 :
365 12 : void SysBlockMap::SetVirtualFileSize( int image_index, uint64 file_length )
366 :
367 : {
368 12 : FullLoad();
369 :
370 12 : layer_data.Put( file_length, 24*image_index + 12, 12 );
371 12 : dirty = true;
372 12 : }
373 :
374 : /************************************************************************/
375 : /* GetVirtualFile() */
376 : /************************************************************************/
377 :
378 18 : SysVirtualFile *SysBlockMap::GetVirtualFile( int image )
379 :
380 : {
381 18 : PartialLoad();
382 :
383 18 : if( image < 0 || image >= (int) virtual_files.size() )
384 : ThrowPCIDSKException( "GetImageSysFile(%d): invalid image index",
385 0 : image );
386 :
387 18 : if( virtual_files[image] != NULL )
388 4 : return virtual_files[image];
389 :
390 14 : uint64 vfile_length = layer_data.GetUInt64( 24*image + 12, 12 );
391 14 : 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 14 : this, image );
397 :
398 14 : return virtual_files[image];
399 : }
400 :
401 : /************************************************************************/
402 : /* CreateVirtualFile() */
403 : /************************************************************************/
404 :
405 4 : int SysBlockMap::CreateVirtualFile()
406 :
407 : {
408 4 : FullLoad();
409 :
410 : /* -------------------------------------------------------------------- */
411 : /* Is there an existing dead layer we can reuse? */
412 : /* -------------------------------------------------------------------- */
413 : unsigned int layer_index;
414 :
415 4 : 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 4 : if( layer_index == virtual_files.size() )
427 : {
428 4 : layer_index = virtual_files.size();
429 4 : layer_data.SetSize( (layer_index+1) * 24 );
430 4 : virtual_files.push_back( NULL );
431 : }
432 :
433 : /* -------------------------------------------------------------------- */
434 : /* Set all the entries for this layer. */
435 : /* -------------------------------------------------------------------- */
436 4 : dirty = true;
437 :
438 4 : layer_data.Put( 2, 24*layer_index + 0, 4 );
439 4 : layer_data.Put( -1, 24*layer_index + 4, 8 );
440 4 : layer_data.Put( 0, 24*layer_index + 12, 12 );
441 :
442 4 : return layer_index;
443 : }
444 :
445 : /************************************************************************/
446 : /* CreateVirtualImageFile() */
447 : /************************************************************************/
448 :
449 4 : 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 4 : if( compression == "" )
456 0 : compression = "NONE";
457 :
458 : /* -------------------------------------------------------------------- */
459 : /* Create the underlying virtual file. */
460 : /* -------------------------------------------------------------------- */
461 4 : int img_index = CreateVirtualFile();
462 4 : SysVirtualFile *vfile = GetVirtualFile( img_index );
463 :
464 : /* -------------------------------------------------------------------- */
465 : /* Set up the image header. */
466 : /* -------------------------------------------------------------------- */
467 4 : PCIDSKBuffer theader(128);
468 :
469 4 : theader.Put( "", 0, 128 );
470 :
471 4 : theader.Put( width, 0, 8 );
472 4 : theader.Put( height, 8, 8 );
473 4 : theader.Put( block_width, 16, 8 );
474 4 : theader.Put( block_height, 24, 8 );
475 4 : theader.Put( DataTypeName(chan_type).c_str(), 32, 4 );
476 4 : theader.Put( compression.c_str(), 54, 8 );
477 :
478 4 : vfile->WriteToFile( theader.buffer, 0, 128 );
479 :
480 : /* -------------------------------------------------------------------- */
481 : /* Setup the tile map - initially with no tiles referenced. */
482 : /* -------------------------------------------------------------------- */
483 4 : int tiles_per_row = (width + block_width - 1) / block_width;
484 4 : int tiles_per_col = (height + block_height - 1) / block_height;
485 4 : int tile_count = tiles_per_row * tiles_per_col;
486 : int i;
487 :
488 4 : PCIDSKBuffer tmap( tile_count * 20 );
489 :
490 8 : for( i = 0; i < tile_count; i++ )
491 : {
492 4 : tmap.Put( -1, i*12, 12 );
493 4 : tmap.Put( 0, tile_count*12 + i*8, 8 );
494 : }
495 :
496 4 : vfile->WriteToFile( tmap.buffer, 128, tile_count*20 );
497 :
498 4 : 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 10 : int SysBlockMap::GetNextBlockMapEntry( int bm_index,
511 : uint16 &segment,
512 : int &block_in_segment )
513 :
514 : {
515 10 : 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 10 : if( full_loaded )
529 : {
530 0 : memcpy( bm_entry, blockmap_data.buffer + bm_index * 28, 28 );
531 : }
532 : else
533 : {
534 10 : ReadFromFile( bm_entry, bm_index * 28 + 512, 28 );
535 : }
536 :
537 : /* -------------------------------------------------------------------- */
538 : /* Parse the values as efficiently as we can. */
539 : /* -------------------------------------------------------------------- */
540 10 : bm_entry[28] = '\0';
541 :
542 10 : int next_block = atoi( bm_entry+20 );
543 :
544 10 : bm_entry[12] = '\0';
545 10 : block_in_segment = atoi(bm_entry+4);
546 :
547 10 : bm_entry[4] = '\0';
548 10 : segment = atoi(bm_entry);
549 :
550 10 : return next_block;
551 : }
|