lib/file_systems/AppleCPM.rb in ripxplore-0.5.2 vs lib/file_systems/AppleCPM.rb in ripxplore-0.7.0
- old
+ new
@@ -34,11 +34,20 @@
s=""
SECTORS_IN_BLOCK[block_no%4].each {|sector_no| s+=file_system_image.get_sector(track_no,sector_no)}
s
end
+ def self.set_block(file_system_image,block_no,contents)
+ raise "invalid block #{block_no} - length was #{contents.length}" unless contents.length==BLOCK_SIZE
+ track_no=(block_no/4)+3
+ 0.upto(3) do |i|
+ sector_no=SECTORS_IN_BLOCK[block_no%4][i]
+ file_system_image.set_sector(track_no,sector_no,contents[(i*256),256])
+ end
+ end
+
#look for a valid looking CPM directory on track 3.
#go through each sector in order, for each sector, look at every 32nd byte, and see if it is a valid 'user number' (i.e. a number from 00..0F). Stop looking when you see an 'E5'.
#if an invalid user number is found before an E5, then the disk is NOT a CPM disk
is_valid_file_system_if lambda {
is_valid=true
@@ -92,9 +101,91 @@
files[full_filename].contents+=s[0,(records_allocated*128)]
end
end
files
end
+
+def self.find_free_space(file_system_image)
+ free_blocks=[]
+ free_directory_entries=[]
+ #first two blocks are where the catalog lives
+ (2..((file_system_image.track_count-3)*4)-1).each {|block| free_blocks<<block}
+ catalog=get_block(file_system_image,0)+get_block(file_system_image,1)
+ 0.upto(63) do |dir_entry_no|
+ dir_entry_start=dir_entry_no*0x20
+ dir_entry=catalog[dir_entry_start..dir_entry_start+0x1F]
+ if (dir_entry[0]<0x10) then
+ 0x10.upto(0x1f) do |i|
+ block=dir_entry[i]
+ free_blocks.delete(block)
+ end
+ else
+ free_directory_entries<<dir_entry_no
+ end
+ end
+ [free_blocks,free_directory_entries]
+end
+
+
+ def self.add_file(file_system_image,native_filetype_class,full_filename,file_contents,file_type=nil,aux_code=nil)
+ raise "{native_filetype_class} not supported on Apple CPM file system" if native_filetype_class.file_system_file_types[self].nil?
+ partial_filename,file_type=CPMFile.split_filename(full_filename)
+ full_filename ="#{partial_filename}.#{file_type}"
+# puts "#{full_filename},#{partial_filename},#{file_type}"
+ delete_file(file_system_image,full_filename) unless file_system_image.files[full_filename].nil? #so we can overwrite the file if it already exists
+ free_blocks,free_directory_entries=find_free_space(file_system_image)
+ total_record_count=(file_contents.length/RECORD_SIZE.to_f).ceil
+ total_blocks_needed=(total_record_count/RECORDS_PER_BLOCK.to_f).ceil
+ total_extents_needed=(total_blocks_needed/BLOCKS_PER_EXTENT.to_f).ceil
+# puts "#{total_extents_needed} extents needed"
+ raise "#{total_blocks_needed} free blocks required, only #{free_blocks.length} available" unless total_blocks_needed<=free_blocks.length
+ raise "#{total_extents_needed} free directory entries required, only #{free_directory_entries.length} available" unless total_extents_needed<=free_directory_entries.length
+ catalog=get_block(file_system_image,0)+get_block(file_system_image,1)
+ padded_file_contents=file_contents+(0x1A.chr*BLOCK_SIZE)
+
+ total_extents_needed.times do |extent_no|
+ records_this_extent=(extent_no==(total_extents_needed-1) ? (total_record_count % RECORDS_PER_EXTENT):RECORDS_PER_EXTENT)
+ blocks_this_extent=(records_this_extent/RECORDS_PER_BLOCK.to_f).ceil
+ blocks_used=[0]*BLOCKS_PER_EXTENT
+ first_record_this_extent=extent_no*RECORDS_PER_EXTENT
+ contents_this_extent=padded_file_contents[(first_record_this_extent*RECORD_SIZE),blocks_this_extent*BLOCK_SIZE]
+
+ blocks_this_extent.times do |block_no|
+ this_block=free_blocks[block_no+(extent_no*BLOCKS_PER_EXTENT)]
+ set_block(file_system_image,this_block,contents_this_extent[block_no*BLOCK_SIZE,BLOCK_SIZE])
+ blocks_used[block_no]=this_block
+ end
+ dir_entry_no=free_directory_entries[extent_no]
+ dir_entry_start=dir_entry_no*0x20
+ dir_entry=[0,partial_filename,file_type,extent_no,0,0,records_this_extent,blocks_used].flatten.pack("CA8A3C4C16")
+ catalog[dir_entry_start,0x20]=dir_entry
+# puts "dir entry # #{dir_entry_no}"
+ end
+
+ set_block(file_system_image,0,catalog[0,BLOCK_SIZE])
+ set_block(file_system_image,1,catalog[BLOCK_SIZE,BLOCK_SIZE])
+
+ native_file=files(file_system_image)[full_filename]
+ raise "error: file should now be in catalog\n #{file_system_image.catalog}" if native_file.nil?
+ return native_file
+ end
+
+ def self.delete_file(file_system_image,full_filename)
+ catalog=get_block(file_system_image,0)+get_block(file_system_image,1)
+ (partial_filename,file_ext)=CPMFile.split_filename(full_filename)
+ 0.upto(63) do |dir_entry_no|
+ dir_entry_start=dir_entry_no*0x20
+ dir_entry=catalog[dir_entry_start..dir_entry_start+0x1F]
+ if (partial_filename==dir_entry[0x01..0x08].gsub(' ','')) && (file_ext==dir_entry[0x09..0x0B].gsub(' ',''))
+ #we found a matching filename, so set the 'user number' field to a 'blank' entry
+ catalog[dir_entry_start]=0xE5
+ end
+ end
+
+ set_block(file_system_image,0,catalog[0,BLOCK_SIZE])
+ set_block(file_system_image,1,catalog[BLOCK_SIZE,BLOCK_SIZE])
+ end
+
end
# == Author
# Jonno Downes (jonno@jamtronix.com)