File System
This wraps the fs2 Files API to create
a BinaryStore
.
Usage
You need a FsStoreConfig
object to create an instance of the
FsBinaryStore
. The companion object has some convenience
constructors.
The FsBinaryStore.default
creates a store that saves files in a
subdirectory hierarchy.
import binny._
import binny.fs._
import fs2.io.file.{Files, Path}
import cats.effect.IO
import cats.effect.unsafe.implicits._
import fs2.Stream
val logger = binny.util.Logger.silent[IO]
// logger: util.Logger[IO] = binny.util.Logger$$anon$2@52ec5ba6
val someData = ExampleData.file2M
// someData: Binary[IO] = Stream(..)
// lets store two pieces and look at the outcome
val run =
for {
baseDir <- Stream.resource(DocUtil.tempDir)
store = FsBinaryStore.default(logger, baseDir)
id1 <- someData.through(store.insert)
id2 <- someData.through(store.insert)
layout <- Stream.eval(DocUtil.directoryContentAsString(baseDir))
} yield (id1, id2, layout)
// run: Stream[[x]IO[x], (BinaryId, BinaryId, String)] = Stream(..)
run.compile.lastOrError.unsafeRunSync()
// res0: (BinaryId, BinaryId, String) = (
// BinaryId(H8QZY941vLgEaeCpMBEf9LxiiexU7FF8YDZRTyLgJSo3),
// BinaryId(3HNfwAqd6RKoHzH9524fP4tsJh9uYVy3tteUfreA6qqN),
// """
// π binny-docs-3363400676315580922
// π 3H
// π 3HNfwAqd6RKoHzH9524fP4tsJh9uYVy3tteUfreA6qqN
// Β· file
// π H8
// π H8QZY941vLgEaeCpMBEf9LxiiexU7FF8YDZRTyLgJSo3
// Β· file"""
// )
This store uses the id to create a directory using the first two
characters and another below using the complete id. Then the data is
stored in file
and its attributes in attr
.
This can be changed by providing a different FsStoreConfig
. The
mapping of an id
to a file in the filesystem is given by a
PathMapping
. There are some provided, the above results are from
PathMapping.subdir2
.
As another example, the next FsBinaryStore
puts the files directly
into the baseDir
- using the id as its name.
val run2 =
Stream.resource(DocUtil.tempDir).flatMap { baseDir =>
val store = FsBinaryStore[IO](
FsStoreConfig.default(baseDir).withMapping(PathMapping.simple),
logger
)
someData.through(store.insertWith(BinaryId("hello-world.txt"))) ++
someData.through(store.insertWith(BinaryId("hello_world.txt"))) ++
Stream.eval(DocUtil.directoryContentAsString(baseDir))
}
// run2: Stream[[x]IO[x], String] = Stream(..)
run2.compile.lastOrError.unsafeRunSync()
// res1: String = """
// π binny-docs-10763800237879449321
// Β· hello-world.txt
// Β· hello_world.txt"""
A PathMapping
is a function (Path, BinaryId) => Path)
where the
given path is the base directory. So you can easily create a custom
layout.
FsChunkedBinaryStore
The FsChunkedBinaryStore
implements ChunkedBinaryStore
to allow
storing chunks independently. This is useful if chunks are received in
random order and the whole file is not available as complete stream.
This is implemented by storing each chunk as a file and concatenating
these when loading. Therefore, a DirectoryMapping
is required that
maps a BinaryId
to a directory (and not a file as PathMapping
does). For binaries that are provided as a complete stream, it stores
just one chunk file - same as FsBinaryStore
does.
However, in order to use this the complete size of the file must be known up front. This is needed to know when the last chunk is received.
FsBinaryStoreWithCleanup
The FsBinaryStoreWithCleanup
is a wrapper around a FsBinaryStore
that upon deletion of a file also removes all empty directories until
its base path. Depending on which PathMapping
is used, deleting a
file could leave empty directories behind. The reason for this default
behavior is so inserting and deleting are independent as they wonβt
write into same subdirectories. The FsBinaryStoreWithCleanup
makes
sure that inserting and deleting happen interleaved.