dmg-builder 26.5.0 → 26.7.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,772 +0,0 @@
1
- import datetime
2
- import io
3
- import os
4
- import os.path
5
- import struct
6
- import sys
7
- from unicodedata import normalize
8
-
9
- if sys.platform == "darwin":
10
- from . import osx
11
-
12
- from .utils import mac_epoch
13
-
14
- ALIAS_KIND_FILE = 0
15
- ALIAS_KIND_FOLDER = 1
16
-
17
- ALIAS_HFS_VOLUME_SIGNATURE = b"H+"
18
-
19
- ALIAS_FILESYSTEM_UDF = "UDF (CD/DVD)"
20
- ALIAS_FILESYSTEM_FAT32 = "FAT32"
21
- ALIAS_FILESYSTEM_EXFAT = "exFAT"
22
- ALIAS_FILESYSTEM_HFSX = "HFSX"
23
- ALIAS_FILESYSTEM_HFSPLUS = "HFS+"
24
- ALIAS_FILESYSTEM_FTP = "FTP"
25
- ALIAS_FILESYSTEM_NTFS = "NTFS"
26
- ALIAS_FILESYSTEM_UNKNOWN = "unknown"
27
-
28
- ALIAS_FIXED_DISK = 0
29
- ALIAS_NETWORK_DISK = 1
30
- ALIAS_400KB_FLOPPY_DISK = 2
31
- ALIAS_800KB_FLOPPY_DISK = 3
32
- ALIAS_1_44MB_FLOPPY_DISK = 4
33
- ALIAS_EJECTABLE_DISK = 5
34
-
35
- ALIAS_NO_CNID = 0xFFFFFFFF
36
-
37
- ALIAS_FSTYPE_MAP = {
38
- # Version 2 aliases
39
- b"HX": ALIAS_FILESYSTEM_HFSX,
40
- b"H+": ALIAS_FILESYSTEM_HFSPLUS,
41
- # Version 3 aliases
42
- b"BDcu": ALIAS_FILESYSTEM_UDF,
43
- b"BDIS": ALIAS_FILESYSTEM_FAT32,
44
- b"BDxF": ALIAS_FILESYSTEM_EXFAT,
45
- b"HX\0\0": ALIAS_FILESYSTEM_HFSX,
46
- b"H+\0\0": ALIAS_FILESYSTEM_HFSPLUS,
47
- b"KG\0\0": ALIAS_FILESYSTEM_FTP,
48
- b"NTcu": ALIAS_FILESYSTEM_NTFS,
49
- }
50
-
51
-
52
- def encode_utf8(s):
53
- if isinstance(s, bytes):
54
- return s
55
- return s.encode("utf-8")
56
-
57
-
58
- def decode_utf8(s):
59
- if isinstance(s, bytes):
60
- return s.decode("utf-8")
61
- return s
62
-
63
-
64
- class AppleShareInfo:
65
- def __init__(self, zone=None, server=None, user=None):
66
- #: The AppleShare zone
67
- self.zone = zone
68
- #: The AFP server
69
- self.server = server
70
- #: The username
71
- self.user = user
72
-
73
- def __repr__(self):
74
- return "AppleShareInfo({!r},{!r},{!r})".format(
75
- self.zone, self.server, self.user
76
- )
77
-
78
-
79
- class VolumeInfo:
80
- def __init__(
81
- self,
82
- name,
83
- creation_date,
84
- fs_type,
85
- disk_type,
86
- attribute_flags,
87
- fs_id,
88
- appleshare_info=None,
89
- driver_name=None,
90
- posix_path=None,
91
- disk_image_alias=None,
92
- dialup_info=None,
93
- network_mount_info=None,
94
- ):
95
- #: The name of the volume on which the target resides
96
- self.name = name
97
-
98
- #: The creation date of the target's volume
99
- self.creation_date = creation_date
100
-
101
- #: The filesystem type
102
- #: (for v2 aliases, this is a 2-character code; for v3 aliases, a
103
- #: 4-character code).
104
- self.fs_type = fs_type
105
-
106
- #: The type of disk; should be one of
107
- #:
108
- #: * ALIAS_FIXED_DISK
109
- #: * ALIAS_NETWORK_DISK
110
- #: * ALIAS_400KB_FLOPPY_DISK
111
- #: * ALIAS_800KB_FLOPPY_DISK
112
- #: * ALIAS_1_44MB_FLOPPY_DISK
113
- #: * ALIAS_EJECTABLE_DISK
114
- self.disk_type = disk_type
115
-
116
- #: Filesystem attribute flags (from HFS volume header)
117
- self.attribute_flags = attribute_flags
118
-
119
- #: Filesystem identifier
120
- self.fs_id = fs_id
121
-
122
- #: AppleShare information (for automatic remounting of network shares)
123
- #: *(optional)*
124
- self.appleshare_info = appleshare_info
125
-
126
- #: Driver name (*probably* contains a disk driver name on older Macs)
127
- #: *(optional)*
128
- self.driver_name = driver_name
129
-
130
- #: POSIX path of the mount point of the target's volume
131
- #: *(optional)*
132
- self.posix_path = posix_path
133
-
134
- #: :class:`Alias` object pointing at the disk image on which the
135
- #: target's volume resides *(optional)*
136
- self.disk_image_alias = disk_image_alias
137
-
138
- #: Dialup information (for automatic establishment of dialup connections)
139
- self.dialup_info = dialup_info
140
-
141
- #: Network mount information (for automatic remounting)
142
- self.network_mount_info = network_mount_info
143
-
144
- @property
145
- def filesystem_type(self):
146
- return ALIAS_FSTYPE_MAP.get(self.fs_type, ALIAS_FILESYSTEM_UNKNOWN)
147
-
148
- def __repr__(self):
149
- args = [
150
- "name",
151
- "creation_date",
152
- "fs_type",
153
- "disk_type",
154
- "attribute_flags",
155
- "fs_id",
156
- ]
157
- values = []
158
- for a in args:
159
- v = getattr(self, a)
160
- values.append(repr(v))
161
-
162
- kwargs = [
163
- "appleshare_info",
164
- "driver_name",
165
- "posix_path",
166
- "disk_image_alias",
167
- "dialup_info",
168
- "network_mount_info",
169
- ]
170
- for a in kwargs:
171
- v = getattr(self, a)
172
- if v is not None:
173
- values.append(f"{a}={v!r}")
174
- return "VolumeInfo(%s)" % ",".join(values)
175
-
176
-
177
- class TargetInfo:
178
- def __init__(
179
- self,
180
- kind,
181
- filename,
182
- folder_cnid,
183
- cnid,
184
- creation_date,
185
- creator_code,
186
- type_code,
187
- levels_from=-1,
188
- levels_to=-1,
189
- folder_name=None,
190
- cnid_path=None,
191
- carbon_path=None,
192
- posix_path=None,
193
- user_home_prefix_len=None,
194
- ):
195
- #: Either ALIAS_KIND_FILE or ALIAS_KIND_FOLDER
196
- self.kind = kind
197
-
198
- #: The filename of the target
199
- self.filename = filename
200
-
201
- #: The CNID (Catalog Node ID) of the target's containing folder;
202
- #: CNIDs are similar to but different than traditional UNIX inode
203
- #: numbers
204
- self.folder_cnid = folder_cnid
205
-
206
- #: The CNID (Catalog Node ID) of the target
207
- self.cnid = cnid
208
-
209
- #: The target's *creation* date.
210
- self.creation_date = creation_date
211
-
212
- #: The target's Mac creator code (a four-character binary string)
213
- self.creator_code = creator_code
214
-
215
- #: The target's Mac type code (a four-character binary string)
216
- self.type_code = type_code
217
-
218
- #: The depth of the alias? Always seems to be -1 on OS X.
219
- self.levels_from = levels_from
220
-
221
- #: The depth of the target? Always seems to be -1 on OS X.
222
- self.levels_to = levels_to
223
-
224
- #: The (POSIX) name of the target's containing folder. *(optional)*
225
- self.folder_name = folder_name
226
-
227
- #: The path from the volume root as a sequence of CNIDs. *(optional)*
228
- self.cnid_path = cnid_path
229
-
230
- #: The Carbon path of the target *(optional)*
231
- self.carbon_path = carbon_path
232
-
233
- #: The POSIX path of the target relative to the volume root. Note
234
- #: that this may or may not have a leading '/' character, but it is
235
- #: always relative to the containing volume. *(optional)*
236
- self.posix_path = posix_path
237
-
238
- #: If the path points into a user's home folder, the number of folders
239
- #: deep that we go before we get to that home folder. *(optional)*
240
- self.user_home_prefix_len = user_home_prefix_len
241
-
242
- def __repr__(self):
243
- args = [
244
- "kind",
245
- "filename",
246
- "folder_cnid",
247
- "cnid",
248
- "creation_date",
249
- "creator_code",
250
- "type_code",
251
- ]
252
- values = []
253
- for a in args:
254
- v = getattr(self, a)
255
- values.append(repr(v))
256
-
257
- if self.levels_from != -1:
258
- values.append("levels_from=%r" % self.levels_from)
259
- if self.levels_to != -1:
260
- values.append("levels_to=%r" % self.levels_to)
261
-
262
- kwargs = [
263
- "folder_name",
264
- "cnid_path",
265
- "carbon_path",
266
- "posix_path",
267
- "user_home_prefix_len",
268
- ]
269
- for a in kwargs:
270
- v = getattr(self, a)
271
- values.append(f"{a}={v!r}")
272
-
273
- return "TargetInfo(%s)" % ",".join(values)
274
-
275
-
276
- TAG_CARBON_FOLDER_NAME = 0
277
- TAG_CNID_PATH = 1
278
- TAG_CARBON_PATH = 2
279
- TAG_APPLESHARE_ZONE = 3
280
- TAG_APPLESHARE_SERVER_NAME = 4
281
- TAG_APPLESHARE_USERNAME = 5
282
- TAG_DRIVER_NAME = 6
283
- TAG_NETWORK_MOUNT_INFO = 9
284
- TAG_DIALUP_INFO = 10
285
- TAG_UNICODE_FILENAME = 14
286
- TAG_UNICODE_VOLUME_NAME = 15
287
- TAG_HIGH_RES_VOLUME_CREATION_DATE = 16
288
- TAG_HIGH_RES_CREATION_DATE = 17
289
- TAG_POSIX_PATH = 18
290
- TAG_POSIX_PATH_TO_MOUNTPOINT = 19
291
- TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE = 20
292
- TAG_USER_HOME_LENGTH_PREFIX = 21
293
-
294
-
295
- class Alias:
296
- def __init__(
297
- self,
298
- appinfo=b"\0\0\0\0",
299
- version=2,
300
- volume=None,
301
- target=None,
302
- extra=[],
303
- ):
304
- """Construct a new :class:`Alias` object with the specified
305
- contents."""
306
-
307
- #: Application specific information (four byte byte-string)
308
- self.appinfo = appinfo
309
-
310
- #: Version (we support versions 2 and 3)
311
- self.version = version
312
-
313
- #: A :class:`VolumeInfo` object describing the target's volume
314
- self.volume = volume
315
-
316
- #: A :class:`TargetInfo` object describing the target
317
- self.target = target
318
-
319
- #: A list of extra `(tag, value)` pairs
320
- self.extra = list(extra)
321
-
322
- @classmethod
323
- def _from_fd(cls, b):
324
- appinfo, recsize, version = struct.unpack(b">4shh", b.read(8))
325
-
326
- if recsize < 150:
327
- raise ValueError("Incorrect alias length")
328
-
329
- if version not in (2, 3):
330
- raise ValueError("Unsupported alias version %u" % version)
331
-
332
- if version == 2:
333
- (
334
- kind, # h
335
- volname, # 28p
336
- voldate, # I
337
- fstype, # 2s
338
- disktype, # h
339
- folder_cnid, # I
340
- filename, # 64p
341
- cnid, # I
342
- crdate, # I
343
- creator_code, # 4s
344
- type_code, # 4s
345
- levels_from, # h
346
- levels_to, # h
347
- volattrs, # I
348
- volfsid, # 2s
349
- reserved, # 10s
350
- ) = struct.unpack(b">h28pI2shI64pII4s4shhI2s10s", b.read(142))
351
- else:
352
- (
353
- kind, # h
354
- voldate_hr, # Q
355
- fstype, # 4s
356
- disktype, # h
357
- folder_cnid, # I
358
- cnid, # I
359
- crdate_hr, # Q
360
- volattrs, # I
361
- reserved, # 14s
362
- ) = struct.unpack(b">hQ4shIIQI14s", b.read(46))
363
-
364
- volname = b""
365
- filename = b""
366
- creator_code = None
367
- type_code = None
368
- voldate = voldate_hr / 65536.0
369
- crdate = crdate_hr / 65536.0
370
-
371
- voldate = mac_epoch + datetime.timedelta(seconds=voldate)
372
- crdate = mac_epoch + datetime.timedelta(seconds=crdate)
373
-
374
- alias = Alias()
375
- alias.appinfo = appinfo
376
-
377
- alias.volume = VolumeInfo(
378
- volname.decode().replace("/", ":"),
379
- voldate,
380
- fstype,
381
- disktype,
382
- volattrs,
383
- volfsid,
384
- )
385
- alias.target = TargetInfo(
386
- kind,
387
- filename.decode().replace("/", ":"),
388
- folder_cnid,
389
- cnid,
390
- crdate,
391
- creator_code,
392
- type_code,
393
- )
394
- alias.target.levels_from = levels_from
395
- alias.target.levels_to = levels_to
396
-
397
- tag = struct.unpack(b">h", b.read(2))[0]
398
-
399
- while tag != -1:
400
- length = struct.unpack(b">h", b.read(2))[0]
401
- value = b.read(length)
402
- if length & 1:
403
- b.read(1)
404
-
405
- if tag == TAG_CARBON_FOLDER_NAME:
406
- alias.target.folder_name = value.decode().replace("/", ":")
407
- elif tag == TAG_CNID_PATH:
408
- alias.target.cnid_path = struct.unpack(">%uI" % (length // 4), value)
409
- elif tag == TAG_CARBON_PATH:
410
- alias.target.carbon_path = value
411
- elif tag == TAG_APPLESHARE_ZONE:
412
- if alias.volume.appleshare_info is None:
413
- alias.volume.appleshare_info = AppleShareInfo()
414
- alias.volume.appleshare_info.zone = value
415
- elif tag == TAG_APPLESHARE_SERVER_NAME:
416
- if alias.volume.appleshare_info is None:
417
- alias.volume.appleshare_info = AppleShareInfo()
418
- alias.volume.appleshare_info.server = value
419
- elif tag == TAG_APPLESHARE_USERNAME:
420
- if alias.volume.appleshare_info is None:
421
- alias.volume.appleshare_info = AppleShareInfo()
422
- alias.volume.appleshare_info.user = value
423
- elif tag == TAG_DRIVER_NAME:
424
- alias.volume.driver_name = value
425
- elif tag == TAG_NETWORK_MOUNT_INFO:
426
- alias.volume.network_mount_info = value
427
- elif tag == TAG_DIALUP_INFO:
428
- alias.volume.dialup_info = value
429
- elif tag == TAG_UNICODE_FILENAME:
430
- alias.target.filename = value[2:].decode("utf-16be")
431
- elif tag == TAG_UNICODE_VOLUME_NAME:
432
- alias.volume.name = value[2:].decode("utf-16be")
433
- elif tag == TAG_HIGH_RES_VOLUME_CREATION_DATE:
434
- seconds = struct.unpack(b">Q", value)[0] / 65536.0
435
- alias.volume.creation_date = mac_epoch + datetime.timedelta(
436
- seconds=seconds
437
- )
438
- elif tag == TAG_HIGH_RES_CREATION_DATE:
439
- seconds = struct.unpack(b">Q", value)[0] / 65536.0
440
- alias.target.creation_date = mac_epoch + datetime.timedelta(
441
- seconds=seconds
442
- )
443
- elif tag == TAG_POSIX_PATH:
444
- alias.target.posix_path = value.decode()
445
- elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT:
446
- alias.volume.posix_path = value.decode()
447
- elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE:
448
- alias.volume.disk_image_alias = Alias.from_bytes(value)
449
- elif tag == TAG_USER_HOME_LENGTH_PREFIX:
450
- alias.target.user_home_prefix_len = struct.unpack(b">h", value)[0]
451
- else:
452
- alias.extra.append((tag, value))
453
-
454
- tag = struct.unpack(b">h", b.read(2))[0]
455
-
456
- return alias
457
-
458
- @classmethod
459
- def from_bytes(cls, bytes):
460
- """Construct an :class:`Alias` object given binary Alias data."""
461
- with io.BytesIO(bytes) as b:
462
- return cls._from_fd(b)
463
-
464
- @classmethod
465
- def for_file(cls, path):
466
- """Create an :class:`Alias` that points at the specified file."""
467
- if sys.platform != "darwin":
468
- raise Exception("Not implemented (requires special support)")
469
-
470
- path = encode_utf8(path)
471
-
472
- a = Alias()
473
-
474
- # Find the filesystem
475
- st = osx.statfs(path)
476
- vol_path = st.f_mntonname
477
-
478
- # File and folder names in HFS+ are normalized to a form similar to NFD.
479
- # Must be normalized (NFD->NFC) before use to avoid unicode string comparison issues.
480
- vol_path = normalize("NFC", vol_path.decode("utf-8")).encode("utf-8")
481
-
482
- # Grab its attributes
483
- attrs = [osx.ATTR_CMN_CRTIME, osx.ATTR_VOL_NAME, 0, 0, 0]
484
- volinfo = osx.getattrlist(vol_path, attrs, 0)
485
-
486
- vol_crtime = volinfo[0]
487
- vol_name = encode_utf8(volinfo[1])
488
-
489
- # Also grab various attributes of the file
490
- attrs = [
491
- osx.ATTR_CMN_OBJTYPE
492
- | osx.ATTR_CMN_CRTIME
493
- | osx.ATTR_CMN_FNDRINFO
494
- | osx.ATTR_CMN_FILEID
495
- | osx.ATTR_CMN_PARENTID,
496
- 0,
497
- 0,
498
- 0,
499
- 0,
500
- ]
501
- info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
502
-
503
- if info[0] == osx.VDIR:
504
- kind = ALIAS_KIND_FOLDER
505
- else:
506
- kind = ALIAS_KIND_FILE
507
-
508
- cnid = info[3]
509
- folder_cnid = info[4]
510
-
511
- dirname, filename = os.path.split(path)
512
-
513
- if dirname == b"" or dirname == b".":
514
- dirname = os.getcwd()
515
-
516
- foldername = os.path.basename(dirname)
517
-
518
- creation_date = info[1]
519
-
520
- if kind == ALIAS_KIND_FILE:
521
- creator_code = struct.pack(b"I", info[2].fileInfo.fileCreator)
522
- type_code = struct.pack(b"I", info[2].fileInfo.fileType)
523
- else:
524
- creator_code = b"\0\0\0\0"
525
- type_code = b"\0\0\0\0"
526
-
527
- a.target = TargetInfo(
528
- kind, filename, folder_cnid, cnid, creation_date, creator_code, type_code
529
- )
530
- a.volume = VolumeInfo(vol_name, vol_crtime, b"H+", ALIAS_FIXED_DISK, 0, b"\0\0")
531
-
532
- a.target.folder_name = foldername
533
- a.volume.posix_path = vol_path
534
-
535
- rel_path = os.path.relpath(path, vol_path)
536
-
537
- # Leave off the initial '/' if vol_path is '/' (no idea why)
538
- if vol_path == b"/":
539
- a.target.posix_path = rel_path
540
- else:
541
- a.target.posix_path = b"/" + rel_path
542
-
543
- # Construct the Carbon and CNID paths
544
- carbon_path = []
545
- cnid_path = []
546
- head, tail = os.path.split(rel_path)
547
- if not tail:
548
- head, tail = os.path.split(head)
549
- while head or tail:
550
- if head:
551
- attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
552
- info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
553
- cnid_path.append(info[0])
554
- carbon_tail = tail.replace(b":", b"/")
555
- carbon_path.insert(0, carbon_tail)
556
- head, tail = os.path.split(head)
557
-
558
- carbon_path = vol_name + b":" + b":\0".join(carbon_path)
559
-
560
- a.target.carbon_path = carbon_path
561
- a.target.cnid_path = cnid_path
562
-
563
- return a
564
-
565
- def _to_fd(self, b):
566
- # We'll come back and fix the length when we're done
567
- pos = b.tell()
568
- b.write(struct.pack(b">4shh", self.appinfo, 0, self.version))
569
-
570
- carbon_volname = encode_utf8(self.volume.name).replace(b":", b"/")
571
- carbon_filename = encode_utf8(self.target.filename).replace(b":", b"/")
572
- voldate = (self.volume.creation_date - mac_epoch).total_seconds()
573
- crdate = (self.target.creation_date - mac_epoch).total_seconds()
574
-
575
- if self.version == 2:
576
- # NOTE: crdate should be in local time, but that's system dependent
577
- # (so doing so is ridiculous, and nothing could rely on it).
578
- b.write(
579
- struct.pack(
580
- b">h28pI2shI64pII4s4shhI2s10s",
581
- self.target.kind, # h
582
- carbon_volname, # 28p
583
- int(voldate), # I
584
- self.volume.fs_type, # 2s
585
- self.volume.disk_type, # h
586
- self.target.folder_cnid, # I
587
- carbon_filename, # 64p
588
- self.target.cnid, # I
589
- int(crdate), # I
590
- self.target.creator_code, # 4s
591
- self.target.type_code, # 4s
592
- self.target.levels_from, # h
593
- self.target.levels_to, # h
594
- self.volume.attribute_flags, # I
595
- self.volume.fs_id, # 2s
596
- b"\0" * 10, # 10s
597
- )
598
- )
599
- else:
600
- b.write(
601
- struct.pack(
602
- b">hQ4shIIQI14s",
603
- self.target.kind, # h
604
- int(voldate * 65536), # Q
605
- self.volume.fs_type, # 4s
606
- self.volume.disk_type, # h
607
- self.target.folder_cnid, # I
608
- self.target.cnid, # I
609
- int(crdate * 65536), # Q
610
- self.volume.attribute_flags, # I
611
- b"\0" * 14, # 14s
612
- )
613
- )
614
-
615
- # Excuse the odd order; we're copying Finder
616
- if self.target.folder_name:
617
- carbon_foldername = encode_utf8(self.target.folder_name).replace(b":", b"/")
618
- b.write(struct.pack(b">hh", TAG_CARBON_FOLDER_NAME, len(carbon_foldername)))
619
- b.write(carbon_foldername)
620
- if len(carbon_foldername) & 1:
621
- b.write(b"\0")
622
-
623
- b.write(
624
- struct.pack(
625
- b">hhQhhQ",
626
- TAG_HIGH_RES_VOLUME_CREATION_DATE,
627
- 8,
628
- int(voldate * 65536),
629
- TAG_HIGH_RES_CREATION_DATE,
630
- 8,
631
- int(crdate * 65536),
632
- )
633
- )
634
-
635
- if self.target.cnid_path:
636
- cnid_path = struct.pack(
637
- ">%uI" % len(self.target.cnid_path), *self.target.cnid_path
638
- )
639
- b.write(struct.pack(b">hh", TAG_CNID_PATH, len(cnid_path)))
640
- b.write(cnid_path)
641
-
642
- if self.target.carbon_path:
643
- carbon_path = encode_utf8(self.target.carbon_path)
644
- b.write(struct.pack(b">hh", TAG_CARBON_PATH, len(carbon_path)))
645
- b.write(carbon_path)
646
- if len(carbon_path) & 1:
647
- b.write(b"\0")
648
-
649
- if self.volume.appleshare_info:
650
- ai = self.volume.appleshare_info
651
- if ai.zone:
652
- b.write(struct.pack(b">hh", TAG_APPLESHARE_ZONE, len(ai.zone)))
653
- b.write(ai.zone)
654
- if len(ai.zone) & 1:
655
- b.write(b"\0")
656
- if ai.server:
657
- b.write(struct.pack(b">hh", TAG_APPLESHARE_SERVER_NAME, len(ai.server)))
658
- b.write(ai.server)
659
- if len(ai.server) & 1:
660
- b.write(b"\0")
661
- if ai.username:
662
- b.write(struct.pack(b">hh", TAG_APPLESHARE_USERNAME, len(ai.username)))
663
- b.write(ai.username)
664
- if len(ai.username) & 1:
665
- b.write(b"\0")
666
-
667
- if self.volume.driver_name:
668
- driver_name = encode_utf8(self.volume.driver_name)
669
- b.write(struct.pack(b">hh", TAG_DRIVER_NAME, len(driver_name)))
670
- b.write(driver_name)
671
- if len(driver_name) & 1:
672
- b.write(b"\0")
673
-
674
- if self.volume.network_mount_info:
675
- b.write(
676
- struct.pack(
677
- b">hh", TAG_NETWORK_MOUNT_INFO, len(self.volume.network_mount_info)
678
- )
679
- )
680
- b.write(self.volume.network_mount_info)
681
- if len(self.volume.network_mount_info) & 1:
682
- b.write(b"\0")
683
-
684
- if self.volume.dialup_info:
685
- b.write(
686
- struct.pack(
687
- b">hh", TAG_DIALUP_INFO, len(self.volume.network_mount_info)
688
- )
689
- )
690
- b.write(self.volume.network_mount_info)
691
- if len(self.volume.network_mount_info) & 1:
692
- b.write(b"\0")
693
-
694
- utf16 = decode_utf8(self.target.filename).replace(":", "/").encode("utf-16-be")
695
- b.write(
696
- struct.pack(b">hhh", TAG_UNICODE_FILENAME, len(utf16) + 2, len(utf16) // 2)
697
- )
698
- b.write(utf16)
699
-
700
- utf16 = decode_utf8(self.volume.name).replace(":", "/").encode("utf-16-be")
701
- b.write(
702
- struct.pack(
703
- b">hhh", TAG_UNICODE_VOLUME_NAME, len(utf16) + 2, len(utf16) // 2
704
- )
705
- )
706
- b.write(utf16)
707
-
708
- if self.target.posix_path:
709
- posix_path = encode_utf8(self.target.posix_path)
710
- b.write(struct.pack(b">hh", TAG_POSIX_PATH, len(posix_path)))
711
- b.write(posix_path)
712
- if len(posix_path) & 1:
713
- b.write(b"\0")
714
-
715
- if self.volume.posix_path:
716
- posix_path = encode_utf8(self.volume.posix_path)
717
- b.write(struct.pack(b">hh", TAG_POSIX_PATH_TO_MOUNTPOINT, len(posix_path)))
718
- b.write(posix_path)
719
- if len(posix_path) & 1:
720
- b.write(b"\0")
721
-
722
- if self.volume.disk_image_alias:
723
- d = self.volume.disk_image_alias.to_bytes()
724
- b.write(struct.pack(b">hh", TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE, len(d)))
725
- b.write(d)
726
- if len(d) & 1:
727
- b.write(b"\0")
728
-
729
- if self.target.user_home_prefix_len is not None:
730
- b.write(
731
- struct.pack(
732
- b">hhh",
733
- TAG_USER_HOME_LENGTH_PREFIX,
734
- 2,
735
- self.target.user_home_prefix_len,
736
- )
737
- )
738
-
739
- for t, v in self.extra:
740
- b.write(struct.pack(b">hh", t, len(v)))
741
- b.write(v)
742
- if len(v) & 1:
743
- b.write(b"\0")
744
-
745
- b.write(struct.pack(b">hh", -1, 0))
746
-
747
- blen = b.tell() - pos
748
- b.seek(pos + 4, os.SEEK_SET)
749
- b.write(struct.pack(b">h", blen))
750
-
751
- def to_bytes(self):
752
- """Returns the binary representation for this :class:`Alias`."""
753
- with io.BytesIO() as b:
754
- self._to_fd(b)
755
- return b.getvalue()
756
-
757
- def __str__(self):
758
- return "<Alias target=%s>" % self.target.filename
759
-
760
- def __repr__(self):
761
- values = []
762
- if self.appinfo != b"\0\0\0\0":
763
- values.append("appinfo=%r" % self.appinfo)
764
- if self.version != 2:
765
- values.append("version=%r" % self.version)
766
- if self.volume is not None:
767
- values.append("volume=%r" % self.volume)
768
- if self.target is not None:
769
- values.append("target=%r" % self.target)
770
- if self.extra:
771
- values.append("extra=%r" % self.extra)
772
- return "Alias(%s)" % ",".join(values)