geotree 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4e790d44323caacbf4a2bd3ec69bc02bb55dc847
4
+ data.tar.gz: bdb2927e4dea7857cd9863cbc2b53ee0c089bef7
5
+ SHA512:
6
+ metadata.gz: eef40796846cbabd4bddd8f32cef9334f7b4e6b0d17fcb851af0247907c8e89b9582808179bed41dc635069965e2c634a8c55e0fa5a1411f0ef09d23f4ca4980
7
+ data.tar.gz: e92b0f2e40906f8bc9853b21af28af24691a0b6d8e77682fd1fb8a557b36f9e520c33e092aa8b6ae4fe2f47eb78035460d901b8744c85bd56c5d5e4a6be4e329
@@ -0,0 +1,3 @@
1
+ 2013-04-02 Jeff Sember jpsember@gmail.com
2
+ * Version 1.0.0 released
3
+
@@ -0,0 +1,6 @@
1
+ geotree : xxx
2
+
3
+ Written and (c) by Jeff Sember, April 2013.
4
+ ================================================================================
5
+
6
+
@@ -0,0 +1 @@
1
+ require_relative 'lib/geotree'
@@ -0,0 +1,453 @@
1
+ require_relative 'tools'
2
+
3
+ # Block file.
4
+ #
5
+ # For storing data in a collection of 'blocks', fixed-length arrays of bytes,
6
+ # with facility for recycling blocks that are no longer used.
7
+ #
8
+ # Base class is in-memory only; subclass to persist blocks, e.g., to a file system.
9
+ # The DiskBlockFile class does exactly this.
10
+ #
11
+ # Each block has a unique 'name'. This is a positive integer. The word 'name' is
12
+ # chosen instead of 'id' to avoid conflicting with the use of 'id' in some languages.
13
+ #
14
+ # Each block file header includes space for four integers, for use by the application.
15
+ #
16
+ class BlockFile
17
+
18
+ FIRST_BLOCK_ID = 2
19
+ USER_HEADER_INTS = 4
20
+
21
+ #---- All constants ending with '_' will be made private
22
+
23
+ VERSION_ = 1965
24
+
25
+ # Header block fields; each is an integer
26
+ INT_BYTES_ = 4
27
+ HDR_VERSION_ = 0
28
+ HDR_BLOCKSIZE_ = 1
29
+ HDR_MAXINDEX_ = 2
30
+ HDR_RECYCLEINDEX_ = 3
31
+ HDR_USERSTART_ = 4
32
+ HDR_SIZE_BYTES_ = (HDR_USERSTART_ + USER_HEADER_INTS) * INT_BYTES_
33
+
34
+ # Fields of a recycle directory block
35
+ RC_PREV_DIR_NAME_ = 0
36
+ RC_ENTRIES_USED_ = 1
37
+ RC_ENTRIES_START_ = 2
38
+
39
+ privatize(self)
40
+
41
+ #---------------------------------------------
42
+
43
+ # Size of blocks, in bytes
44
+ attr_reader :block_size
45
+ # Constructor. Constructs a file, initially closed.
46
+ # @param block_size size of blocks, in bytes
47
+ def initialize(block_size)
48
+ block_size = [block_size, HDR_SIZE_BYTES_].max
49
+
50
+ @header_data = nil
51
+ @recycle_data = nil
52
+ @header_modified = false
53
+
54
+ # for in-memory version only:
55
+ @mem_file = nil
56
+
57
+ @block_size = block_size
58
+ end
59
+
60
+ # Determine if file is open
61
+ def open?
62
+ return @header_data != nil
63
+ end
64
+
65
+ # Open file. Creates underlying storage if necessary.
66
+ # File must not already be open.
67
+ # @return true if underlying storage already existed
68
+ #
69
+ def open
70
+ db = false
71
+ # db = true
72
+ !db || pr("BlockFile.open\n")
73
+ !open? || raise(IllegalStateException)
74
+ existed = open_storage
75
+ !db || pr(" existed=#{existed}\n")
76
+ if !existed
77
+ @header_data = alloc_buffer
78
+ BlockFile.write_int(@header_data, HDR_VERSION_, VERSION_)
79
+ BlockFile.write_int(@header_data, HDR_BLOCKSIZE_, block_size)
80
+ BlockFile.write_int(@header_data, HDR_RECYCLEINDEX_, 1)
81
+ append_or_replace(0, @header_data)
82
+
83
+ @recycle_data = alloc_buffer
84
+ append_or_replace(rdir_head_name, @recycle_data)
85
+
86
+ aux_flush
87
+ else
88
+ @header_data = read(0)
89
+ if BlockFile.read_int(@header_data,HDR_VERSION_) != VERSION_
90
+ raise ArgumentError,"bad version"
91
+ end
92
+ if BlockFile.read_int(@header_data,HDR_BLOCKSIZE_) != block_size
93
+ raise ArgumentError,"unexpected block size"
94
+ end
95
+ !db || puts(hex_dump_to_string(@header_data,'header data'))
96
+ @recycle_data = read(rdir_head_name)
97
+ end
98
+ existed
99
+ end
100
+
101
+ # Allocate a new block. First block allocated will have name = FIRST_BLOCK_ID.
102
+ # @param src block data to write; if null, allocates and writes zeros
103
+ # @return name of block
104
+ #
105
+ def alloc(src = nil)
106
+
107
+ db = false
108
+ !db || pr("blockfile alloc\n")
109
+ #!db || puts(self.to_s)
110
+
111
+ ensure_open
112
+
113
+ src ||= alloc_buffer
114
+
115
+ # get index of last recycle block directory
116
+ r_index = rdir_head_name
117
+ !db||pr(" last recycle block dir=%d\n",r_index)
118
+
119
+ # any entries remain in this directory?
120
+ n_ent = get_rdir_slots_used
121
+ !db||pr(" n_ent=%d\n",n_ent);
122
+
123
+ if n_ent == 0
124
+ prev_rb_block = get_rdir_next_name
125
+
126
+ if prev_rb_block > 0
127
+ # use directory as new block
128
+ ret = r_index
129
+ r_index = prev_rb_block
130
+ write_hdr(HDR_RECYCLEINDEX_, r_index)
131
+ read(prev_rb_block, @recycle_data)
132
+ !db||pr(" using directory as new block: %d\n",ret)
133
+ append_or_replace(ret, src)
134
+ else
135
+ ret = name_max
136
+ !db||pr(" using name_max %d\n",ret)
137
+ append_or_replace(ret, src)
138
+ end
139
+ else
140
+ slot = n_ent - 1;
141
+ ret = get_rdir_slot(slot)
142
+ !db || pr(" read slot %d to get %d\n",slot,ret)
143
+ set_rdir_slot(slot,0)
144
+ set_rdir_slots_used(slot)
145
+ append_or_replace(r_index, @recycle_data)
146
+ append_or_replace(ret,src)
147
+ end
148
+ ret
149
+ end
150
+
151
+ # Free up a block
152
+ def free(block_name)
153
+ ensure_open
154
+
155
+ raise(ArgumentError,"no such block: #{block_name}") if block_name >= name_max
156
+
157
+ slot = get_rdir_slots_used()
158
+
159
+ # if there is a free slot in the current recycle block, use it
160
+
161
+ if slot < get_rdir_capacity
162
+ set_rdir_slot(slot,block_name)
163
+ set_rdir_slots_used(slot+1)
164
+ append_or_replace(rdir_head_name, @recycle_data)
165
+ else
166
+
167
+ # use freed block as next recycle page
168
+
169
+ old_dir = rdir_head_name
170
+
171
+ write_hdr(HDR_RECYCLEINDEX_, block_name)
172
+
173
+ read(block_name, @recycle_data)
174
+ BlockFile.clear_block(@recycle_data)
175
+ # mark_rc_block
176
+
177
+ set_rdir_next_name(old_dir)
178
+ append_or_replace(block_name, @recycle_data)
179
+ end
180
+ end
181
+
182
+ def close
183
+ ensure_open
184
+ aux_flush
185
+ close_storage
186
+
187
+ @header_data = nil
188
+ @recycle_data = nil
189
+ @mem_file = nil
190
+ end
191
+
192
+ # Read one of the user values from the header
193
+ # @param int_index index of user value (0..3)
194
+ def read_user(int_index)
195
+ ensure_open
196
+ raise ArgumentError if !(int_index >= 0 && int_index < USER_HEADER_INTS)
197
+ BlockFile.read_int(@header_data, HDR_USERSTART_ + int_index)
198
+ end
199
+
200
+ # Write a user value
201
+ # @param int_index index of user value (0..3)
202
+ # @param value value to write
203
+ def write_user(int_index, value)
204
+ ensure_open
205
+ raise ArgumentError if !(int_index >= 0 && int_index < USER_HEADER_INTS)
206
+ BlockFile.write_int(@header_data, HDR_USERSTART_ + int_index, value)
207
+ @header_modified = true
208
+ end
209
+
210
+ def inspect
211
+ to_s
212
+ end
213
+
214
+ def to_s
215
+ s = ''
216
+ s << "BlockFile blockSize:#{block_size} "
217
+ if open?
218
+ s << "\n name_max=#{name_max}"
219
+ s << "\n rdir_head_name=#{rdir_head_name}"
220
+
221
+ s << "\n"
222
+
223
+ # Dump a map of currently allocated blocks
224
+ usage = {}
225
+ usage[0] = 'H'
226
+ ri = rdir_head_name
227
+ while ri != 0
228
+ usage[ri] = 'R'
229
+ rd = read(ri)
230
+
231
+ next_ri = BlockFile.read_int(rd, RC_PREV_DIR_NAME_)
232
+ used = BlockFile.read_int(rd, RC_ENTRIES_USED_)
233
+ used.times do |i|
234
+ rblock = BlockFile.read_int(rd,RC_ENTRIES_START_+i)
235
+ usage[rblock] = 'r'
236
+ end
237
+ ri = next_ri
238
+ end
239
+
240
+ row_size = 64
241
+
242
+ puts("------------- Block Map --------------")
243
+ name_max.times do |i|
244
+ if (i % row_size) == 0
245
+ printf("%04x: ",i)
246
+ elsif (i % 4 == 0)
247
+ print(' ')
248
+ end
249
+ label = usage[i]
250
+ label ||= '.'
251
+ print label
252
+ print "\n" if ((i+1) % row_size) == 0
253
+ end
254
+ print "\n" if (name_max % row_size != 0)
255
+ puts("--------------------------------------")
256
+ end
257
+ s
258
+ end
259
+
260
+ def dump(block_name)
261
+ b = read(block_name)
262
+ hex_dump(b,"Block #{block_name}")
263
+ end
264
+
265
+ # Create an array of bytes, all zeros, of length equal to this block file's block length
266
+ def alloc_buffer
267
+ zero_bytes(@block_size)
268
+ end
269
+
270
+ # Read an integer from a block of bytes
271
+ def BlockFile.read_int(block, int_offset)
272
+ # assert!(block)
273
+ j = int_offset*INT_BYTES_
274
+
275
+ # We must treat the most significant byte as a signed byte
276
+ high_byte = block[j].ord
277
+ if high_byte > 127
278
+ high_byte = high_byte - 256
279
+ end
280
+ (high_byte << 24) | (block[j+1].ord << 16) | (block[j+2].ord << 8) | block[j+3].ord
281
+ end
282
+
283
+ # Write an integer into a block of bytes
284
+ def BlockFile.write_int(block, int_offset, value)
285
+ j = int_offset * INT_BYTES_
286
+ block[j] = ((value >> 24) & 0xff).chr
287
+ block[j+1] = ((value >> 16) & 0xff).chr
288
+ block[j+2] = ((value >> 8) & 0xff).chr
289
+ block[j+3] = (value & 0xff).chr
290
+ end
291
+
292
+ # Clear block to zeros
293
+ def self.clear_block(block)
294
+ block[0..-1] = zero_bytes(block.size)
295
+ end
296
+
297
+ def BlockFile.copy_block(dest, src)
298
+ # assert!(dest && src)
299
+ dest[0..-1] = src
300
+ end
301
+
302
+ # -------------------------------------------------------
303
+ # These methods should be overridden by subclasses
304
+ # for block files that are not to be memory-only (as
305
+ # the default implementations assume)
306
+
307
+ # Read block from storage.
308
+ # @param block_name index of block
309
+ # @param dest_buffer where to store data; if nil, you should
310
+ # call alloc_buffer to create it
311
+ # @return buffer
312
+ #
313
+ def read(block_name, dest_buffer = nil)
314
+ db = false
315
+ # db = true
316
+ !db || pr("BlockFile read #{block_name}, memory version!\n")
317
+
318
+ dest_buffer ||= alloc_buffer
319
+ if block_name >= @mem_file.size
320
+ raise ArgumentError,"No such block name #{block_name} exists (size=#{@mem_file.size})"
321
+ end
322
+
323
+ src = @mem_file[block_name]
324
+ BlockFile.copy_block(dest_buffer, src)
325
+ !db || hex_dump(dest_buffer,"Contents of block #{block_name}")
326
+ dest_buffer
327
+ end
328
+
329
+ # Write block to storage.
330
+ # Name is either index of existing block, or
331
+ # number of existing blocks (to append to end of existing ones)
332
+ # @param block_name name of block
333
+ # @param src_buffer data to write
334
+ def write(block_name, src_buffer)
335
+
336
+ if block_name == @mem_file.size
337
+ @mem_file << alloc_buffer
338
+ end
339
+ BlockFile.copy_block(@mem_file[block_name], src_buffer)
340
+ end
341
+
342
+ # Open underlying storage; create it if necessary
343
+ # @return true if underlying storage already existed
344
+ def open_storage
345
+ @mem_file = []
346
+ false
347
+ end
348
+
349
+ # Close underlying storage
350
+ #
351
+ def close_storage
352
+ @mem_file = nil
353
+ end
354
+
355
+ # Flush underlying storage
356
+ #
357
+ def flush
358
+ end
359
+
360
+ # -------------------------------------------------------
361
+
362
+ # Get 1 + name of highest block ever created
363
+ def name_max
364
+ BlockFile.read_int(@header_data, HDR_MAXINDEX_)
365
+ end
366
+
367
+ private
368
+
369
+ def aux_flush
370
+ if @header_modified
371
+ append_or_replace(0, @header_data)
372
+ @header_modified = false
373
+ end
374
+ flush
375
+ end
376
+
377
+ def ensure_block_exists(name)
378
+ raise(ArgumentError,"no such block: #{name} (name_max=#{name_max})") if block_name >= name_max
379
+ end
380
+
381
+ def ensure_open
382
+ raise IllegalStateException if !open?
383
+ end
384
+
385
+ # Determine how many names can be stored in a single
386
+ # recycle directory block
387
+ #
388
+ def get_rdir_capacity
389
+ (@block_size / INT_BYTES_) - RC_ENTRIES_START_
390
+ end
391
+
392
+ def get_rdir_slots_used
393
+ BlockFile.read_int(@recycle_data, RC_ENTRIES_USED_)
394
+ end
395
+
396
+ def set_rdir_slots_used(n)
397
+ BlockFile.write_int(@recycle_data,RC_ENTRIES_USED_,n)
398
+ end
399
+
400
+ # Get name of next recycle directory block
401
+ # @return name of next, or 0 if there are no more
402
+ def get_rdir_next_name
403
+ BlockFile.read_int(@recycle_data,RC_PREV_DIR_NAME_)
404
+ end
405
+
406
+ def get_rdir_slot(offset)
407
+ BlockFile.read_int(@recycle_data,(RC_ENTRIES_START_+offset))
408
+ end
409
+
410
+ def set_rdir_slot(offset, value)
411
+ BlockFile.write_int(@recycle_data,(RC_ENTRIES_START_+offset),value)
412
+ end
413
+
414
+ def set_rdir_next_name(n)
415
+ BlockFile.write_int(@recycle_data,RC_PREV_DIR_NAME_,n)
416
+ end
417
+
418
+ # def mark_rc_block
419
+ # @recycle_data[RC_BLOCKTYPE_] = BLOCKTYPE_RECYCLE_.chr
420
+ # end
421
+
422
+ # Get name of first recycle directory block (they are connected as
423
+ # a singly-linked list)
424
+ #
425
+ def rdir_head_name
426
+ BlockFile.read_int(@header_data,HDR_RECYCLEINDEX_)
427
+ end
428
+
429
+ # Write a block; if we're appending a new block, increment
430
+ # the name_max field
431
+ #
432
+ def append_or_replace(block_name, src)
433
+ if (block_name < 0 || block_name > name_max)
434
+ raise ArgumentError,"write to nonexistent block #{block_name}; only #{name_max} in file"
435
+ end
436
+
437
+ if block_name == name_max
438
+ write_hdr(HDR_MAXINDEX_, block_name+1)
439
+ end
440
+
441
+ write(block_name, src)
442
+ end
443
+
444
+ # Write a value to a header field, mark header as modified
445
+ # @param field field number (HDR_xxx)
446
+ # @param value value to write
447
+ #
448
+ def write_hdr(field, value)
449
+ BlockFile.write_int(@header_data, field, value)
450
+ @header_modified = true
451
+ end
452
+
453
+ end