1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | from __future__ import unicode_literals, print_function
|
8 |
|
9 | import struct
|
10 | import uuid
|
11 | import datetime
|
12 | import os
|
13 | import sys
|
14 | import pprint
|
15 |
|
16 | try:
|
17 | from urlparse import urljoin
|
18 | except ImportError:
|
19 | from urllib.parse import urljoin
|
20 |
|
21 | if sys.platform == 'darwin':
|
22 | from . import osx
|
23 |
|
24 | def iteritems(x):
|
25 | return x.iteritems()
|
26 |
|
27 | try:
|
28 | unicode
|
29 | except NameError:
|
30 | unicode = str
|
31 | long = int
|
32 | xrange = range
|
33 | def iteritems(x):
|
34 | return x.items()
|
35 |
|
36 | from .utils import *
|
37 |
|
38 | BMK_DATA_TYPE_MASK = 0xffffff00
|
39 | BMK_DATA_SUBTYPE_MASK = 0x000000ff
|
40 |
|
41 | BMK_STRING = 0x0100
|
42 | BMK_DATA = 0x0200
|
43 | BMK_NUMBER = 0x0300
|
44 | BMK_DATE = 0x0400
|
45 | BMK_BOOLEAN = 0x0500
|
46 | BMK_ARRAY = 0x0600
|
47 | BMK_DICT = 0x0700
|
48 | BMK_UUID = 0x0800
|
49 | BMK_URL = 0x0900
|
50 | BMK_NULL = 0x0a00
|
51 |
|
52 | BMK_ST_ZERO = 0x0000
|
53 | BMK_ST_ONE = 0x0001
|
54 |
|
55 | BMK_BOOLEAN_ST_FALSE = 0x0000
|
56 | BMK_BOOLEAN_ST_TRUE = 0x0001
|
57 |
|
58 |
|
59 | kCFNumberSInt8Type = 1
|
60 | kCFNumberSInt16Type = 2
|
61 | kCFNumberSInt32Type = 3
|
62 | kCFNumberSInt64Type = 4
|
63 | kCFNumberFloat32Type = 5
|
64 | kCFNumberFloat64Type = 6
|
65 | kCFNumberCharType = 7
|
66 | kCFNumberShortType = 8
|
67 | kCFNumberIntType = 9
|
68 | kCFNumberLongType = 10
|
69 | kCFNumberLongLongType = 11
|
70 | kCFNumberFloatType = 12
|
71 | kCFNumberDoubleType = 13
|
72 | kCFNumberCFIndexType = 14
|
73 | kCFNumberNSIntegerType = 15
|
74 | kCFNumberCGFloatType = 16
|
75 |
|
76 |
|
77 | kCFURLResourceIsRegularFile = 0x00000001
|
78 | kCFURLResourceIsDirectory = 0x00000002
|
79 | kCFURLResourceIsSymbolicLink = 0x00000004
|
80 | kCFURLResourceIsVolume = 0x00000008
|
81 | kCFURLResourceIsPackage = 0x00000010
|
82 | kCFURLResourceIsSystemImmutable = 0x00000020
|
83 | kCFURLResourceIsUserImmutable = 0x00000040
|
84 | kCFURLResourceIsHidden = 0x00000080
|
85 | kCFURLResourceHasHiddenExtension = 0x00000100
|
86 | kCFURLResourceIsApplication = 0x00000200
|
87 | kCFURLResourceIsCompressed = 0x00000400
|
88 | kCFURLResourceIsSystemCompressed = 0x00000400
|
89 | kCFURLCanSetHiddenExtension = 0x00000800
|
90 | kCFURLResourceIsReadable = 0x00001000
|
91 | kCFURLResourceIsWriteable = 0x00002000
|
92 | kCFURLResourceIsExecutable = 0x00004000
|
93 | kCFURLIsAliasFile = 0x00008000
|
94 | kCFURLIsMountTrigger = 0x00010000
|
95 |
|
96 |
|
97 | kCFURLVolumeIsLocal = 0x1
|
98 | kCFURLVolumeIsAutomount = 0x2
|
99 | kCFURLVolumeDontBrowse = 0x4
|
100 | kCFURLVolumeIsReadOnly = 0x8
|
101 | kCFURLVolumeIsQuarantined = 0x10
|
102 | kCFURLVolumeIsEjectable = 0x20
|
103 | kCFURLVolumeIsRemovable = 0x40
|
104 | kCFURLVolumeIsInternal = 0x80
|
105 | kCFURLVolumeIsExternal = 0x100
|
106 | kCFURLVolumeIsDiskImage = 0x200
|
107 | kCFURLVolumeIsFileVault = 0x400
|
108 | kCFURLVolumeIsLocaliDiskMirror = 0x800
|
109 | kCFURLVolumeIsiPod = 0x1000
|
110 | kCFURLVolumeIsiDisk = 0x2000
|
111 | kCFURLVolumeIsCD = 0x4000
|
112 | kCFURLVolumeIsDVD = 0x8000
|
113 | kCFURLVolumeIsDeviceFileSystem = 0x10000
|
114 | kCFURLVolumeSupportsPersistentIDs = 0x100000000
|
115 | kCFURLVolumeSupportsSearchFS = 0x200000000
|
116 | kCFURLVolumeSupportsExchange = 0x400000000
|
117 |
|
118 | kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
|
119 | kCFURLVolumeSupportsDenyModes = 0x2000000000
|
120 | kCFURLVolumeSupportsCopyFile = 0x4000000000
|
121 | kCFURLVolumeSupportsReadDirAttr = 0x8000000000
|
122 | kCFURLVolumeSupportsJournaling = 0x10000000000
|
123 | kCFURLVolumeSupportsRename = 0x20000000000
|
124 | kCFURLVolumeSupportsFastStatFS = 0x40000000000
|
125 | kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
|
126 | kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
|
127 | kCFURLVolumeSupportsFLock = 0x200000000000
|
128 | kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
|
129 | kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
|
130 | kCFURLVolumeSupports2TBFileSize = 0x1000000000000
|
131 | kCFURLVolumeSupportsHardLinks = 0x2000000000000
|
132 | kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
|
133 | kCFURLVolumeSupportsPathFromID = 0x8000000000000
|
134 |
|
135 | kCFURLVolumeIsJournaling = 0x20000000000000
|
136 | kCFURLVolumeSupportsSparseFiles = 0x40000000000000
|
137 | kCFURLVolumeSupportsZeroRuns = 0x80000000000000
|
138 | kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
|
139 | kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
|
140 | kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
|
141 | kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
|
142 | kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
|
143 | kCFURLVolumePropertyFlagsAll = 0xffffffffffffffff
|
144 |
|
145 | BMK_URL_ST_ABSOLUTE = 0x0001
|
146 | BMK_URL_ST_RELATIVE = 0x0002
|
147 |
|
148 |
|
149 |
|
150 | kBookmarkPath = 0x1004
|
151 | kBookmarkCNIDPath = 0x1005
|
152 | kBookmarkFileProperties = 0x1010
|
153 |
|
154 |
|
155 | kBookmarkFileName = 0x1020
|
156 | kBookmarkFileID = 0x1030
|
157 | kBookmarkFileCreationDate = 0x1040
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | kBookmarkTOCPath = 0x2000
|
164 | kBookmarkVolumePath = 0x2002
|
165 | kBookmarkVolumeURL = 0x2005
|
166 | kBookmarkVolumeName = 0x2010
|
167 | kBookmarkVolumeUUID = 0x2011
|
168 | kBookmarkVolumeSize = 0x2012
|
169 | kBookmarkVolumeCreationDate = 0x2013
|
170 | kBookmarkVolumeProperties = 0x2020
|
171 |
|
172 |
|
173 | kBookmarkVolumeIsRoot = 0x2030
|
174 | kBookmarkVolumeBookmark = 0x2040
|
175 | kBookmarkVolumeMountPoint = 0x2050
|
176 |
|
177 | kBookmarkContainingFolder = 0xc001
|
178 | kBookmarkUserName = 0xc011
|
179 | kBookmarkUID = 0xc012
|
180 | kBookmarkWasFileReference = 0xd001
|
181 | kBookmarkCreationOptions = 0xd010
|
182 | kBookmarkURLLengths = 0xe003
|
183 |
|
184 |
|
185 | kBookmarkSecurityExtension = 0xf080
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | class Data (object):
|
202 | def __init__(self, bytedata=None):
|
203 |
|
204 | self.bytes = bytes(bytedata)
|
205 |
|
206 | def __repr__(self):
|
207 | return 'Data(%r)' % self.bytes
|
208 |
|
209 | class URL (object):
|
210 | def __init__(self, base, rel=None):
|
211 | if rel is not None:
|
212 |
|
213 | self.base = base
|
214 |
|
215 | self.relative = rel
|
216 | else:
|
217 | self.base = None
|
218 | self.relative = base
|
219 |
|
220 | @property
|
221 | def absolute(self):
|
222 | """Return an absolute URL."""
|
223 | if self.base is None:
|
224 | return self.relative
|
225 | else:
|
226 | base_abs = self.base.absolute
|
227 | return urljoin(self.base.absolute, self.relative)
|
228 |
|
229 | def __repr__(self):
|
230 | return 'URL(%r)' % self.absolute
|
231 |
|
232 | class Bookmark (object):
|
233 | def __init__(self, tocs=None):
|
234 | if tocs is None:
|
235 |
|
236 | self.tocs = []
|
237 | else:
|
238 | self.tocs = tocs
|
239 |
|
240 | @classmethod
|
241 | def _get_item(cls, data, hdrsize, offset):
|
242 | offset += hdrsize
|
243 | if offset > len(data) - 8:
|
244 | raise ValueError('Offset out of range')
|
245 |
|
246 | length,typecode = struct.unpack(b'<II', data[offset:offset+8])
|
247 |
|
248 | if len(data) - offset < 8 + length:
|
249 | raise ValueError('Data item truncated')
|
250 |
|
251 | databytes = data[offset+8:offset+8+length]
|
252 |
|
253 | dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
|
254 | dtype = typecode & BMK_DATA_TYPE_MASK
|
255 |
|
256 | if dtype == BMK_STRING:
|
257 | return databytes.decode('utf-8')
|
258 | elif dtype == BMK_DATA:
|
259 | return Data(databytes)
|
260 | elif dtype == BMK_NUMBER:
|
261 | if dsubtype == kCFNumberSInt8Type:
|
262 | return ord(databytes[0])
|
263 | elif dsubtype == kCFNumberSInt16Type:
|
264 | return struct.unpack(b'<h', databytes)[0]
|
265 | elif dsubtype == kCFNumberSInt32Type:
|
266 | return struct.unpack(b'<i', databytes)[0]
|
267 | elif dsubtype == kCFNumberSInt64Type:
|
268 | return struct.unpack(b'<q', databytes)[0]
|
269 | elif dsubtype == kCFNumberFloat32Type:
|
270 | return struct.unpack(b'<f', databytes)[0]
|
271 | elif dsubtype == kCFNumberFloat64Type:
|
272 | return struct.unpack(b'<d', databytes)[0]
|
273 | elif dtype == BMK_DATE:
|
274 |
|
275 |
|
276 | secs = datetime.timedelta(seconds=struct.unpack(b'>d', databytes)[0])
|
277 | return osx_epoch + secs
|
278 | elif dtype == BMK_BOOLEAN:
|
279 | if dsubtype == BMK_BOOLEAN_ST_TRUE:
|
280 | return True
|
281 | elif dsubtype == BMK_BOOLEAN_ST_FALSE:
|
282 | return False
|
283 | elif dtype == BMK_UUID:
|
284 | return uuid.UUID(bytes=databytes)
|
285 | elif dtype == BMK_URL:
|
286 | if dsubtype == BMK_URL_ST_ABSOLUTE:
|
287 | return URL(databytes.decode('utf-8'))
|
288 | elif dsubtype == BMK_URL_ST_RELATIVE:
|
289 | baseoff,reloff = struct.unpack(b'<II', databytes)
|
290 | base = cls._get_item(data, hdrsize, baseoff)
|
291 | rel = cls._get_item(data, hdrsize, reloff)
|
292 | return URL(base, rel)
|
293 | elif dtype == BMK_ARRAY:
|
294 | result = []
|
295 | for aoff in xrange(offset+8,offset+8+length,4):
|
296 | eltoff, = struct.unpack(b'<I', data[aoff:aoff+4])
|
297 | result.append(cls._get_item(data, hdrsize, eltoff))
|
298 | return result
|
299 | elif dtype == BMK_DICT:
|
300 | result = {}
|
301 | for eoff in xrange(offset+8,offset+8+length,8):
|
302 | keyoff,valoff = struct.unpack(b'<II', data[eoff:eoff+8])
|
303 | key = cls._get_item(data, hdrsize, keyoff)
|
304 | val = cls._get_item(data, hdrsize, valoff)
|
305 | result[key] = val
|
306 | return result
|
307 | elif dtype == BMK_NULL:
|
308 | return None
|
309 |
|
310 | print('Unknown data type %08x' % typecode)
|
311 | return (typecode, databytes)
|
312 |
|
313 | @classmethod
|
314 | def from_bytes(cls, data):
|
315 | """Create a :class:`Bookmark` given byte data."""
|
316 |
|
317 | if len(data) < 16:
|
318 | raise ValueError('Not a bookmark file (too short)')
|
319 |
|
320 | if isinstance(data, bytearray):
|
321 | data = bytes(data)
|
322 |
|
323 | magic,size,dummy,hdrsize = struct.unpack(b'<4sIII', data[0:16])
|
324 |
|
325 | if magic != b'book':
|
326 | raise ValueError('Not a bookmark file (bad magic) %r' % magic)
|
327 |
|
328 | if hdrsize < 16:
|
329 | raise ValueError('Not a bookmark file (header size too short)')
|
330 |
|
331 | if hdrsize > size:
|
332 | raise ValueError('Not a bookmark file (header size too large)')
|
333 |
|
334 | if size != len(data):
|
335 | raise ValueError('Not a bookmark file (truncated)')
|
336 |
|
337 | tocoffset, = struct.unpack(b'<I', data[hdrsize:hdrsize+4])
|
338 |
|
339 | tocs = []
|
340 |
|
341 | while tocoffset != 0:
|
342 | tocbase = hdrsize + tocoffset
|
343 | if tocoffset > size - hdrsize \
|
344 | or size - tocbase < 20:
|
345 | raise ValueError('TOC offset out of range')
|
346 |
|
347 | tocsize,tocmagic,tocid,nexttoc,toccount \
|
348 | = struct.unpack(b'<IIIII',
|
349 | data[tocbase:tocbase+20])
|
350 |
|
351 | if tocmagic != 0xfffffffe:
|
352 | break
|
353 |
|
354 | tocsize += 8
|
355 |
|
356 | if size - tocbase < tocsize:
|
357 | raise ValueError('TOC truncated')
|
358 |
|
359 | if tocsize < 12 * toccount:
|
360 | raise ValueError('TOC entries overrun TOC size')
|
361 |
|
362 | toc = {}
|
363 | for n in xrange(0,toccount):
|
364 | ebase = tocbase + 20 + 12 * n
|
365 | eid,eoffset,edummy = struct.unpack(b'<III',
|
366 | data[ebase:ebase+12])
|
367 |
|
368 | if eid & 0x80000000:
|
369 | eid = cls._get_item(data, hdrsize, eid & 0x7fffffff)
|
370 |
|
371 | toc[eid] = cls._get_item(data, hdrsize, eoffset)
|
372 |
|
373 | tocs.append((tocid, toc))
|
374 |
|
375 | tocoffset = nexttoc
|
376 |
|
377 | return cls(tocs)
|
378 |
|
379 | def __getitem__(self, key):
|
380 | for tid,toc in self.tocs:
|
381 | if key in toc:
|
382 | return toc[key]
|
383 | raise KeyError('Key not found')
|
384 |
|
385 | def __setitem__(self, key, value):
|
386 | if len(self.tocs) == 0:
|
387 | self.tocs = [(1, {})]
|
388 | self.tocs[0][1][key] = value
|
389 |
|
390 | def get(self, key, default=None):
|
391 | """Lookup the value for a given key, returning a default if not
|
392 | present."""
|
393 | for tid,toc in self.tocs:
|
394 | if key in toc:
|
395 | return toc[key]
|
396 | return default
|
397 |
|
398 | @classmethod
|
399 | def _encode_item(cls, item, offset):
|
400 | if item is True:
|
401 | result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
|
402 | elif item is False:
|
403 | result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
|
404 | elif isinstance(item, unicode):
|
405 | encoded = item.encode('utf-8')
|
406 | result = (struct.pack(b'<II', len(encoded), BMK_STRING | BMK_ST_ONE)
|
407 | + encoded)
|
408 | elif isinstance(item, bytes):
|
409 | result = (struct.pack(b'<II', len(item), BMK_STRING | BMK_ST_ONE)
|
410 | + item)
|
411 | elif isinstance(item, Data):
|
412 | result = (struct.pack(b'<II', len(item.bytes),
|
413 | BMK_DATA | BMK_ST_ONE)
|
414 | + bytes(item.bytes))
|
415 | elif isinstance(item, bytearray):
|
416 | result = (struct.pack(b'<II', len(item),
|
417 | BMK_DATA | BMK_ST_ONE)
|
418 | + bytes(item))
|
419 | elif isinstance(item, int) or isinstance(item, long):
|
420 | if item > -0x80000000 and item < 0x7fffffff:
|
421 | result = struct.pack(b'<IIi', 4,
|
422 | BMK_NUMBER | kCFNumberSInt32Type, item)
|
423 | else:
|
424 | result = struct.pack(b'<IIq', 8,
|
425 | BMK_NUMBER | kCFNumberSInt64Type, item)
|
426 | elif isinstance(item, float):
|
427 | result = struct.pack(b'<IId', 8,
|
428 | BMK_NUMBER | kCFNumberFloat64Type, item)
|
429 | elif isinstance(item, datetime.datetime):
|
430 | secs = item - osx_epoch
|
431 | result = struct.pack(b'<II', 8, BMK_DATE | BMK_ST_ZERO) \
|
432 | + struct.pack(b'>d', float(secs.total_seconds()))
|
433 | elif isinstance(item, uuid.UUID):
|
434 | result = struct.pack(b'<II', 16, BMK_UUID | BMK_ST_ONE) \
|
435 | + item.bytes
|
436 | elif isinstance(item, URL):
|
437 | if item.base:
|
438 | baseoff = offset + 16
|
439 | reloff, baseenc = cls._encode_item(item.base, baseoff)
|
440 | xoffset, relenc = cls._encode_item(item.relative, reloff)
|
441 | result = b''.join([
|
442 | struct.pack(b'<IIII', 8, BMK_URL | BMK_URL_ST_RELATIVE,
|
443 | baseoff, reloff),
|
444 | baseenc,
|
445 | relenc])
|
446 | else:
|
447 | encoded = item.relative.encode('utf-8')
|
448 | result = struct.pack(b'<II', len(encoded),
|
449 | BMK_URL | BMK_URL_ST_ABSOLUTE) + encoded
|
450 | elif isinstance(item, list):
|
451 | ioffset = offset + 8 + len(item) * 4
|
452 | result = [struct.pack(b'<II', len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
|
453 | enc = []
|
454 | for elt in item:
|
455 | result.append(struct.pack(b'<I', ioffset))
|
456 | ioffset, ienc = cls._encode_item(elt, ioffset)
|
457 | enc.append(ienc)
|
458 | result = b''.join(result + enc)
|
459 | elif isinstance(item, dict):
|
460 | ioffset = offset + 8 + len(item) * 8
|
461 | result = [struct.pack(b'<II', len(item) * 8, BMK_DICT | BMK_ST_ONE)]
|
462 | enc = []
|
463 | for k,v in iteritems(item):
|
464 | result.append(struct.pack(b'<I', ioffset))
|
465 | ioffset, ienc = cls._encode_item(k, ioffset)
|
466 | enc.append(ienc)
|
467 | result.append(struct.pack(b'<I', ioffset))
|
468 | ioffset, ienc = cls._encode_item(v, ioffset)
|
469 | enc.append(ienc)
|
470 | result = b''.join(result + enc)
|
471 | elif item is None:
|
472 | result = struct.pack(b'<II', 0, BMK_NULL | BMK_ST_ONE)
|
473 | else:
|
474 | raise ValueError('Unknown item type when encoding: %s' % item)
|
475 |
|
476 | offset += len(result)
|
477 |
|
478 |
|
479 | if offset & 3:
|
480 | extra = 4 - (offset & 3)
|
481 | result += b'\0' * extra
|
482 | offset += extra
|
483 |
|
484 | return (offset, result)
|
485 |
|
486 | def to_bytes(self):
|
487 | """Convert this :class:`Bookmark` to a byte representation."""
|
488 |
|
489 | result = []
|
490 | tocs = []
|
491 | offset = 4
|
492 |
|
493 |
|
494 | for tid,toc in self.tocs:
|
495 | entries = []
|
496 |
|
497 | for k,v in iteritems(toc):
|
498 | if isinstance(k, (str, unicode)):
|
499 | noffset = offset
|
500 | voffset, enc = self._encode_item(k, offset)
|
501 | result.append(enc)
|
502 | offset, enc = self._encode_item(v, voffset)
|
503 | result.append(enc)
|
504 | entries.append((noffset | 0x80000000, voffset))
|
505 | else:
|
506 | entries.append((k, offset))
|
507 | offset, enc = self._encode_item(v, offset)
|
508 | result.append(enc)
|
509 |
|
510 |
|
511 |
|
512 | entries.sort()
|
513 |
|
514 | tocs.append((tid, b''.join([struct.pack(b'<III',k,o,0)
|
515 | for k,o in entries])))
|
516 |
|
517 | first_toc_offset = offset
|
518 |
|
519 |
|
520 | for ndx,toc in enumerate(tocs):
|
521 | tid, data = toc
|
522 | if ndx == len(tocs) - 1:
|
523 | next_offset = 0
|
524 | else:
|
525 | next_offset = offset + 20 + len(data)
|
526 |
|
527 | result.append(struct.pack(b'<IIIII', len(data) - 8,
|
528 | 0xfffffffe,
|
529 | tid,
|
530 | next_offset,
|
531 | len(data) // 12))
|
532 | result.append(data)
|
533 |
|
534 | offset += 20 + len(data)
|
535 |
|
536 |
|
537 |
|
538 | header = struct.pack(b'<4sIIIQQQQI', b'book',
|
539 | offset + 48,
|
540 | 0x10040000,
|
541 | 48,
|
542 | 0, 0, 0, 0, first_toc_offset)
|
543 |
|
544 | result.insert(0, header)
|
545 |
|
546 | return b''.join(result)
|
547 |
|
548 | @classmethod
|
549 | def for_file(cls, path):
|
550 | """Construct a :class:`Bookmark` for a given file."""
|
551 |
|
552 |
|
553 | st = osx.statfs(path)
|
554 | vol_path = st.f_mntonname.decode('utf-8')
|
555 |
|
556 |
|
557 | attrs = [osx.ATTR_CMN_CRTIME,
|
558 | osx.ATTR_VOL_SIZE
|
559 | | osx.ATTR_VOL_NAME
|
560 | | osx.ATTR_VOL_UUID,
|
561 | 0, 0, 0]
|
562 | volinfo = osx.getattrlist(vol_path, attrs, 0)
|
563 |
|
564 | vol_crtime = volinfo[0]
|
565 | vol_size = volinfo[1]
|
566 | vol_name = volinfo[2]
|
567 | vol_uuid = volinfo[3]
|
568 |
|
569 |
|
570 | attrs = [(osx.ATTR_CMN_OBJTYPE
|
571 | | osx.ATTR_CMN_CRTIME
|
572 | | osx.ATTR_CMN_FILEID), 0, 0, 0, 0]
|
573 | info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
|
574 |
|
575 | cnid = info[2]
|
576 | crtime = info[1]
|
577 |
|
578 | if info[0] == osx.VREG:
|
579 | flags = kCFURLResourceIsRegularFile
|
580 | elif info[0] == osx.VDIR:
|
581 | flags = kCFURLResourceIsDirectory
|
582 | elif info[0] == osx.VLNK:
|
583 | flags = kCFURLResourceIsSymbolicLink
|
584 | else:
|
585 | flags = kCFURLResourceIsRegularFile
|
586 |
|
587 | dirname, filename = os.path.split(path)
|
588 |
|
589 | relcount = 0
|
590 | if not os.path.isabs(dirname):
|
591 | curdir = os.getcwd()
|
592 | head, tail = os.path.split(curdir)
|
593 | relcount = 0
|
594 | while head and tail:
|
595 | relcount += 1
|
596 | head, tail = os.path.split(head)
|
597 | dirname = os.path.join(curdir, dirname)
|
598 |
|
599 | foldername = os.path.basename(dirname)
|
600 |
|
601 | rel_path = os.path.relpath(path, vol_path)
|
602 |
|
603 |
|
604 | name_path = []
|
605 | cnid_path = []
|
606 | head, tail = os.path.split(rel_path)
|
607 | if not tail:
|
608 | head, tail = os.path.split(head)
|
609 | while head or tail:
|
610 | if head:
|
611 | attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
|
612 | info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
|
613 | cnid_path.insert(0, info[0])
|
614 | head, tail = os.path.split(head)
|
615 | name_path.insert(0, tail)
|
616 | else:
|
617 | head, tail = os.path.split(head)
|
618 | name_path.append(filename)
|
619 | cnid_path.append(cnid)
|
620 |
|
621 | url_lengths = [relcount, len(name_path) - relcount]
|
622 |
|
623 | fileprops = Data(struct.pack(b'<QQQ', flags, 0x0f, 0))
|
624 | volprops = Data(struct.pack(b'<QQQ', 0x81 | kCFURLVolumeSupportsPersistentIDs,
|
625 | 0x13ef | kCFURLVolumeSupportsPersistentIDs, 0))
|
626 |
|
627 | toc = {
|
628 | kBookmarkPath: name_path,
|
629 | kBookmarkCNIDPath: cnid_path,
|
630 | kBookmarkFileCreationDate: crtime,
|
631 | kBookmarkFileProperties: fileprops,
|
632 | kBookmarkContainingFolder: len(name_path) - 2,
|
633 | kBookmarkVolumePath: vol_path,
|
634 | kBookmarkVolumeIsRoot: vol_path == '/',
|
635 | kBookmarkVolumeURL: URL('file://' + vol_path),
|
636 | kBookmarkVolumeName: vol_name,
|
637 | kBookmarkVolumeSize: vol_size,
|
638 | kBookmarkVolumeCreationDate: vol_crtime,
|
639 | kBookmarkVolumeUUID: str(vol_uuid).upper(),
|
640 | kBookmarkVolumeProperties: volprops,
|
641 | kBookmarkCreationOptions: 512,
|
642 | kBookmarkWasFileReference: True,
|
643 | kBookmarkUserName: 'unknown',
|
644 | kBookmarkUID: 99,
|
645 | }
|
646 |
|
647 | if relcount:
|
648 | toc[kBookmarkURLLengths] = url_lengths
|
649 |
|
650 | return Bookmark([(1, toc)])
|
651 |
|
652 | def __repr__(self):
|
653 | result = ['Bookmark([']
|
654 | for tid,toc in self.tocs:
|
655 | result.append('(0x%x, {\n' % tid)
|
656 | for k,v in iteritems(toc):
|
657 | if isinstance(k, (str, unicode)):
|
658 | kf = repr(k)
|
659 | else:
|
660 | kf = '0x%04x' % k
|
661 | result.append(' %s: %r\n' % (kf, v))
|
662 | result.append('}),\n')
|
663 | result.append('])')
|
664 |
|
665 | return ''.join(result)
|