Class: AppleCPM

Inherits:
FileSystem show all
Defined in:
lib/file_systems/AppleCPM.rb

Constant Summary collapse

RECORD_SIZE =
128
BLOCK_SIZE =
1024
BLOCKS_PER_EXTENT =
16
RECORDS_PER_BLOCK =
BLOCK_SIZE/RECORD_SIZE
RECORDS_PER_EXTENT =
RECORDS_PER_BLOCK*BLOCKS_PER_EXTENT
SECTORS_IN_BLOCK =
[
  [0x00,0x06,0x0C,0x03],  #block 0
  [0x09,0x0F,0x0E,0x05],  #block 1
  [0x0B,0x02,0x08,0x07],  #block 2
  [0x0D,0x04,0x0A,0x01]  #block 3
]

Class Method Summary collapse

Methods inherited from FileSystem

all_file_systems, code_for_tests, compatability_score, is_valid_file_system_if, non_matching_score

Methods included from SubclassTracking

extended

Class Method Details

.add_file(file_system_image, native_filetype_class, full_filename, file_contents, file_type = nil, aux_code = nil) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/file_systems/AppleCPM.rb', line 129

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

.delete_file(file_system_image, full_filename) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/file_systems/AppleCPM.rb', line 172

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

.files(file_system_image) ⇒ Object

CPM DIR looks like this: $00 User number, or E5h if it’s a free entry $01..0B Filename + extension: 8+3 characters $0C..0D Extent number of this entry $0E ??? $0F Number of 128-byte records allocated in this extant $10..1F Allocation map for this directory entry



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/file_systems/AppleCPM.rb', line 81

def self.files(file_system_image)
  files=FileContainer.new
  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
      file_name=dir_entry[0x01..0x08].gsub(' ','')
      file_ext=dir_entry[0x09..0x0B].gsub(' ','')
      full_filename="#{file_name}.#{file_ext}"
      files<<NativeFileType.best_fit(file_system_image,full_filename,'',file_ext)
      if files[full_filename].nil? then
        files<<file 
      end
      s=""
      0x10.upto(0x1f) do |i|
        block=dir_entry[i]
        s+=get_block(file_system_image,block) unless block==0
      end        
      records_allocated=dir_entry[0x0F]
      files[full_filename].contents+=s[0,(records_allocated*128)]
    end
  end    
  files
end

.find_free_space(file_system_image) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/file_systems/AppleCPM.rb', line 107

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

.get_block(file_system_image, block_no) ⇒ Object



32
33
34
35
36
37
# File 'lib/file_systems/AppleCPM.rb', line 32

def self.get_block(file_system_image,block_no)
  track_no= ((block_no/4)+3)%file_system_image.track_count
  s=""
  SECTORS_IN_BLOCK[block_no%4].each {|sector_no| s+=file_system_image.get_sector(track_no,sector_no)}
  s
end

.host_systemObject



11
12
13
# File 'lib/file_systems/AppleCPM.rb', line 11

def self.host_system
  Apple2
end

.matching_scoreObject



15
16
17
# File 'lib/file_systems/AppleCPM.rb', line 15

def self.matching_score
  20
end

.set_block(file_system_image, block_no, contents) ⇒ Object



39
40
41
42
43
44
45
46
# File 'lib/file_systems/AppleCPM.rb', line 39

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