Commit dfd086fb authored by Alberto Ramos's avatar Alberto Ramos

First version of the BDIO package in julia

parents
# This file is machine-generated - editing it directly is not advised
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[BinDeps]]
deps = ["Compat", "Libdl", "SHA", "URIParser"]
git-tree-sha1 = "12093ca6cdd0ee547c39b1870e0c9c3f154d9ca9"
uuid = "9e28174c-4ba2-5203-b857-d8d62c4213ee"
version = "0.8.10"
[[BinaryProvider]]
deps = ["Libdl", "Logging", "SHA"]
git-tree-sha1 = "c7361ce8a2129f20b0e05a89f7070820cfed6648"
uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
version = "0.5.6"
[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "ed2c4abadf84c53d9e58510b5fc48912c2336fbb"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "2.2.0"
[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[DocStringExtensions]]
deps = ["LibGit2", "Markdown", "Pkg", "Test"]
git-tree-sha1 = "88bb0edb352b16608036faadcc071adda068582a"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.1"
[[Documenter]]
deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
git-tree-sha1 = "d45c163c7a3ae293c15361acc52882c0f853f97c"
uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
version = "0.23.4"
[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.0"
[[LibGit2]]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[Nettle]]
deps = ["BinDeps", "BinaryProvider", "Libdl", "Test"]
git-tree-sha1 = "f57e8e907faab4f55f9f164313a633509ac83e2c"
uuid = "49dea1ee-f6fa-5aa6-9a11-8816cee7d4b9"
version = "0.4.0"
[[Parsers]]
deps = ["Dates", "Test"]
git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "0.3.7"
[[Pkg]]
deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
[[Test]]
deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[URIParser]]
deps = ["Test", "Unicode"]
git-tree-sha1 = "6ddf8244220dfda2f17539fa8c9de20d6c575b69"
uuid = "30578b45-9adc-5946-b283-645ec420af67"
version = "0.4.0"
[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
name = "BDIO"
uuid = "375f315e-f2c4-11e9-2ef9-134f02f79e27"
authors = ["Alberto Ramos <alberto.ramos@desy.de>"]
version = "0.1.0"
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Nettle = "49dea1ee-f6fa-5aa6-9a11-8816cee7d4b9"
using Documenter, BDIO
makedocs(modules=[BDIO], doctest=true)
deploydocs(deps = Deps.pip("mkdocs", "python-markdown-math"),
repo = "github.com/GITHUBNAME/GITHUBREPO.git",
julia = "1.1.1",
osname = "FreeBSD")
# BDIO in Julia
This package provides an interface to read/write
[BDIO](http://bdio.org/) files in Julia.
## Externally callable functions
### Openning `BDIO` files
```@docs
BDIO.BDIO_open
```
### Writing data to `BDIO` files
```@docs
BDIO.BDIO_start_record!
BDIO.BDIO_write!
BDIO.BDIO_write_hash!
```
### Reading data from `BDIO` files
```@docs
BDIO.BDIO_read
BDIO.BDIO_seek!
```
### Obtaining information about `BDIO` records
```@docs
BDIO.BDIO_get_uinfo
BDIO.BDIO_get_len
BDIO.BDIO_get_fmt
```
## Index
```@index
```
module BDIO
import Nettle
export BDIO_BIN_GENERIC, BDIO_ASC_EXEC, BDIO_BIN_INT32BE,
BDIO_BIN_INT32LE, BDIO_BIN_INT64BE, BDIO_BIN_INT64LE,
BDIO_BIN_F32BE, BDIO_BIN_F32LE, BDIO_BIN_F64BE, BDIO_BIN_F64LE,
BDIO_ASC_GENERIC, BDIO_ASC_XML, BDIO_BIN_INT32, BDIO_BIN_INT64,
BDIO_BIN_F32, BDIO_BIN_F64
export BDIO, BDIO_open, BDIO_start_record!, BDIO_write_hash!,
BDIO_write!, BDIO_seek!, BDIO_get_len, BDIO_get_fmt,
BDIO_get_uinfo, BDIO_read
const BDIO_R_MODE = 0
const BDIO_W_MODE = 1
const BDIO_A_MODE = 2
const BDIO_D_MODE = 3
const MAXFNAME = 4096
const BDIO_SHORT_LEN = 256
const BDIO_LONG_LEN = 4096
const BDIO_MAGIC = 2147209342
const BDIO_VERSION = 1
const BDIO_R_STATE = 0
const BDIO_W_STATE = 1
const BDIO_BIN_GENERIC = 0
const BDIO_ASC_EXEC = 1
const BDIO_BIN_INT32BE = 2
const BDIO_BIN_INT32LE = 3
const BDIO_BIN_INT64BE = 4
const BDIO_BIN_INT64LE = 5
const BDIO_BIN_F32BE = 6
const BDIO_BIN_F32LE = 7
const BDIO_BIN_F64BE = 8
const BDIO_BIN_F64LE = 9
const BDIO_ASC_GENERIC = 10
const BDIO_ASC_XML = 11
const BDIO_BIN_INT32 = 240
const BDIO_BIN_INT64 = 241
const BDIO_BIN_F32 = 242
const BDIO_BIN_F64 = 243
const BDIO_HASH_MAGIC_S = 1515784845
user = " "
host = " "
mutable struct Record
ishdr::Bool
islong::Bool
# Record length, start, end
rlen::Int64
rpos::Int64
rend::Int64
rfmt::Int32
ruinfo::Int8
hsh::Nettle.Hasher
end
mutable struct BDIOstream
io::IO
ipt::Int64
imode::Int32
istate::Int32
rwpos::Int64
lendian::Bool
user::String
host::String
created::Int32
modified::Int32
info::String
rdate::Int64
records::Array{Record,1}
end
"""
Set user name globally for writing BDIO files.
### Example
BDIO_set_user("alberto")
"""
function BDIO_set_user(us::String)
global user = SubString(us*user, 1, 255)
return true
end
"""
Set host machine globally for writing BDIO files.
### Example
BDIO_set_host("HLRN")
"""
function BDIO_set_host(us::String)
global host = SubString(us*host, 1, 255)
return true
end
function BDIOstream(fname::String, mode::String, protocol_info::String="\0")
md::Int32 = 0
if (mode == "w")
if (isfile(fname))
error("File "*strip(fname)*" already exists")
end
io = open(fname, "w+")
md = BDIO_W_MODE
elseif (mode == "r")
io = open(fname, "r")
md = BDIO_R_MODE
elseif (mode == "a")
io = open(fname, "a+")
md = BDIO_A_MODE
elseif (mode == "d")
rm(fnme, force=true)
io = open(fname, "w+")
md = BDIO_D_MODE
else
error("Incorrect mode")
end
rcs = similar(Array{Record,1},0)
lend::Bool = true
if (Base.ENDIAN_BOM == 0x04030201)
lend = true
elseif (Base.ENDIAN_BOM == 0x01020304)
lend = false
else
error("Wrong endianness")
end
if (!lend)
error("Current version only support Little Endian Machines")
end
return BDIOstream(io,1,md,0,0,lend,user,host,
floor(Int32, time()), floor(Int32, time()), protocol_info, 0, rcs)
end
"""
Opens a BDIO file and returns the BDIO handle. The file can be opened in several modes:
- Write mode ("w"): The file is created and a header written. If the file exists an error is printed.
- Write mode ("d"): The file is created and a header written. If the file exists it is overwritten.
- Write mode ("a"): The file is created if it does not exist, or opened for appending if the file exists.
- Write mode ("r"): The file is opened for reading.
## Arguments
- `fname`: File name
- `mode`: The mode in which the file is opened. See above.
- `protocol_info`: Only used when the file is created (i.e. "w" mode) and labels the file.
## Examples
```julia-repl
julia> fb = BDIO_open("new_file.bdio", "w", "Test file")
```
## Returns
A BDIOstream type.
"""
function BDIO_open(fname::String, mode::String, protocol_info::String="")
fb = BDIOstream(fname,mode)
fb.info = protocol_info
fb.user = user
fb.host = host
if (mode == "w")
fb.created = floor(Int32, time())
fb.modified = floor(Int32, time())
BDIO_write_header!(fb)
elseif (mode == "r")
BDIO_parse!(fb)
fb.ipt = 1
elseif (mode == "a")
BDIO_parse!(fb)
fb.ipt = length(fb.records)
elseif (mode == "d")
BDIO_parse!(fb)
fb.ipt = length(fb.records)
else
error("Incorrect mode")
end
return fb
end
function BDIO_write_header!(fb::BDIOstream)
if (fb.imode == BDIO_R_MODE)
error("Attemp to write in READ mode")
end
ihdr::Int32 = BDIO_MAGIC
write(fb.io, ihdr)
mark(fb.io)
ill::Int16 = 0
write(fb.io, ill)
iv::Int16 = 1
write(fb.io, iv)
ist::Int64 = position(fb.io)
i4::Int32 = 0
write(fb.io, i4)
hcr::Int64 = position(fb.io)
write(fb.io, fb.created)
fb.rdate = position(fb.io)
write(fb.io, fb.modified)
write(fb.io, user*"\0")
hlu::Int64 = position(fb.io)
write(fb.io, user*"\0")
write(fb.io, host*"\0")
hlh::Int64 = position(fb.io)
write(fb.io, host*"\0")
write(fb.io, fb.info*"\0")
ind::Int64 = position(fb.io)
ill = Int16(ind-ist)
reset(fb.io)
write(fb.io, ill)
flush(fb.io)
hsh = Nettle.Hasher("md5")
new = Record(true,false,Int64(ill),ist,ind,0,0,hsh)
push!(fb.records, new)
fb.ipt += 1
return true
end
"""
Start a new BDIO record at the end of the file. Currently the supported formats are
- `BDIO_BIN_GENERIC`: Generic binary data
- `BDIO_ASC_EXEC`: ASCII Executable (i.e. `sh` script)
- `BDIO_BIN_INT32BE`: 32-bit integer in Big Endian format
- `BDIO_BIN_INT32LE`: 32-bit integer in Little Endian format
- `BDIO_BIN_INT64BE`: 64-bit integer in Big Endian format
- `BDIO_BIN_INT64LE`: 64-bit integer in Little Endian format
- `BDIO_BIN_F32BE`: 32-bit float in Big Endian format
- `BDIO_BIN_F32LE`: 32-bit float in Little Endian format
- `BDIO_BIN_F64BE`: 64-bit float in Big Endian format
- `BDIO_BIN_F64LE`: 64-bit float in Little Endian format
- `BDIO_ASC_GENERIC`: ASCII text file
- `BDIO_ASC_XML`: Plain XML data
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file in either `w` or `a` mode
- `ifmt`: Format (see above).
- `iuinfo`: Integer in the range 0-15. A user specified label to help indentifying the record
- `long` (optional): If true create a long record. To store more than 1048575 bytes of data (``\\approx 1\\, {\\rm MB}``), a long record is required. Default value of `false`.
## Examples
```julia-repl
julia> fb = BDIO_open("new_file.bdio", "w", "Test file")
julia> BDIO_start_record!(fb, BDIO.BDIO_BIN_F32, 2)
```
"""
function BDIO_start_record!(fb::BDIOstream, ifmt, iuinfo, long::Bool = false)
if (fb.imode == BDIO_R_MODE)
error("Attemp to write in READ mode")
end
seekend(fb.io)
shdr::Int32 = 0
if long
shdr = 0b1001
else
shdr = 0b1
end
shdr = shdr | ifmt<<4
shdr = shdr | iuinfo<<8
write(fb.io,shdr)
if long
zero::Int32 = 0
write(fb.io,zero)
end
ist::Int64 = position(fb.io)
hsh = Nettle.Hasher("md5")
new = Record(false,long,Int64(0),ist,ist,Int32(ifmt),Int8(iuinfo),hsh)
push!(fb.records, new)
fb.ipt += 1
flush(fb.io)
return true
end
"""
Write `data` to BDIO file to the end of the last record.
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file in either `w` or `a` mode.
- `data`: Data to write to file.
- `hash` (optional): Bool. If true, data will be checksummed before writtento file. The hash can be stored in a BDIO record by calling `BDIO_write_hash(...)`
## Examples
```julia-repl
julia> # Write 1000 normal random numbers to file `randoms.bdio`
julia> fb = BDIO_open("randoms.bdio", "w", "File with random numbers")
julia> BDIO_start_record!(fb, BDIO.BDIO_BIN_F32, 2)
julia> BDIO_write!(fb, randn(1000))
```
"""
function BDIO_write!(fb,data,hash::Bool=true)
if (fb.imode == BDIO_R_MODE)
error("Attemp to write in READ mode")
end
ir = length(fb.records)
fb.records[ir].rlen += sizeof(data)
fb.records[ir].rend += sizeof(data)
update_length(fb, ir)
seekend(fb.io)
write(fb.io, data)
flush(fb.io)
if hash
Nettle.update!(fb.records[ir].hsh, data)
end
update_hdrinfo!(fb)
return true
end
"""
Write the `MD5` checksum of the actual record as a new record.
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file in either `w` or `a` mode.
## Examples
```julia-repl
julia> # Write 1000 normal random numbers to file `randoms.bdio` with checksum
julia> fb = BDIO_open("randoms.bdio", "w", "File with random numbers")
julia> BDIO_start_record!(fb, BDIO.BDIO_BIN_F32, 2)
julia> BDIO_write!(fb, randn(1000))
julia> BDIO_write_hash!(fb)
```
"""
function BDIO_write_hash!(fb)
if (fb.imode == BDIO_R_MODE)
error("Attemp to write in READ mode")
end
ir = length(fb.records)
BDIO_start_record!(fb,BDIO_BIN_GENERIC,7)
i4::Int32 = BDIO_HASH_MAGIC_S
BDIO_write!(fb, i4, false)
BDIO_write!(fb, Nettle.digest!(fb.records[ir].hsh))
end
function update_length(fb::BDIOstream, ir)
mark(fb.io)
if fb.records[ir].islong
seek(fb.io,fb.records[ir].rpos-8)
r64 = read(fb.io,Int64)
mask64::Int64 = 0b111111111111
mask64 = mask64 & r64
il64::Int64 = (fb.records[ir].rend - fb.records[ir].rpos)<<12
iw64::Int64 = il64 | mask64
seek(fb.io,fb.records[ir].rpos-8)
write(fb.io,iw64)
else
seek(fb.io,fb.records[ir].rpos-4)
r32 = read(fb.io,Int32)
mask32::Int32 = 0b111111111111
mask32 = mask32 & r32
il32::Int32 = Int32((fb.records[ir].rend - fb.records[ir].rpos))<<12
iw32::Int32 = il32 | mask32
seek(fb.io,fb.records[ir].rpos-4)
write(fb.io,iw32)
end
flush(fb.io)
reset(fb.io)
return
end
function update_hdrinfo!(fb)
mark(fb.io)
seek(fb.io, fb.rdate)
write(fb.io,floor(Int32, time()))
flush(fb.io)
reset(fb.io)
end
function BDIO_parse!(fb::BDIOstream)
seekstart(fb.io)
ihdr = read(fb.io, Int32)
mask32::Int32 = 0b111111111111
rlen = Int64(read(fb.io, Int32) & mask32)
rpos = position(fb.io)
hsh = Nettle.Hasher("md5")
il4 = read(fb.io, Int32)
idt1 = read(fb.io, Int32)
idt2 = read(fb.io, Int32)
user = readuntil(fb.io, '\0')
user = readuntil(fb.io, '\0')
host = readuntil(fb.io, '\0')
host = readuntil(fb.io, '\0')
info = readuntil(fb.io, '\0')
if (ihdr!=BDIO_MAGIC)
error("Not a BDIO file")
end
new = Record(true,false,rlen,rpos,rpos+rlen,0,0,hsh)
push!(fb.records, new)
seek(fb.io, rpos+rlen)
rlen::Int64 = 0
rpos::Int64 = 0
while !eof(fb.io)
ihdr = read(fb.io, Int32)
ishdr = (mask = 1<<0; ihdr & mask != mask)
if ishdr
if (ihdr != BDIO_MAGIC)
error("Not a BDIO file")
end
i32::Int32 = read(fb.io, Int32)
rlen = Int64(i32 & 0b111111111111)
rpos = position(fb.io)
new = Record(true,false,rlen,rpos,rpos+rlen,0,0,hsh)
else
islong = (mask = 1<<3; ihdr & mask == mask)
ifmt::Int32 = (ihdr & 0b11110000)>>>4
iuinfo::Int8 = Int8((ihdr & 0b111100000000)>>>8)
if islong
rpos = position(fb.io) + 4
ihdr2 = read(fb.io, Int32)
rlen = ihdr>>>12 | ihdr2<<32
else
rpos = position(fb.io)
rlen = ihdr>>>12
end
new = Record(false,islong,rlen,rpos,rpos+rlen,Int32(ifmt),Int8(iuinfo),hsh)
end
push!(fb.records, new)
skip(fb.io, rlen)
end
fb.ipt = 1
end
"""
Move the read position backward/forward `icnt` records
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file.
- `icnt` (optional): number of records to move forward (if `icnt>0`) or backwards (`icnt<0`). The default value is `+1`.
## Examples
```julia-repl
julia> # count number of records in a file.
julia> fb = BDIO_open("randoms.bdio", "r")
julia> count = 0
julia> while BDIO_seek(fb)
julia> count += 1
julia> end
```
"""
function BDIO_seek!(fb::BDIOstream, icnt::Int = 1)
ipt::Int64 = fb.ipt
if (icnt == 0)
ipt = 1
else
ipt += icnt
end
if (ipt < length(fb.records))&&(ipt > 0)
fb.ipt = ipt
return true
else
return false
end
end
"""
Read `data` from BDIO file.
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file in either `w` or `a` mode.
- `data[:]`: A `Vector` of data to read.
- `n` (optional): Integer. If present read `n` elements of `data[:]` from file
## Examples
```julia-repl
julia> # Real 1000 floats from the first record of file `randoms.bdio`
julia> fb = BDIO_open("randoms.bdio", "r")
julia> BDIO_seek!(fb)
julia> data = similar({Array, 1}, 1000)
julia> BDIO_read(fb, data)
```
"""
function BDIO_read(fb, vdata::Vector, n::Int64 = 0)
if (n>0)
nmax = n
else
nmax = length(vdata)
end
if (nmax*sizeof(vdata[1]) > fb.records[fb.ipt].rlen)
error("Data in record not available")
end
seek(fb.io,fb.records[fb.ipt].rpos)
for i = 1:nmax
vdata[i] = read(fb.io, typeof(vdata[1]))
end
end
"""
Returns the len (in bytes) of the current record
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file.
## Examples
```julia-repl
julia> # Write length of all records
julia> fb = BDIO_open("randoms.bdio", "r")
julia> while BDIO_seek(fb)
julia> count += 1
julia> println("Record: ", count, " length: ", BDIO_get_len(fb), " bytes")
julia> end
```
"""
BDIO_get_len(fb::BDIOstream) = fb.records[fb.ipt].rlen
"""
Returns the format of the current record
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file.
## Examples
```julia-repl
julia> # Print all BDIO_BIN_GENERIC records
julia> fb = BDIO_open("randoms.bdio", "r")
julia> while BDIO_seek(fb)
julia> count += 1
julia> if BDIO_get_fmt(fb)
julia> println("Record: ", count)
julia> end
julia> end
```
"""
BDIO_get_fmt(fb::BDIOstream) = fb.records[fb.ipt].rfmt
"""
Returns the user provided info of each record
## Arguments
- `fb`: A BDIOstream type. It must be associated with a file.
## Examples
```julia-repl
julia> # Write length of all records
julia> fb = BDIO_open("randoms.bdio", "r")
julia> while BDIO_seek(fb)
julia> count += 1
julia> println("Record: ", count, " user info: ", BDIO_get_uinfo(fb))
julia> end
```
"""
BDIO_get_uinfo(fb::BDIOstream) = fb.records[fb.ipt].ruinfo
end # module
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment