smp_tool 0.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.
- checksums.yaml +7 -0
- data/.rubocop.yml +13 -0
- data/.vscode/launch.json +21 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +109 -0
- data/Rakefile +16 -0
- data/lib/smp_tool/autoloader.rb +24 -0
- data/lib/smp_tool/basic_10.rb +95 -0
- data/lib/smp_tool/basic_20.rb +96 -0
- data/lib/smp_tool/filename.rb +71 -0
- data/lib/smp_tool/version.rb +5 -0
- data/lib/smp_tool/virtual_volume/data_entry.rb +47 -0
- data/lib/smp_tool/virtual_volume/data_entry_header.rb +85 -0
- data/lib/smp_tool/virtual_volume/file_interface.rb +33 -0
- data/lib/smp_tool/virtual_volume/utils/converter_from_volume_io.rb +81 -0
- data/lib/smp_tool/virtual_volume/utils/converter_to_volume_io.rb +125 -0
- data/lib/smp_tool/virtual_volume/utils/empty_vol_data_initializer.rb +32 -0
- data/lib/smp_tool/virtual_volume/utils/file_converter.rb +55 -0
- data/lib/smp_tool/virtual_volume/utils/file_extracter.rb +90 -0
- data/lib/smp_tool/virtual_volume/utils/volume_params_validator.rb +20 -0
- data/lib/smp_tool/virtual_volume/utils.rb +10 -0
- data/lib/smp_tool/virtual_volume/volume.rb +249 -0
- data/lib/smp_tool/virtual_volume/volume_data.rb +216 -0
- data/lib/smp_tool/virtual_volume/volume_params.rb +70 -0
- data/lib/smp_tool/virtual_volume/volume_params_contract.rb +53 -0
- data/lib/smp_tool/virtual_volume.rb +8 -0
- data/lib/smp_tool/volume_io/bootloader.rb +18 -0
- data/lib/smp_tool/volume_io/data.rb +14 -0
- data/lib/smp_tool/volume_io/dir_entry.rb +45 -0
- data/lib/smp_tool/volume_io/dir_seg.rb +33 -0
- data/lib/smp_tool/volume_io/dir_seg_header.rb +23 -0
- data/lib/smp_tool/volume_io/directory.rb +26 -0
- data/lib/smp_tool/volume_io/file_content.rb +13 -0
- data/lib/smp_tool/volume_io/home_block.rb +18 -0
- data/lib/smp_tool/volume_io/volume_io.rb +53 -0
- data/lib/smp_tool/volume_io.rb +10 -0
- data/lib/smp_tool.rb +54 -0
- data/sig/smp_tool.rbs +4 -0
- data/smp_tool.gemspec +40 -0
- metadata +206 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Converts volume IO to the virtual volume.
|
8
|
+
#
|
9
|
+
module ConverterFromVolumeIO
|
10
|
+
class << self
|
11
|
+
private
|
12
|
+
|
13
|
+
def _volume_data(entries, data, extra_word)
|
14
|
+
VolumeData.new(
|
15
|
+
_data_entries(entries, data),
|
16
|
+
extra_word
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def _data_entries(entries, data)
|
21
|
+
entries.each_with_index.map do |e, i|
|
22
|
+
DataEntry.new(
|
23
|
+
header: DataEntryHeader.new(e.snapshot.to_h),
|
24
|
+
data: data[i]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Extra word value is defined by the target BASIC version,
|
30
|
+
# and the target BASIC version can be identified by the
|
31
|
+
# number of extra bytes per entry (and vice versa).
|
32
|
+
def _choose_extra_word(n_extra_bytes_per_entry)
|
33
|
+
case n_extra_bytes_per_entry
|
34
|
+
when 0
|
35
|
+
Basic10::ENTRY_EXTRA_WORD
|
36
|
+
else
|
37
|
+
Basic20::ENTRY_EXTRA_WORD
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def _build_volume_params(volume_io)
|
42
|
+
VirtualVolume::VolumeParams.new(
|
43
|
+
n_clusters_allocated: volume_io.n_clusters_allocated.to_i,
|
44
|
+
n_extra_bytes_per_entry: volume_io.n_extra_bytes_per_entry.to_i,
|
45
|
+
n_dir_segs: volume_io.n_dir_segs.to_i,
|
46
|
+
n_clusters_per_dir_seg: volume_io.n_clusters_per_dir_seg.to_i,
|
47
|
+
extra_word: _choose_extra_word(volume_io.n_extra_bytes_per_entry)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def _read_entries(volume_io)
|
52
|
+
volume_io.directory.segments.to_ary.flat_map(&:dir_seg_entries)
|
53
|
+
.reject { |e| e.status == DIR_SEG_FOOTER }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.read_io(io)
|
58
|
+
read_volume_io(
|
59
|
+
SMPTool::VolumeIO::VolumeIO.read(io)
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.read_volume_io(volume_io)
|
64
|
+
entries = _read_entries(volume_io)
|
65
|
+
data = volume_io.data.to_ary
|
66
|
+
|
67
|
+
raise ArgumentError, "entries => data lengths mismatch" unless entries.length == data.length
|
68
|
+
|
69
|
+
volume_params = _build_volume_params(volume_io)
|
70
|
+
|
71
|
+
VirtualVolume::Volume.new(
|
72
|
+
bootloader: volume_io.bootloader.bytes.to_ary,
|
73
|
+
home_block: volume_io.home_block.bytes.to_ary,
|
74
|
+
volume_params: volume_params,
|
75
|
+
volume_data: _volume_data(entries, data, volume_params.extra_word)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Converts virtual volume to the volume IO.
|
8
|
+
#
|
9
|
+
class ConverterToVolumeIO
|
10
|
+
def initialize(volume)
|
11
|
+
@bootloader = volume.bootloader
|
12
|
+
@home_block = volume.home_block
|
13
|
+
@n_clusters_allocated = volume.n_clusters_allocated
|
14
|
+
@n_extra_bytes_per_entry = volume.n_extra_bytes_per_entry
|
15
|
+
@n_clusters_per_dir_seg = volume.n_clusters_per_dir_seg
|
16
|
+
@n_max_entries_per_dir_seg = volume.n_max_entries_per_dir_seg
|
17
|
+
@n_dir_segs = volume.n_dir_segs
|
18
|
+
|
19
|
+
@data = _group_entries(volume.data)
|
20
|
+
|
21
|
+
@data_offset = N_SYS_CLUSTERS + @n_dir_segs * @n_clusters_per_dir_seg
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
VolumeIO::VolumeIO.new(
|
26
|
+
bootloader: _build_bootloader,
|
27
|
+
home_block: _build_home_block,
|
28
|
+
directory: _build_directory,
|
29
|
+
data: _build_data,
|
30
|
+
n_clusters_allocated: @n_clusters_allocated
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _build_data
|
37
|
+
@data.flatten.map do |e|
|
38
|
+
VolumeIO::FileContent.new(
|
39
|
+
e.data
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def _group_entries(arr)
|
45
|
+
result = arr.each_slice(@n_max_entries_per_dir_seg).to_a
|
46
|
+
diff = @n_dir_segs - result.length
|
47
|
+
diff.times { result << [] }
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
def _build_bootloader
|
52
|
+
VolumeIO::Bootloader.new(bytes: @bootloader)
|
53
|
+
end
|
54
|
+
|
55
|
+
def _build_home_block
|
56
|
+
VolumeIO::HomeBlock.new(bytes: @home_block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def _build_directory
|
60
|
+
segments = (1..@n_dir_segs).map do |i_dir_seg|
|
61
|
+
_build_dir_seg(i_dir_seg, _build_dir_seg_entries(i_dir_seg))
|
62
|
+
end
|
63
|
+
|
64
|
+
VolumeIO::Directory.new(
|
65
|
+
segments: segments
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def _build_dir_seg_entries(i_dir_seg)
|
70
|
+
@data[i_dir_seg - 1]
|
71
|
+
.map { |e| _build_dir_entry(e) }
|
72
|
+
.push(_build_dir_seg_footer)
|
73
|
+
end
|
74
|
+
|
75
|
+
def _build_dir_entry(v_entry)
|
76
|
+
VolumeIO::DirEntry.new(
|
77
|
+
status: v_entry.status,
|
78
|
+
filename: v_entry.filename,
|
79
|
+
n_clusters: v_entry.n_clusters,
|
80
|
+
ch_job: v_entry.ch_job,
|
81
|
+
date: v_entry.date,
|
82
|
+
extra_word: v_entry.extra_word
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _build_dir_seg(i_dir_seg, dir_seg_entries)
|
87
|
+
i_next_seg = i_dir_seg == @n_dir_segs ? 0 : i_dir_seg + 1
|
88
|
+
|
89
|
+
dir_seg = VolumeIO::DirSeg.new(
|
90
|
+
header: _build_header(
|
91
|
+
i_next_seg: i_next_seg,
|
92
|
+
data_offset: @data_offset
|
93
|
+
),
|
94
|
+
dir_seg_entries: dir_seg_entries
|
95
|
+
)
|
96
|
+
|
97
|
+
_upd_data_offset(i_dir_seg)
|
98
|
+
|
99
|
+
dir_seg
|
100
|
+
end
|
101
|
+
|
102
|
+
def _upd_data_offset(i_dir_seg)
|
103
|
+
n_clustes_in_files = @data[i_dir_seg - 1].sum(&:n_clusters)
|
104
|
+
|
105
|
+
@data_offset += n_clustes_in_files
|
106
|
+
end
|
107
|
+
|
108
|
+
def _build_header(i_next_seg:, data_offset:)
|
109
|
+
VolumeIO::DirSegHeader.new(
|
110
|
+
n_dir_segs: @n_dir_segs,
|
111
|
+
i_next_seg: i_next_seg,
|
112
|
+
n_extra_bytes_per_entry: @n_extra_bytes_per_entry,
|
113
|
+
data_offset: data_offset
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def _build_dir_seg_footer
|
118
|
+
VolumeIO::DirEntry.new(
|
119
|
+
status: DIR_SEG_FOOTER
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Initializes empty data for a volume with the given params.
|
8
|
+
#
|
9
|
+
module EmptyVolDataInitializer
|
10
|
+
#
|
11
|
+
# Initialize empty data.
|
12
|
+
#
|
13
|
+
# @param [Hash{ Symbol => Object }] volume_params
|
14
|
+
#
|
15
|
+
# @return [VolumeData]
|
16
|
+
#
|
17
|
+
def self.call(volume_params)
|
18
|
+
n_data_clusters = volume_params.n_clusters_allocated -
|
19
|
+
N_SYS_CLUSTERS -
|
20
|
+
(volume_params.n_dir_segs * volume_params.n_clusters_per_dir_seg)
|
21
|
+
|
22
|
+
data = VolumeData.new(
|
23
|
+
[],
|
24
|
+
volume_params.extra_word
|
25
|
+
)
|
26
|
+
|
27
|
+
data.push_empty_entry(n_data_clusters)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Converts hashes to data entry objects.
|
8
|
+
#
|
9
|
+
class FileConverter
|
10
|
+
def initialize(f_hash, extra_word, &block)
|
11
|
+
str = _process_data(f_hash[:data], &block)
|
12
|
+
@n_clusters = _calc_n_clusters(str)
|
13
|
+
@data = str.ljust(@n_clusters * CLUSTER_SIZE, PAD_CHR)
|
14
|
+
|
15
|
+
@filename = f_hash[:filename]
|
16
|
+
@extra_word = extra_word
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
DataEntry.new(
|
21
|
+
header: _make_header,
|
22
|
+
data: @data
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def _make_header
|
29
|
+
DataEntryHeader.new(
|
30
|
+
status: PERM_ENTRY,
|
31
|
+
filename: Filename.new(ascii: @filename).radix50,
|
32
|
+
n_clusters: @n_clusters,
|
33
|
+
ch_job: DEF_CH_JOB,
|
34
|
+
date: DEF_DATE,
|
35
|
+
extra_word: @extra_word
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def _process_data(arr, &block)
|
40
|
+
arr.map(&block)
|
41
|
+
.join("\r\n")
|
42
|
+
.prepend(0x0A.chr)
|
43
|
+
.prepend(0x0D.chr)
|
44
|
+
.concat(0x0D.chr)
|
45
|
+
.concat(0x0A.chr)
|
46
|
+
.concat(0x00.chr)
|
47
|
+
end
|
48
|
+
|
49
|
+
def _calc_n_clusters(str)
|
50
|
+
(str.length.to_f / CLUSTER_SIZE).ceil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Extracts files.
|
8
|
+
#
|
9
|
+
class FileExtracter
|
10
|
+
def initialize(data)
|
11
|
+
@data = data.reject { |e| e.status == EMPTY_ENTRY }
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Extract file as is.
|
16
|
+
#
|
17
|
+
# @param [Filename] file_id
|
18
|
+
#
|
19
|
+
# @return [FileInterface]
|
20
|
+
#
|
21
|
+
def f_extract_raw(file_id)
|
22
|
+
FileInterface.new(
|
23
|
+
filename: file_id.print_ascii,
|
24
|
+
data: _extract_raw_data(file_id)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Extract file as array of strings.
|
30
|
+
#
|
31
|
+
# @param [Filename] file_id
|
32
|
+
#
|
33
|
+
# @yield [str]
|
34
|
+
#
|
35
|
+
# @return [FileInterface]
|
36
|
+
#
|
37
|
+
def f_extract_txt(file_id, &block)
|
38
|
+
FileInterface.new(
|
39
|
+
filename: file_id.print_ascii,
|
40
|
+
data: _text_data(file_id, &block)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
#
|
47
|
+
# Extract file content as an array of strings.
|
48
|
+
#
|
49
|
+
# @param [Filename] id
|
50
|
+
#
|
51
|
+
# @yield [str]
|
52
|
+
#
|
53
|
+
# @return [Array<Strings>]
|
54
|
+
# Line by line content of the file.
|
55
|
+
#
|
56
|
+
def _text_data(id, &block)
|
57
|
+
_payload(id).split("\r\n")
|
58
|
+
.reject(&:empty?)
|
59
|
+
.map(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Return payload of a file.
|
64
|
+
#
|
65
|
+
# @param [Filename] id
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
#
|
69
|
+
def _payload(id)
|
70
|
+
_extract_raw_data(id).split(/\x00/).first
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Extract raw data of a file.
|
75
|
+
#
|
76
|
+
# @param [Filename]
|
77
|
+
#
|
78
|
+
# @return [String]
|
79
|
+
#
|
80
|
+
def _extract_raw_data(id)
|
81
|
+
index = @data.find_index { |e| e.filename == id.radix50 }
|
82
|
+
|
83
|
+
raise ArgumentError, "File '#{id.ascii}' not found in the volume" unless index
|
84
|
+
|
85
|
+
@data[index].data
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
module Utils
|
6
|
+
#
|
7
|
+
# Validates virtual volume params.
|
8
|
+
#
|
9
|
+
module VolumeParamsValidator
|
10
|
+
def self.call(params)
|
11
|
+
result = VolumeParamsContract.new.call(params)
|
12
|
+
|
13
|
+
raise ArgumentError, result.errors.to_h.to_a.join(": ") unless result.success?
|
14
|
+
|
15
|
+
result.schema_result.output
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SMPTool
|
4
|
+
module VirtualVolume
|
5
|
+
#
|
6
|
+
# Ruby representation of the volume.
|
7
|
+
#
|
8
|
+
class Volume
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :@volume_params, :n_clusters_allocated, :n_extra_bytes_per_entry, :n_dir_segs,
|
12
|
+
:n_clusters_per_dir_seg, :extra_word, :n_max_entries_per_dir_seg, :n_max_entries
|
13
|
+
|
14
|
+
attr_reader :bootloader, :home_block, :volume_params, :data
|
15
|
+
|
16
|
+
def self.read_volume_io(volume_io)
|
17
|
+
Utils::ConverterFromVolumeIO.read_volume_io(volume_io)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.read_io(volume_io)
|
21
|
+
Utils::ConverterFromVolumeIO.read_io(volume_io)
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Create new virtual volume.
|
26
|
+
#
|
27
|
+
# @param [Array<Integer>] bootloader
|
28
|
+
# @param [Array<Integer>] home_block
|
29
|
+
# @param [VolumeParams] volume_params
|
30
|
+
# @param [VolumeData, nil] volume_data
|
31
|
+
# Existing volume data (parsed from an existing VolumeIO obj) or nil.
|
32
|
+
# Nil indicates that an empty volume should be created, and empty data
|
33
|
+
# must be initialized.
|
34
|
+
#
|
35
|
+
def initialize(bootloader:, home_block:, volume_params:, volume_data: nil)
|
36
|
+
@bootloader = bootloader
|
37
|
+
@home_block = home_block
|
38
|
+
|
39
|
+
@volume_params = volume_params
|
40
|
+
@data = volume_data || Utils::EmptyVolDataInitializer.call(@volume_params)
|
41
|
+
end
|
42
|
+
|
43
|
+
def snapshot
|
44
|
+
{
|
45
|
+
volume_params: @volume_params.snapshot,
|
46
|
+
volume_data: @data.snapshot,
|
47
|
+
n_free_clusters: @data.calc_n_free_clusters
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Convert `self` to a VolumeIO object.
|
53
|
+
#
|
54
|
+
# @return [VolumeIO]
|
55
|
+
#
|
56
|
+
def to_volume_io
|
57
|
+
Utils::ConverterToVolumeIO.new(self).call
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Convert `self` to a binary string. Write this string to a binary file
|
62
|
+
# to get a MK90 volume that works on an emulator or on a real machine.
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
#
|
66
|
+
def to_binary_s
|
67
|
+
to_volume_io.to_binary_s
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Allocate more clusters to the volume or trim free clusters.
|
72
|
+
#
|
73
|
+
# @param [Integer] n_clusters
|
74
|
+
# Number of clusters to add (pos. int.) or to trim (neg. int.).
|
75
|
+
#
|
76
|
+
# @return [Integer]
|
77
|
+
# Number of clusters that were added/trimmed.
|
78
|
+
#
|
79
|
+
def resize(n_clusters)
|
80
|
+
if n_clusters.positive?
|
81
|
+
_resize_validate_pos_input(n_clusters)
|
82
|
+
elsif n_clusters.negative?
|
83
|
+
_resize_validate_neg_input(n_clusters)
|
84
|
+
else
|
85
|
+
return n_clusters
|
86
|
+
end
|
87
|
+
|
88
|
+
_resize(n_clusters)
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Push a file to the volume.
|
93
|
+
#
|
94
|
+
# @param [FileInterface, Hash{ Symbol => Object }] file_obj
|
95
|
+
#
|
96
|
+
# @yield [str]
|
97
|
+
# Each line of a file gets passed through this block. The default block encodes
|
98
|
+
# a string from the UTF-8 to the KOI-7, but a custom block allows to alter this
|
99
|
+
# behavior (e.g. when the file is already in the KOI-7 encoding).
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
# ASCII filename of the pushed file.
|
103
|
+
#
|
104
|
+
def f_push(file_obj, &block)
|
105
|
+
block = ->(str) { InjalidDejice.utf_to_koi(str, forced_latin: "\"") } unless block_given?
|
106
|
+
|
107
|
+
_f_push(file_obj, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Extract content of a file as an array of strings.
|
112
|
+
#
|
113
|
+
# @param [<String>] filename
|
114
|
+
# ASCII filename of the file to extract.
|
115
|
+
#
|
116
|
+
# @yield [str]
|
117
|
+
# Each line of a file gets passed through this block. The default block decodes
|
118
|
+
# a string from the KOI-7 to the UTF-8, but a custom block allows to alter this
|
119
|
+
# behavior.
|
120
|
+
#
|
121
|
+
# @return [FileInterface]
|
122
|
+
#
|
123
|
+
def f_extract_txt(filename, &block)
|
124
|
+
block = ->(str) { InjalidDejice.koi_to_utf(str) } unless block_given?
|
125
|
+
|
126
|
+
Utils::FileExtracter.new(@data).f_extract_txt(
|
127
|
+
Filename.new(ascii: filename),
|
128
|
+
&block
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Extract all files as arrays of strings.
|
134
|
+
#
|
135
|
+
# @return [Array<FileInterface>]
|
136
|
+
#
|
137
|
+
def f_extract_txt_all
|
138
|
+
_all_filenames.map { |fn| f_extract_txt(fn) }
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Extract content of a file as a 'raw' string (as is).
|
143
|
+
#
|
144
|
+
# @param [<String>] filename
|
145
|
+
# ASCII filename of the file to extract.
|
146
|
+
#
|
147
|
+
# @return [FileInterface]
|
148
|
+
#
|
149
|
+
def f_extract_raw(filename)
|
150
|
+
Utils::FileExtracter.new(@data).f_extract_raw(
|
151
|
+
Filename.new(ascii: filename)
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Extract all files as 'raw' strings.
|
157
|
+
#
|
158
|
+
# @return [Array<FileInterface>]
|
159
|
+
#
|
160
|
+
def f_extract_raw_all
|
161
|
+
_all_filenames.map { |fn| f_extract_raw(fn) }
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Rename a file.
|
166
|
+
#
|
167
|
+
# @param [<String>] old_filename
|
168
|
+
# @param [<String>] new_filename
|
169
|
+
#
|
170
|
+
# @return [Array<String>]
|
171
|
+
# Old and new ASCII filenames of a renamed file.
|
172
|
+
#
|
173
|
+
def f_rename(old_filename, new_filename)
|
174
|
+
@data.f_rename(
|
175
|
+
Filename.new(ascii: old_filename),
|
176
|
+
Filename.new(ascii: new_filename)
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Delete a file.
|
182
|
+
#
|
183
|
+
# @param [<String>] filename
|
184
|
+
#
|
185
|
+
# @return [String]
|
186
|
+
# ASCII filename of a deleted file.
|
187
|
+
#
|
188
|
+
def f_delete(filename)
|
189
|
+
@data.f_delete(Filename.new(ascii: filename))
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Consolidate all free space at the end ot the volume.
|
194
|
+
#
|
195
|
+
# @return [Integer]
|
196
|
+
# Number of free clusters that were joined.
|
197
|
+
#
|
198
|
+
def squeeze
|
199
|
+
@data.squeeze
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def _all_filenames
|
205
|
+
@data.reject { |e| e.status == EMPTY_ENTRY }
|
206
|
+
.map { |e| e.header.print_ascii_filename }
|
207
|
+
end
|
208
|
+
|
209
|
+
def _resize_validate_pos_input(n_delta_clusters)
|
210
|
+
_check_dir_overflow
|
211
|
+
|
212
|
+
return if n_delta_clusters + @volume_params.n_clusters_allocated <= N_CLUSTERS_MAX
|
213
|
+
|
214
|
+
raise ArgumentError, "Volume size can't be more than #{N_CLUSTERS_MAX} clusters"
|
215
|
+
end
|
216
|
+
|
217
|
+
def _resize_validate_neg_input(n_delta_clusters)
|
218
|
+
n_free_clusters = @data.calc_n_free_clusters
|
219
|
+
|
220
|
+
return if n_delta_clusters.abs <= n_free_clusters
|
221
|
+
|
222
|
+
raise ArgumentError, "Can't trim more than #{n_free_clusters} clusters"
|
223
|
+
end
|
224
|
+
|
225
|
+
def _resize(n_clusters)
|
226
|
+
@volume_params.n_clusters_allocated += n_clusters
|
227
|
+
@data.resize(n_clusters)
|
228
|
+
end
|
229
|
+
|
230
|
+
def _check_dir_overflow
|
231
|
+
raise ArgumentError, "Directory table is full" if @data.length >= @volume_params.n_max_entries
|
232
|
+
end
|
233
|
+
|
234
|
+
def _f_push(f_hash, &block)
|
235
|
+
@data.squeeze
|
236
|
+
|
237
|
+
_check_dir_overflow
|
238
|
+
|
239
|
+
file = SMPTool::VirtualVolume::Utils::FileConverter.new(
|
240
|
+
f_hash,
|
241
|
+
@volume_params.extra_word,
|
242
|
+
&block
|
243
|
+
).call
|
244
|
+
|
245
|
+
@data.f_push(file)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|