Документация Engee

Реализация методов загрузки и сохранения

Принцип работы: квалификация модуля

Когда пакет FileIO обнаруживает, что файл или поток должны обрабатываться определенным пакетом, он пытается вызвать частные методы из этого пакета для обработки запроса. Например, предположим, что вы создали пакет MyFileFormat для обработки файлов определенного формата. При вызове load("somefile.myfmt") для подходящего файла пакет FileIO выполнит следующие действия:

  • попытается загрузить ваш пакет MyFileFormat с помощью Base.require(id::PkgId), где PkgId — это сочетание имени и UUID, которые вы предоставили посредством add_format;

  • вызовет MyFileFormat.load(file), где file — это объект File.

Ключевым моментом является то, что MyFileFormat.load не расширяет FileIO.load: это частная функция, определенная в модуле MyFileFormat. Это необходимо для того, чтобы один формат мог поддерживаться несколькими пакетами; если бы в двух или более пакетах метод File.load специализировался для file::File{format"MYFORMAT"}), то инструкция

using Pkg1, Pkg2   # два пакета, неправильно расширяющие FileIO.load

привела бы к обработке всех операций загрузки пакетом Pkg2, но инструкция

using Pkg2, Pkg1

привела бы к их обработке пакетом Pkg1. Это сделало бы загрузку очень ненадежной. По этой причине важно, чтобы метод load был частным методом пакета, а пакет FileIO вызывал его посредством квалификации модуля.

То же самое относится к методам save, loadstreaming и savestreaming.

Если происходит конфликт именования функций load и save (например, в вашем пакете уже есть другая функция с одним из этих имен), вы можете назвать методы загрузки fileio_load, fileio_save и т. д. Обратите внимание, что смешивать эти стили нельзя: либо все методы загрузки должны называться load, либо все они должны называться fileio_load. Сочетать оба варианта в одном модуле нельзя.

Единовременный ввод-вывод: реализация load и save

В пакете напишите код наподобие следующего:

module MyFileFormat

using FileIO

# Еще раз напомним, что это *частная* функция `load`, она не расширяет `FileIO.load`!
function load(f::File{format"PNG"})
    open(f) do s
        skipmagic(s)  # пропускаем магические байты
        # Вы можете просто вызвать приведенный ниже метод `load(::Stream)...
        ret = load(s)
        # ...или же реализовать все здесь
    end
end

# Вы можете обеспечить поддержку потоков и добавить именованные аргументы:
function load(s::Stream{format"PNG"}; keywords...)
    # s уже находится в позиции после магических байтов
    # Инструкции для считывания файла PNG
    chunklength = read(s, UInt32)
    ...
end

function save(f::File{format"PNG"}, data)
    open(f, "w") do s
        # Не забудьте записать магические байты!
        write(s, magic(format"PNG"))
        # Остальные инструкции, необходимые для сохранения в формате PNG
    end
end

end # module MyFileFormat

Методы load(::File) и save(::File) должны закрывать открытые ими потоки. (При использовании синтаксиса do это происходит автоматически, даже если код в области do выдает ошибку.) Напротив, методы load(::Stream) и save(::Stream) не должны закрывать переданные в аргументе потоки.

Реализация потокового ввода-вывода

Методы loadstreaming и savestreaming используют тот же механизм запросов, но возвращают декодированный поток, который пользователи могут прочитать (read) или записать (write). В типе метода чтения или записи следует также реализовать метод close. Как и в случае с load и save, если пользователь предоставил имя файла, метод close должен отвечать за закрытие всех потоков, открытых с целью чтения или записи файла. Если предоставлен поток (Stream), метод close должен выполнять только очистку для типа метода чтения или записи, но не закрывать поток.

struct WAVReader
    io::IO
    ownstream::Bool
end

function Base.read(reader::WAVReader, frames::Int)
    # считываем и декодируем звуковые фрагменты из reader.io
end

function Base.close(reader::WAVReader)
    # выполняем очистку, необходимую после считывания данных
    reader.ownstream && close(reader.io)
end

# В FileIO есть альтернативные функции, которые выполняют эти задачи с использованием синтаксиса `do`
# и автоматически вызывают `close` для возвращенного объекта.
loadstreaming(f::File{format"WAV"}) = WAVReader(open(f), true)
loadstreaming(s::Stream{format"WAV"}) = WAVReader(s, false)

Если вы решите реализовать loadstreaming и savestreaming в своем пакете, то можете легко добавить методы save и load в следующей форме:

function save(q::Formatted{format"WAV"}, data, args...; kwargs...)
    savestreaming(q, args...; kwargs...) do stream
        write(stream, data)
    end
end

function load(q::Formatted{format"WAV"}, args...; kwargs...)
    loadstreaming(q, args...; kwargs...) do stream
        read(stream)
    end
end

где Formatted — это абстрактный супертип типов File и Stream.