about summary refs log tree commit diff stats
path: root/src/types/blob.nim
blob: cc69fd7c18c304e1addd708419037c1d02f954c7 (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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import std/options
import std/posix
import std/strutils

import io/bufreader
import io/bufwriter
import monoucha/fromjs
import monoucha/javascript
import monoucha/jstypes
import utils/mimeguess

type
  DeallocFun = proc(opaque, p: pointer) {.nimcall, raises: [].}

  Blob* = ref object of RootObj
    size* {.jsget.}: uint64
    ctype* {.jsget: "type".}: string
    buffer*: pointer
    opaque*: pointer
    deallocFun*: DeallocFun
    fd*: Option[FileHandle]

  WebFile* = ref object of Blob
    webkitRelativePath {.jsget.}: string
    name* {.jsget.}: string

jsDestructor(Blob)
jsDestructor(WebFile)

# Forward declarations
proc deallocBlob*(opaque, p: pointer) {.raises: [].}

#TODO it would be nice if we had a separate fd type that does sendAux;
# this solution fails when blob isn't swritten by some module that does
# not import it (just as a transitive dependency).
proc swrite*(writer: var BufferedWriter; blob: Blob) =
  if blob.fd.isSome:
    writer.sendAux.add(blob.fd.get)
  writer.swrite(blob of WebFile)
  if blob of WebFile:
    writer.swrite(WebFile(blob).name)
  writer.swrite(blob.fd.isSome)
  writer.swrite(blob.ctype)
  writer.swrite(blob.size)
  if blob.size > 0:
    writer.writeData(blob.buffer, int(blob.size))

proc sread*(reader: var BufferedReader; blob: var Blob) =
  var isWebFile: bool
  reader.sread(isWebFile)
  blob = if isWebFile: WebFile() else: Blob()
  if isWebFile:
    reader.sread(WebFile(blob).name)
  var hasFd: bool
  reader.sread(hasFd)
  if hasFd:
    blob.fd = some(reader.recvAux.pop())
  reader.sread(blob.ctype)
  reader.sread(blob.size)
  if blob.size > 0:
    let buffer = alloc(blob.size)
    reader.readData(blob.buffer, int(blob.size))
    blob.buffer = buffer
    blob.deallocFun = deallocBlob

proc newBlob*(buffer: pointer; size: int; ctype: string;
    deallocFun: DeallocFun; opaque: pointer = nil): Blob =
  return Blob(
    buffer: buffer,
    size: uint64(size),
    ctype: ctype,
    deallocFun: deallocFun,
    opaque: opaque
  )

proc deallocBlob*(opaque, p: pointer) =
  if p != nil:
    dealloc(p)

proc finalize(blob: Blob) {.jsfin.} =
  if blob.fd.isSome:
    discard close(blob.fd.get)
  if blob.deallocFun != nil:
    blob.deallocFun(blob.opaque, blob.buffer)
    blob.buffer = nil

proc finalize(file: WebFile) {.jsfin.} =
  Blob(file).finalize()

proc newWebFile*(name: string; fd: FileHandle): WebFile =
  return WebFile(
    name: name,
    fd: some(fd),
    ctype: DefaultGuess.guessContentType(name)
  )

type
  BlobPropertyBag = object of JSDict
    `type` {.jsdefault.}: string
    #TODO endings

  FilePropertyBag = object of BlobPropertyBag
    #TODO lastModified: int64

proc newWebFile(ctx: JSContext; fileBits: seq[string]; fileName: string;
    options = FilePropertyBag()): WebFile {.jsctor.} =
  let file = WebFile(
    name: fileName
  )
  var len = 0
  for blobPart in fileBits:
    len += blobPart.len
  let buffer = alloc(len)
  file.buffer = buffer
  file.deallocFun = deallocBlob
  var buf = cast[ptr UncheckedArray[uint8]](file.buffer)
  var i = 0
  for blobPart in fileBits:
    if blobPart.len > 0:
      copyMem(addr buf[i], unsafeAddr blobPart[0], blobPart.len)
      i += blobPart.len
  file.size = uint64(len)
  block ctype:
    for c in options.`type`:
      if c notin char(0x20)..char(0x7E):
        break ctype
      file.ctype &= c.toLowerAscii()
  return file

#TODO File, Blob constructors

proc getSize*(this: Blob): uint64 =
  if this.fd.isSome:
    var statbuf: Stat
    if fstat(this.fd.get, statbuf) < 0:
      return 0
    return uint64(statbuf.st_size)
  return this.size

proc size*(this: WebFile): uint64 {.jsfget.} =
  return this.getSize()

#TODO lastModified

proc addBlobModule*(ctx: JSContext) =
  let blobCID = ctx.registerType(Blob)
  ctx.registerType(WebFile, parent = blobCID, name = "File")