Skip to content

Commit 9b08cb0

Browse files
authored
Propagate content length from filesystem through geny.Writable and os.Source (#320)
This means downstream libraries like Requests-Scala or Cask can properly set the content length when handling `os.read.stream`s and similar values in their HTTP requests or responses Noticed the lack of this when uploading to github failed in com-lihaoyi/mill#3686 due to the lack of content length header in the upload Covered by unit tests
1 parent 07166c5 commit 9b08cb0

File tree

4 files changed

+33
-2
lines changed

4 files changed

+33
-2
lines changed

os/src/Path.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,10 @@ trait ReadablePath {
534534
*/
535535
class Path private[os] (val wrapped: java.nio.file.Path)
536536
extends FilePath with ReadablePath with BasePathImpl {
537-
def toSource: SeekableSource =
538-
new SeekableSource.ChannelSource(java.nio.file.Files.newByteChannel(wrapped))
537+
def toSource: SeekableSource = new SeekableSource.ChannelLengthSource(
538+
java.nio.file.Files.newByteChannel(wrapped),
539+
java.nio.file.Files.size(wrapped)
540+
)
539541

540542
require(wrapped.isAbsolute || Path.driveRelative(wrapped), s"$wrapped is not an absolute path")
541543
def root = Option(wrapped.getRoot).map(_.toString).getOrElse("")

os/src/ReadWriteOps.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ object read extends Function1[ReadablePath, String] {
247247

248248
object stream extends Function1[ReadablePath, geny.Readable] {
249249
def apply(p: ReadablePath): geny.Readable = new geny.Readable {
250+
override def contentLength: Option[Long] = p.toSource.contentLength
250251
def readBytesThrough[T](f: java.io.InputStream => T): T = {
251252
val is = p.getInputStream
252253
try f(is)

os/src/Source.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ object Source extends WritableLowPri {
6666

6767
implicit class WritableSource[T](s: T)(implicit f: T => geny.Writable) extends Source {
6868
val writable = f(s)
69+
70+
override def contentLength: Option[Long] = writable.contentLength
6971
def getHandle() = Left(writable)
7072
}
7173
}
@@ -115,4 +117,9 @@ object SeekableSource {
115117
implicit class ChannelSource(cn: SeekableByteChannel) extends SeekableSource {
116118
def getHandle() = Right(cn)
117119
}
120+
class ChannelLengthSource(cn: SeekableByteChannel, length: Long) extends SeekableSource {
121+
def getHandle() = Right(cn)
122+
123+
override def contentLength: Option[Long] = Some(length)
124+
}
118125
}

os/test/src/SourceTests.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package test.os
2+
import utest.{assert => _, _}
3+
4+
object SourceTests extends TestSuite {
5+
6+
val tests = Tests {
7+
test("contentMetadata") - TestUtil.prep { wd =>
8+
// content type for all files is just treated as application/octet-stream,
9+
// we do not do any clever mime-type inference or guessing
10+
(wd / "folder1/one.txt").toSource.httpContentType ==> Some("application/octet-stream")
11+
// length is taken from the filesystem at the moment at which `.toSource` is called
12+
(wd / "folder1/one.txt").toSource.contentLength ==> Some(22)
13+
(wd / "File.txt").toSource.contentLength ==> Some(8)
14+
15+
// Make sure the `Writable` returned by `os.read.stream` propagates the content length
16+
os.read.stream(wd / "folder1/one.txt").contentLength ==> Some(22)
17+
// Even when converted to an `os.Source`
18+
(os.read.stream(wd / "folder1/one.txt"): os.Source).contentLength ==> Some(22)
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)