// 目录记录(ECMA-119 §9.1):ISO 9660 里唯一描述「文件/目录」的结构,
// 目录本身也是一串目录记录,只有 0 长度记录表示本扇区剩余部分是填充。
///|
/// 目录记录的 7 字节时间戳(§9.1.5):
/// 年 = 字段值 + 1900,最后 1 字节是有符号的 15 分钟时区偏移。
pub struct RecordDate {
year : Int32
month : Int32
day : Int32
hour : Int32
minute : Int32
second : Int32
gmt_offset_quarters : Int32
}
///|
pub fn RecordDate::to_iso_string(self : RecordDate) -> String {
let pad = fn(n : Int32) -> String {
if n < 10 {
"0" + n.to_string()
} else {
n.to_string()
}
}
self.year.to_string() +
"-" +
pad(self.month) +
"-" +
pad(self.day) +
"T" +
pad(self.hour) +
":" +
pad(self.minute) +
":" +
pad(self.second)
}
///|
/// 一条目录记录。
///
/// - extent:文件内容起始的逻辑块号(LBA),目录则是目录数据起始 LBA;
/// - size:数据长度(字节),目录则是目录数据总长度;
/// - name:去掉 `;版本号` 后的名字,目录为原始目录名;
/// - is_dir:flags 的 bit 1。
pub struct DirectoryRecord {
extent : Int32
size : Int32
flags : Int32
name : String
date : RecordDate
}
///|
pub fn DirectoryRecord::is_dir(self : DirectoryRecord) -> Bool {
(self.flags & 2) != 0
}
///|
/// 特殊记录:0x00 是「本目录」,0x01 是「父目录」。
pub fn DirectoryRecord::is_special(self : DirectoryRecord) -> Bool {
self.name == "\u{0}" || self.name == "\u{1}"
}
///|
/// 去掉 ISO 9660 的 `;版本号`,并去掉「无扩展名」时残留的点号。
pub fn normalize_iso_name(raw : String) -> String {
let mut name = raw
match name.find(";") {
Some(i) => name = name[:i].to_owned()
None => ()
}
if name.length() > 1 && name.has_suffix(".") {
name = name[:name.length() - 1].to_owned()
}
name
}
///|
/// 从 reader 当前位置解析一条目录记录,返回记录本身与消耗的字节数。
/// 返回长度为 0 表示目录数据在本扇区后面是填充。
pub fn parse_directory_record(
reader : ByteReader,
) -> (DirectoryRecord, Int32) raise IsoError {
let start = reader.position()
let len = reader.read_u8().reinterpret_as_int()
if len == 0 {
// 填充:调用方负责跳到下一个扇区
return (
DirectoryRecord::{
extent: 0,
size: 0,
flags: 0,
name: "",
date: RecordDate::{
year: 0,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
gmt_offset_quarters: 0,
},
},
0,
)
}
if len < 34 {
raise BadDirectoryRecord(len)
}
let _ext_attr_len = reader.read_u8().reinterpret_as_int()
let extent = reader.read_u32_both()
let size = reader.read_u32_both()
let year = reader.read_u8().reinterpret_as_int() + 1900
let month = reader.read_u8().reinterpret_as_int()
let day = reader.read_u8().reinterpret_as_int()
let hour = reader.read_u8().reinterpret_as_int()
let minute = reader.read_u8().reinterpret_as_int()
let second = reader.read_u8().reinterpret_as_int()
let gmt_raw = reader.read_u8().reinterpret_as_int()
// 时区偏移是 8 位补码,单位 15 分钟
let gmt = if gmt_raw >= 128 { gmt_raw - 256 } else { gmt_raw }
let flags = reader.read_u8().reinterpret_as_int()
let _file_unit_size = reader.read_u8().reinterpret_as_int()
let _interleave_gap = reader.read_u8().reinterpret_as_int()
let _volume_seq = reader.read_u16_both()
let name_len = reader.read_u8().reinterpret_as_int()
let name = if name_len == 1 {
let c = reader.read_u8().reinterpret_as_int()
if c == 0 {
"\u{0}"
} else {
"\u{1}"
}
} else {
normalize_iso_name(reader.read_ascii(name_len))
}
// 名字长度是偶数时,后面补 1 字节
if name_len % 2 == 0 {
reader.skip(1)
}
let consumed = reader.position() - start
// 跳过 system use area(Rock Ridge 将来在这里解析)
if consumed < len {
reader.skip(len - consumed)
}
(
DirectoryRecord::{
extent,
size,
flags,
name,
date: RecordDate::{
year,
month,
day,
hour,
minute,
second,
gmt_offset_quarters: gmt,
},
},
len,
)
}