aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/xyz/driver/core/storage/FileSystemBlobStorage.scala
blob: e12c73dd538b36066735ec32fef2e9b8c8f04ef5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package xyz.driver.core.storage

import java.net.URL
import java.nio.file.{Files, Path, StandardCopyOption}

import akka.stream.scaladsl.{FileIO, Sink, Source}
import akka.util.ByteString
import akka.{Done, NotUsed}

import scala.collection.JavaConverters._
import scala.concurrent.{ExecutionContext, Future}

/** A blob store that is backed by a local filesystem. All objects are stored relative to the given
  * root path. Slashes ('/') in blob names are treated as usual path separators and are converted
  * to directories. */
class FileSystemBlobStorage(root: Path)(implicit ec: ExecutionContext) extends BlobStorage {

  private def ensureParents(file: Path): Path = {
    Files.createDirectories(file.getParent())
    file
  }

  private def file(name: String) = root.resolve(name)

  override def uploadContent(name: String, content: Array[Byte]): Future[String] = Future {
    Files.write(ensureParents(file(name)), content)
    name
  }
  override def uploadFile(name: String, content: Path): Future[String] = Future {
    Files.copy(content, ensureParents(file(name)), StandardCopyOption.REPLACE_EXISTING)
    name
  }

  override def exists(name: String): Future[Boolean] = Future {
    val path = file(name)
    Files.exists(path) && Files.isReadable(path)
  }

  override def list(prefix: String): Future[Set[String]] = Future {
    val dir = file(prefix)
    Files
      .list(dir)
      .iterator()
      .asScala
      .map(p => root.relativize(p))
      .map(_.toString)
      .toSet
  }

  override def content(name: String): Future[Option[Array[Byte]]] = exists(name) map {
    case true =>
      Some(Files.readAllBytes(file(name)))
    case false => None
  }

  override def download(name: String): Future[Option[Source[ByteString, NotUsed]]] = Future {
    if (Files.exists(file(name))) {
      Some(FileIO.fromPath(file(name)).mapMaterializedValue(_ => NotUsed))
    } else {
      None
    }
  }

  override def upload(name: String): Future[Sink[ByteString, Future[Done]]] = Future {
    val f = ensureParents(file(name))
    FileIO.toPath(f).mapMaterializedValue(_.map(_ => Done))
  }

  override def delete(name: String): Future[String] = exists(name).map { e =>
    if (e) {
      Files.delete(file(name))
    }
    name
  }

  override def url(name: String): Future[Option[URL]] = exists(name) map {
    case true =>
      Some(root.resolve(name).toUri.toURL)
    case false =>
      None
  }
}