Skip to content

Commit

Permalink
Making things more type stable (#13)
Browse files Browse the repository at this point in the history
* making things more type stable

* fix 1.6 compat
  • Loading branch information
nhz2 authored Jun 21, 2023
1 parent d3cac61 commit 36da618
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 35 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ version = "0.2.1"
[deps]
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
StringViews = "354b36f9-a18e-4713-926e-db85100087ba"
TranscodingStreams = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
Zlib_jll = "83775a58-1f1d-513f-b197-d71354ab007a"
Expand Down
27 changes: 27 additions & 0 deletions src/ZipArchives.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module ZipArchives

using PrecompileTools

include("constants.jl")
include("filename-checks.jl")

Expand Down Expand Up @@ -37,4 +39,29 @@ export zip_mkdir

# include("high-level.jl")

@setup_workload begin
# Putting some things in `@setup_workload` instead of `@compile_workload` can reduce the size of the
# precompile file and potentially make loading faster.
data1 = [0x01,0x04,0x08]
data2 = codeunits("data2")
@compile_workload begin
# all calls in this block will be precompiled, regardless of whether
# they belong to your package or not (on Julia 1.8 and higher)
io = IOBuffer()
ZipWriter(io) do w
zip_writefile(w, "test1", data1)
zip_writefile(w, "test2", data2)
end
mktemp() do path, fileio
ZipWriter(fileio) do w
zip_writefile(w, "test1", data1)
zip_writefile(w, "test2", data2)
end
end
zipdata = take!(io)
r = ZipBufferReader(zipdata)
zip_readentry(r, 1)
end
end

end
34 changes: 21 additions & 13 deletions src/reader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ end
Return the standard zip CRC32 checksum of data
"""
function zip_crc32(data::Base.ByteArray, crc::UInt32=UInt32(0))::UInt32
function zip_crc32(data::ByteArray, crc::UInt32=UInt32(0))::UInt32
GC.@preserve data unsafe_crc32(pointer(data), UInt(length(data)), crc)
end

Expand Down Expand Up @@ -87,20 +87,20 @@ Otherwise, return nothing.
This will also read the entry and check the crc32 matches.
"""
function zip_test_entry(r::ZipReader, i)::Nothing
crc32::UInt32 = 0
uncompressed_size::UInt64 = 0
crc32 = Ref(UInt32(0))
uncompressed_size = Ref(UInt64(0))
zip_openentry(r, i) do io
buffer_size = 1<<12
buffer = zeros(UInt8, buffer_size)
GC.@preserve buffer while !eof(io)
nb = readbytes!(io, buffer)
@argcheck uncompressed_size < typemax(Int64)
uncompressed_size += nb
crc32 = unsafe_crc32(pointer(buffer), UInt(nb), crc32)
@argcheck uncompressed_size[] < typemax(Int64)
uncompressed_size[] += nb
crc32[] = unsafe_crc32(pointer(buffer), UInt(nb), crc32[])
end
end
@argcheck uncompressed_size == zip_uncompressed_size(r, i)
@argcheck crc32 == zip_stored_crc32(r, i)
@argcheck uncompressed_size[] == zip_uncompressed_size(r, i)
@argcheck crc32[] == zip_stored_crc32(r, i)
nothing
end

Expand Down Expand Up @@ -433,8 +433,8 @@ function ZipFileReader(filename::AbstractString)
end
end

function ZipFileReader(f::Function, filename::AbstractString; kwargs...)
r = ZipFileReader(filename; kwargs...)
function ZipFileReader(f::Function, filename::AbstractString)
r = ZipFileReader(filename)
try
f(r)
finally
Expand Down Expand Up @@ -498,7 +498,7 @@ function zip_openentry(r::ZipFileReader, i::Integer)::TranscodingStream
end
offset::Int64 = entry.offset
method = entry.method
lock(r._lock) do
Base.@lock r._lock begin
# read and validate local header
seek(r._io, offset)
@argcheck readle(r._io, UInt32) == 0x04034b50
Expand Down Expand Up @@ -536,8 +536,8 @@ function zip_openentry(r::ZipFileReader, i::Integer)::TranscodingStream
end
end

function zip_openentry(f::Function, r::ZipReader, args...; kwargs...)
io = zip_openentry(r, args...; kwargs...)
function zip_openentry(f::Function, r::ZipReader, args...)
io = zip_openentry(r, args...)
try
f(io)
finally
Expand Down Expand Up @@ -571,6 +571,14 @@ function Base.unsafe_read(io::ZipFileEntryReader, p::Ptr{UInt8}, n::UInt)::Nothi
nothing
end

function Base.read(io::ZipFileEntryReader, ::Type{UInt8})
error("ZipFileEntryReader does not support byte I/O")
end

function Base.unsafe_write(io::ZipFileEntryReader, p::Ptr{UInt8}, n::UInt)
throw(ArgumentError("ZipFileEntryReader not writable"))
end

Base.position(io::ZipFileEntryReader)::Int64 = io.p

function Base.seek(io::ZipFileEntryReader, n::Integer)::ZipFileEntryReader
Expand Down
27 changes: 18 additions & 9 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ using StringViews

const empty_buffer = view(UInt8[],1:0)



const ByteArray = Union{
Base.CodeUnits{UInt8, String},
Vector{UInt8},
Base.FastContiguousSubArray{UInt8,1,Base.CodeUnits{UInt8,String}},
Base.FastContiguousSubArray{UInt8,1,Vector{UInt8}}
}

"""
This is an internal type.
Info about an entry in a zip file.
Expand Down Expand Up @@ -35,8 +44,8 @@ struct ZipFileReader
central_dir_buffer::Vector{UInt8}
central_dir_offset::Int64
_io::IOStream
_ref_counter::Ref{Int64}
_open::Ref{Bool}
_ref_counter::Base.RefValue{Int64}
_open::Base.RefValue{Bool}
_lock::ReentrantLock
_fsize::Int64
_name::String
Expand All @@ -55,7 +64,7 @@ mutable struct ZipFileEntryReader <: IO
offset::Int64
crc32::UInt32
compressed_size::Int64
_open::Ref{Bool}
_open::Base.RefValue{Bool}
end


Expand All @@ -67,7 +76,7 @@ struct ZipBufferReader{T<:AbstractVector{UInt8}}
end


Base.@kwdef mutable struct PartialEntry
Base.@kwdef mutable struct PartialEntry{S<:IO}
name::String
comment::String = ""
external_attrs::UInt32 = UInt32(0o0100644)<<16 # external file attributes: https://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute
Expand All @@ -81,15 +90,15 @@ Base.@kwdef mutable struct PartialEntry
compressed_size::UInt64 = 0
uncompressed_size::UInt64 = 0
local_header_size::Int64 = 50 + ncodeunits(name)
transcoder::Union{Nothing, NoopStream, DeflateCompressorStream} = nothing
transcoder::Union{Nothing, NoopStream{S}, DeflateCompressorStream{S}} = nothing
end

mutable struct ZipWriter <: IO
_io::IO
mutable struct ZipWriter{S<:IO} <: IO
_io::S
_own_io::Bool
entries::Vector{EntryInfo}
central_dir_buffer::Vector{UInt8}
partial_entry::Union{Nothing, PartialEntry}
partial_entry::Union{Nothing, PartialEntry{S}}
closed::Bool
force_zip64::Bool
used_names_lower::Set{String}
Expand All @@ -99,7 +108,7 @@ mutable struct ZipWriter <: IO
own_io::Bool=false,
force_zip64::Bool=false,
)
new(
new{typeof(io)}(
io,
own_io,
EntryInfo[],
Expand Down
35 changes: 22 additions & 13 deletions src/writer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using TranscodingStreams


"""
mutable struct ZipWriter <: IO
mutable struct ZipWriter{S<:IO} <: IO
ZipWriter(io::IO; zip_kwargs...)
ZipWriter(f::Function, io::IO; zip_kwargs...)
Expand Down Expand Up @@ -182,7 +182,7 @@ function zip_newfile(w::ZipWriter, name::AbstractString;
end
@assert !iswritable(w)
io = w._io
pe = PartialEntry(;
pe = PartialEntry{typeof(io)}(;
name=namestr,
w.force_zip64,
offset=0, # place holder offset
Expand Down Expand Up @@ -235,11 +235,18 @@ function write_buffer(b::Vector{UInt8}, p::Int, x::Integer)::Int
sizeof(x)
end
function write_buffer(b::Vector{UInt8}, p::Int, x::AbstractVector{UInt8})::Int
b[p:p+length(x)-1] .= x
copyto!(b, p, x, firstindex(x), length(x))
# b[p:p+length(x)-1] .= x
length(x)
end
function write_buffer(b::Vector{UInt8}, p::Int, x::String)::Int
write_buffer(b, p, codeunits(x))
nb = ncodeunits(x)
data = codeunits(x)
for i in eachindex(data)
b[p+i-1] = data[i]
end
nb
# write_buffer(b, p, codeunits(x))
end


Expand All @@ -260,8 +267,8 @@ function Base.unsafe_write(w::ZipWriter, p::Ptr{UInt8}, n::UInt)::Int
iszero(n) && return 0
(n > typemax(Int)) && throw(ArgumentError("too many bytes. Tried to write $n bytes"))
assert_writeable(w)
pe::PartialEntry = w.partial_entry
nb::UInt = unsafe_write(pe.transcoder, p, n)
pe = something(w.partial_entry)
nb::UInt = unsafe_write(something(pe.transcoder), p, n)
pe.crc32 = unsafe_crc32(p, nb, pe.crc32)
pe.uncompressed_size += nb
# pe.entry.compressed_size is updated in zip_commitfile
Expand All @@ -281,16 +288,17 @@ then rethrow the error.
"""
function zip_commitfile(w::ZipWriter)
if iswritable(w)
pe::PartialEntry = w.partial_entry
pe = something(w.partial_entry)
transcoder = something(pe.transcoder)
w.partial_entry = nothing
# If some error happens, the file will be partially written,
# but not included in the central directory.
# Finish the compressing here, but don't close underlying IO.
try
write(pe.transcoder, TranscodingStreams.TOKEN_END)
write(transcoder, TranscodingStreams.TOKEN_END)
finally
# Prevent memory leak maybe.
TranscodingStreams.finalize(pe.transcoder.codec)
TranscodingStreams.finalize(transcoder.codec)
end
cur_offset = position(w._io)
pe.compressed_size = cur_offset - pe.offset - pe.local_header_size
Expand Down Expand Up @@ -330,14 +338,15 @@ so will be ignored when the zip archive is read.
"""
function zip_abortfile(w::ZipWriter)
if iswritable(w)
pe::PartialEntry = w.partial_entry
pe = something(w.partial_entry)
transcoder = something(pe.transcoder)
w.partial_entry = nothing
# Finish the compressing here, but don't close underlying IO.
try
write(pe.transcoder, TranscodingStreams.TOKEN_END)
write(transcoder, TranscodingStreams.TOKEN_END)
finally
# Prevent memory leak maybe.
TranscodingStreams.finalize(pe.transcoder.codec)
TranscodingStreams.finalize(transcoder.codec)
end
end
nothing
Expand Down Expand Up @@ -373,7 +382,7 @@ function zip_writefile(w::ZipWriter, name::AbstractString, data::AbstractVector{
@assert !iswritable(w)
io = w._io
crc32 = zip_crc32(data)
pe = PartialEntry(;
pe = PartialEntry{typeof(io)}(;
name=namestr,
offset=0,# place holder offset
w.force_zip64,
Expand Down
1 change: 1 addition & 0 deletions test/test_reader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ function rewrite_zip(old::AbstractString, new::AbstractString)
else
ZipArchives.Store
end
# zip_writefile(w, name, zip_readentry(r, i); executable=isexe)
zip_newfile(w, name; executable=isexe, compression_method= comp)
zip_openentry(r, i) do io
write(w, io)
Expand Down

0 comments on commit 36da618

Please sign in to comment.