Initial commit
[yaffs-website] / node_modules / tar / lib / entry.js
1 // A passthrough read/write stream that sets its properties
2 // based on a header, extendedHeader, and globalHeader
3 //
4 // Can be either a file system object of some sort, or
5 // a pax/ustar metadata entry.
6
7 module.exports = Entry
8
9 var TarHeader = require("./header.js")
10   , tar = require("../tar")
11   , assert = require("assert").ok
12   , Stream = require("stream").Stream
13   , inherits = require("inherits")
14   , fstream = require("fstream").Abstract
15
16 function Entry (header, extended, global) {
17   Stream.call(this)
18   this.readable = true
19   this.writable = true
20
21   this._needDrain = false
22   this._paused = false
23   this._reading = false
24   this._ending = false
25   this._ended = false
26   this._remaining = 0
27   this._abort = false
28   this._queue = []
29   this._index = 0
30   this._queueLen = 0
31
32   this._read = this._read.bind(this)
33
34   this.props = {}
35   this._header = header
36   this._extended = extended || {}
37
38   // globals can change throughout the course of
39   // a file parse operation.  Freeze it at its current state.
40   this._global = {}
41   var me = this
42   Object.keys(global || {}).forEach(function (g) {
43     me._global[g] = global[g]
44   })
45
46   this._setProps()
47 }
48
49 inherits(Entry, Stream)
50
51 Entry.prototype.write = function (c) {
52   if (this._ending) this.error("write() after end()", null, true)
53   if (this._remaining === 0) {
54     this.error("invalid bytes past eof")
55   }
56
57   // often we'll get a bunch of \0 at the end of the last write,
58   // since chunks will always be 512 bytes when reading a tarball.
59   if (c.length > this._remaining) {
60     c = c.slice(0, this._remaining)
61   }
62   this._remaining -= c.length
63
64   // put it on the stack.
65   var ql = this._queueLen
66   this._queue.push(c)
67   this._queueLen ++
68
69   this._read()
70
71   // either paused, or buffered
72   if (this._paused || ql > 0) {
73     this._needDrain = true
74     return false
75   }
76
77   return true
78 }
79
80 Entry.prototype.end = function (c) {
81   if (c) this.write(c)
82   this._ending = true
83   this._read()
84 }
85
86 Entry.prototype.pause = function () {
87   this._paused = true
88   this.emit("pause")
89 }
90
91 Entry.prototype.resume = function () {
92   // console.error("    Tar Entry resume", this.path)
93   this.emit("resume")
94   this._paused = false
95   this._read()
96   return this._queueLen - this._index > 1
97 }
98
99   // This is bound to the instance
100 Entry.prototype._read = function () {
101   // console.error("    Tar Entry _read", this.path)
102
103   if (this._paused || this._reading || this._ended) return
104
105   // set this flag so that event handlers don't inadvertently
106   // get multiple _read() calls running.
107   this._reading = true
108
109   // have any data to emit?
110   while (this._index < this._queueLen && !this._paused) {
111     var chunk = this._queue[this._index ++]
112     this.emit("data", chunk)
113   }
114
115   // check if we're drained
116   if (this._index >= this._queueLen) {
117     this._queue.length = this._queueLen = this._index = 0
118     if (this._needDrain) {
119       this._needDrain = false
120       this.emit("drain")
121     }
122     if (this._ending) {
123       this._ended = true
124       this.emit("end")
125     }
126   }
127
128   // if the queue gets too big, then pluck off whatever we can.
129   // this should be fairly rare.
130   var mql = this._maxQueueLen
131   if (this._queueLen > mql && this._index > 0) {
132     mql = Math.min(this._index, mql)
133     this._index -= mql
134     this._queueLen -= mql
135     this._queue = this._queue.slice(mql)
136   }
137
138   this._reading = false
139 }
140
141 Entry.prototype._setProps = function () {
142   // props = extended->global->header->{}
143   var header = this._header
144     , extended = this._extended
145     , global = this._global
146     , props = this.props
147
148   // first get the values from the normal header.
149   var fields = tar.fields
150   for (var f = 0; fields[f] !== null; f ++) {
151     var field = fields[f]
152       , val = header[field]
153     if (typeof val !== "undefined") props[field] = val
154   }
155
156   // next, the global header for this file.
157   // numeric values, etc, will have already been parsed.
158   ;[global, extended].forEach(function (p) {
159     Object.keys(p).forEach(function (f) {
160       if (typeof p[f] !== "undefined") props[f] = p[f]
161     })
162   })
163
164   // no nulls allowed in path or linkpath
165   ;["path", "linkpath"].forEach(function (p) {
166     if (props.hasOwnProperty(p)) {
167       props[p] = props[p].split("\0")[0]
168     }
169   })
170
171
172   // set date fields to be a proper date
173   ;["mtime", "ctime", "atime"].forEach(function (p) {
174     if (props.hasOwnProperty(p)) {
175       props[p] = new Date(props[p] * 1000)
176     }
177   })
178
179   // set the type so that we know what kind of file to create
180   var type
181   switch (tar.types[props.type]) {
182     case "OldFile":
183     case "ContiguousFile":
184       type = "File"
185       break
186
187     case "GNUDumpDir":
188       type = "Directory"
189       break
190
191     case undefined:
192       type = "Unknown"
193       break
194
195     case "Link":
196     case "SymbolicLink":
197     case "CharacterDevice":
198     case "BlockDevice":
199     case "Directory":
200     case "FIFO":
201     default:
202       type = tar.types[props.type]
203   }
204
205   this.type = type
206   this.path = props.path
207   this.size = props.size
208
209   // size is special, since it signals when the file needs to end.
210   this._remaining = props.size
211 }
212
213 // the parser may not call write if _abort is true. 
214 // useful for skipping data from some files quickly.
215 Entry.prototype.abort = function(){
216   this._abort = true
217 }
218
219 Entry.prototype.warn = fstream.warn
220 Entry.prototype.error = fstream.error