OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library entry; | |
6 | |
7 import 'dart:io'; | |
8 import 'archive.dart' as archive; | |
9 import 'entry_request.dart'; | |
10 import 'read_request.dart' as read; | |
11 import 'utils.dart'; | |
12 | |
13 /** | |
14 * A single file in an archive. | |
15 * | |
16 * This is accessible via [ArchiveInputStream.onEntry]. | |
17 */ | |
18 class ArchiveEntry { | |
19 /** | |
20 * The various properties of this archive entry, as sent over from the C | |
21 * extension. | |
22 */ | |
23 final List _properties; | |
24 | |
25 /** | |
26 * The id of the archive to which this entry belongs. Used to read the entry | |
27 * data. This will be set to null once there's no longer data available to be | |
28 * read for this entry. | |
29 */ | |
30 int _archiveId; | |
31 | |
32 /** | |
33 * The input stream being used to read data from this entry. This is null | |
34 * until [openInputStream] is called. | |
35 */ | |
36 InputStream _input; | |
37 | |
38 // TODO(nweiz): Get rid of this once issue 4202 is fixed. | |
39 /** | |
40 * A future that only exists once [openInputStream] is called, and completes | |
41 * once the input stream is closed. | |
42 * | |
43 * For internal use only. | |
44 */ | |
45 Future inputComplete; | |
46 | |
47 ArchiveEntry.internal(this._properties, this._archiveId) { | |
48 attachFinalizer(this, (id) => call(FREE, id), _id); | |
49 } | |
50 | |
51 /** Create a new [ArchiveEntry] with default values for all of its fields. */ | |
52 static Future<ArchiveEntry> create() { | |
53 return call(NEW).then((properties) { | |
54 return new archive.ArchiveEntry.internal(properties, null); | |
55 }); | |
56 } | |
57 | |
58 /** The id of the underlying archive entry. */ | |
59 int get _id => _properties[0]; | |
60 | |
61 /** If this entry is a hardlink, this is the destination. Otherwise, null. */ | |
62 String get hardlink => _properties[1]; | |
63 set hardlink(String value) => _set(SET_HARDLINK, 1, value); | |
64 | |
65 /** The path to this entry in the archive. */ | |
66 String get pathname => _properties[2]; | |
67 set pathname(String value) => _set(SET_PATHNAME, 2, value); | |
68 | |
69 /** The path to this entry on disk, */ | |
70 String get sourcepath => _properties[3]; | |
71 | |
72 /** If this entry is a symlink, this is the destination. Otherwise, null. */ | |
73 String get symlink => _properties[4]; | |
74 set symlink(String value) => _set(SET_SYMLINK, 4, value); | |
75 | |
76 /** The group identifier for this entry. */ | |
77 int get gid => _properties[5]; | |
78 set gid(int value) => _set(SET_GID, 5, value); | |
79 | |
80 /** The user identifier for this entry. */ | |
81 int get uid => _properties[6]; | |
82 set uid(int value) => _set(SET_UID, 6, value); | |
83 | |
84 /** The permissions bitmask for this entry. */ | |
85 int get perm_mask => _properties[7]; | |
86 set perm_mask(int value) => _set(SET_PERM, 7, value); | |
87 | |
88 /** | |
89 * The String representation of the permissions for this entry. | |
90 * | |
91 * Note that if you set [perm_mask], this value will not change. | |
92 */ | |
93 String get strmode => _properties[8]; | |
94 | |
95 /** The name of the group this entry belongs to. */ | |
96 String get gname => _properties[9]; | |
97 set gname(String value) => _set(SET_GNAME, 9, value); | |
98 | |
99 /** The name of the user this entry belongs to. */ | |
100 String get uname => _properties[10]; | |
101 set uname(String value) => _set(SET_UNAME, 10, value); | |
102 | |
103 /** | |
104 * The file flag bits that should be set for this entry. | |
105 * | |
106 * Note that if you set [fflags_text], this value will not change, and vice | |
107 * versa. | |
108 */ | |
109 int get fflags_set => _properties[11]; | |
110 set fflags_set(int value) => _set(SET_FFLAGS_SET, 11, value); | |
111 | |
112 /** | |
113 * The file flag bits that should be cleared for this entry. | |
114 * | |
115 * Note that if you set [fflags_text], this value will not change, and vice | |
116 * versa. | |
117 */ | |
118 int get fflags_clear => _properties[12]; | |
119 set fflags_clear(int value) => _set(SET_FFLAGS_CLEAR, 12, value); | |
120 | |
121 /** | |
122 * The textual representation of the file flags for this entry. | |
123 * | |
124 * Note that if you set [fflags_set] or [fflags_clear], this value will not | |
125 * change, and vice versa. | |
126 */ | |
127 String get fflags_text => _properties[13]; | |
128 | |
129 /** The filetype bitmask for this entry. */ | |
130 int get filetype_mask => _properties[14]; | |
131 set filetype_mask(int value) => _set(SET_FILETYPE, 14, value); | |
132 | |
133 /** The filetype and permissions bitmask for this entry. */ | |
134 int get mode_mask => _properties[15]; | |
135 set mode_mask(int value) => _set(SET_MODE, 15, value); | |
136 | |
137 /** The size of this entry in bytes, or null if it's unset. */ | |
138 int get size => _properties[16]; | |
139 set size(int value) => _set(SET_SIZE, 16, value); | |
140 | |
141 /** The ID of the device containing this entry, or null if it's unset. */ | |
142 int get dev => _properties[17]; | |
143 set dev(int value) => _set(SET_DEV, 17, value); | |
144 | |
145 /** The major number of the ID of the device containing this entry. */ | |
146 int get devmajor => _properties[18]; | |
147 set devmajor(int value) => _set(SET_DEVMAJOR, 18, value); | |
148 | |
149 /** The minor number of the ID of the device containing this entry. */ | |
150 int get devminor => _properties[19]; | |
151 set devminor(int value) => _set(SET_DEVMINOR, 19, value); | |
152 | |
153 /** The inode number of this entry, or null if it's unset. */ | |
154 int get ino => _properties[20]; | |
155 set ino(int value) => _set(SET_INO, 20, value); | |
156 | |
157 /** The number of references to this entry. */ | |
158 int get nlink => _properties[21]; | |
159 set nlink(int value) => _set(SET_NLINK, 21, value); | |
160 | |
161 /** The device ID of this entry. */ | |
162 int get rdev => _properties[22]; | |
163 set rdev(int value) => _set(SET_RDEV, 22, value); | |
164 | |
165 /** The major number of the device ID of this entry. */ | |
166 int get rdevmajor => _properties[23]; | |
167 set rdevmajor(int value) => _set(SET_RDEVMAJOR, 23, value); | |
168 | |
169 /** The minor number of the device ID of this entry. */ | |
170 int get rdevminor => _properties[24]; | |
171 set rdevminor(int value) => _set(SET_RDEVMINOR, 24, value); | |
172 | |
173 /** The last time this entry was accessed, or null if it's unset. */ | |
174 Date get atime => _fromMs(_properties[25]); | |
175 set atime(Date value) => _set(SET_ATIME, 25, _toMs(value)); | |
176 | |
177 /** The time this entry was created, or null if it's unset. */ | |
178 Date get birthtime => _fromMs(_properties[26]); | |
179 set birthtime(Date value) => _set(SET_BIRTHTIME, 26, _toMs(value)); | |
180 | |
181 /** | |
182 * The last time an inode property of this entry was changed, or null if it's | |
183 * unset. | |
184 */ | |
185 Date get ctime => _fromMs(_properties[27]); | |
186 set ctime(Date value) => _set(SET_CTIME, 27, _toMs(value)); | |
187 | |
188 /** The last time this entry was modified, or null if it's unset. */ | |
189 Date get mtime => _fromMs(_properties[28]); | |
190 set mtime(Date value) => _set(SET_MTIME, 28, _toMs(value)); | |
191 | |
192 /** Whether [openInputStream] has been called. */ | |
193 bool get isInputOpen => _input != null; | |
194 | |
195 /** Create a deep copy of this [ArchiveEntry]. */ | |
196 Future<ArchiveEntry> clone() { | |
197 return call(CLONE, _id). | |
198 transform((array) => new archive.ArchiveEntry.internal(array, null)); | |
199 } | |
200 | |
201 /** | |
202 * Consumes the entire contents of this entry at once and returns it wrapped | |
203 * in a [CompleteArchiveEntry]. All metadata fields in the returned entry are | |
204 * copies of the fields in this entry. | |
205 * | |
206 * This may not be called if [openInputStream] is called, and vice versa. | |
207 */ | |
208 Future<CompleteArchiveEntry> readAll() { | |
209 var stream = openInputStream(); | |
210 var buffer = <int>[]; | |
211 var completer = new Completer<List<int>>(); | |
212 | |
213 stream.onData = () => buffer.addAll(stream.read()); | |
214 stream.onError = completer.completeError; | |
215 stream.onClosed = () => completer.complete(buffer); | |
216 | |
217 return Future.wait([call(CLONE, _id), completer.future]) | |
218 .then((list) => new CompleteArchiveEntry._(list[0], list[1])); | |
219 } | |
220 | |
221 /** | |
222 * Set a property value with index [value] on the local representation of the | |
223 * archive entry and on the native representation. | |
224 */ | |
225 void _set(int requestType, int index, value) { | |
226 _properties[index] = value; | |
227 // Since the native code processes messages in order, the SET_* messages | |
228 // will be received and processed before any further messages. | |
229 call(requestType, _id, [value]).then((_) {}); | |
230 } | |
231 | |
232 /** | |
233 * Converts [ms], the (possibly null) number of milliseconds since the epoch | |
234 * into a Date object (which may also be null). | |
235 */ | |
236 Date _fromMs(int ms) { | |
237 if (ms == null) return null; | |
238 return new Date.fromMillisecondsSinceEpoch(ms); | |
239 } | |
240 | |
241 /** | |
242 * Converts [date], which may be null, into the number of milliseconds since | |
243 * the epoch (which may also be null). | |
244 */ | |
245 int _toMs(Date date) { | |
246 if (date == null) return null; | |
247 return date.millisecondsSinceEpoch; | |
248 } | |
249 | |
250 /** | |
251 * Creates a new input stream for reading the contents of this entry. | |
252 * | |
253 * The contents of an entry must be consumed before the next entry is read | |
254 * from the parent [ArchiveInputStream]. This means that [openInputStream] | |
255 * must be called from the [ArchiveInputStream.onEntry] callback, or before | |
256 * the future returned by that callback completes. Once the next entry has | |
257 * been read, calling [openInputStream] will throw an [ArchiveException]. | |
258 * | |
259 * Only one input stream may be opened per entry. | |
260 */ | |
261 InputStream openInputStream() { | |
262 if (_archiveId == null) { | |
263 throw new UnsupportedError("Cannot open input stream for " | |
264 "archive entry $pathname."); | |
265 } else if (_input != null) { | |
266 throw new UnsupportedError("An input stream has already been" | |
267 "opened for archive entry $pathname."); | |
268 } | |
269 | |
270 var inputCompleter = new Completer(); | |
271 inputComplete = inputCompleter.future; | |
272 | |
273 _input = new ListInputStream(); | |
274 // TODO(nweiz): Report errors once issue 3657 is fixed | |
275 var future = _consumeInput().then((_) { | |
276 if (!_input.closed) _input.markEndOfStream(); | |
277 // Asynchronously complete to give the InputStream callbacks a chance to | |
278 // fire. | |
279 return async(); | |
280 }).then((_) => inputCompleter.complete(null)); | |
281 | |
282 future.handleException((e) { | |
283 print(e); | |
284 print(future.stackTrace); | |
285 }); | |
286 return _input; | |
287 } | |
288 | |
289 /** | |
290 * Close this entry so that its input stream no longer produces data. | |
291 * | |
292 * In addition to closing the associated input stream, this will prevent new | |
293 * input streams from being opened. | |
294 */ | |
295 void close() { | |
296 _archiveId = null; | |
297 if (_input != null) _input.close(); | |
298 } | |
299 | |
300 /** | |
301 * Read all data from the archive and write it to [_input]. Returns a future | |
302 * that completes once this is done. | |
303 * | |
304 * This assumes that both [_input] and [_archiveId] are non-null and that | |
305 * [_input] is open, although if that changes before this completes it will | |
306 * handle it gracefully. | |
307 */ | |
308 Future _consumeInput() { | |
309 var data; | |
310 return call(read.DATA_BLOCK, _archiveId).then((_data) { | |
311 data = _data; | |
312 // TODO(nweiz): This async() call is only necessary because of issue 4222. | |
313 return async(); | |
314 }).then((_) { | |
315 if (_input.closed || _archiveId == null || data == null) { | |
316 return new Future.immediate(null); | |
317 } | |
318 _input.write(data); | |
319 return _consumeInput(); | |
320 }); | |
321 } | |
322 } | |
323 | |
324 /** | |
325 * An [ArchiveEntry] that contains the complete decompressed contents of the | |
326 * file. | |
327 */ | |
328 class CompleteArchiveEntry extends ArchiveEntry { | |
329 /** The contents of the entry as bytes. */ | |
330 final List<int> contentBytes; | |
331 | |
332 /** The contents of the entry as a string. */ | |
333 String get contents => new String.fromCharCodes(contentBytes); | |
334 | |
335 CompleteArchiveEntry._(List properties, this.contentBytes) | |
336 : super.internal(properties, null); | |
337 } | |
OLD | NEW |