dmg-builder 26.4.1 → 26.6.0

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