def BLOBParser_raw(blob): if args.blob_parse_raw == True: print( "----------------------------------------------------------------------------" ) try: b = Bookmark.from_bytes(blob) print("Raw Parsed Bookmark BLOB:") print(b) except: pass try: a = Alias.from_bytes(blob) print("Raw Parsed Alias BLOB:") print(a.version) print(a.target) print(a.volume) print(a.extra) print(a.appinfo) print(a.AppleShareInfo.server) print(a.AppleShareInfo.user) print(a.AppleShareInfo.zone) except: pass print( "----------------------------------------------------------------------------" )
def BLOBParser_human(blob): # http://mac-alias.readthedocs.io/en/latest/bookmark_fmt.html try: b = Bookmark.from_bytes(blob) return "/" + u"/".join(b.get(0x1004, default=None)) except Exception as e: print(e)
def BLOBParser_raw(blob): if args.blob_parse_raw == True: print "----------------------------------------------------------------------------" try: b = Bookmark.from_bytes(blob) print "Raw Parsed Bookmark BLOB:" print b except: pass try: a = Alias.from_bytes(blob) print "Raw Parsed Alias BLOB:" print a.version print a.target print a.volume print a.extra print a.appinfo print a.AppleShareInfo.server print a.AppleShareInfo.user print a.AppleShareInfo.zone except: pass print "----------------------------------------------------------------------------"
def BLOBParser_human(blob): #As described in: #http://mac-alias.readthedocs.io/en/latest/bookmark_fmt.html #http://mac-alias.readthedocs.io/en/latest/alias_fmt.html if args.blob_parse_human == True: print "----------------------------------------------------------------------------" print "Human Readable Parsed BLOB:" try: b = Bookmark.from_bytes(blob) print "\tBookmark BLOB: Volume Name [0x2010]: \t\t" + b.get( 0x2010, default=None) print "\tBookmark BLOB: Volume Path [0x2002]: \t\t" + str( b.get(0x2002, default=None)) print "\tBookmark BLOB: Volume Flags [0x2020]: \t\t" + str( b.get(0x2020, default=None)) print "\tBookmark BLOB: Volume is Root FS [0x2030]: \t" + str( b.get(0x2030, default=None)) print "\tBookmark BLOB: Volume UUID [0x2011]: \t\t" + str( b.get(0x2011, default=None)) print "\tBookmark BLOB: Volume Size [0x2012]: \t\t" + str( b.get(0x2012, default=None)) print "\tBookmark BLOB: Volume Creation Date [0x2013]: \t" + str( b.get(0x2013, default=None)) print "\tBookmark BLOB: Volume URL [0x2005]: \t\t" + str( b.get(0x2005, default=None)) print "\tBookmark BLOB: Volume Bookmark [0x2040]: \t" + str( b.get(0x2040, default=None)) print "\tBookmark BLOB: Volume Mount Point [0x2050]: \t" + str( b.get(0x2050, default=None)) print "\tBookmark BLOB: Security Extension [0xf080]: \t" + str( b.get(0xf080, default=None)) print "\tBookmark BLOB: Security Extension [0xf081]: \t" + str( b.get(0xf081, default=None)) print "" print "\tBookmark BLOB: Target Path [0x1004]: \t\t" + str( b.get(0x1004, default=None)) print "\tBookmark BLOB: Target CNID Path [0x1005]: \t" + str( b.get(0x1005, default=None)) print "\tBookmark BLOB: Containing Folder Index [0xc001]:" + str( b.get(0xc001, default=None)) print "\tBookmark BLOB: Target Creation Date [0x1040]: \t" + str( b.get(0x1040, default=None)) print "\tBookmark BLOB: Target Flags [0x1010]: \t\t" + str( b.get(0x1010, default=None)) print "\tBookmark BLOB: Target Filename [0x1020]: \t" + str( b.get(0x1020, default=None)) print "" print "\tBookmark BLOB: Creator Username [0xc011]: \t" + str( b.get(0xc011, default=None)) print "\tBookmark BLOB: Creator UID [0xc012]: \t\t" + str( b.get(0xc012, default=None)) print "" print "\tBookmark BLOB: Unknown [0x1003]: \t\t" + str( b.get(0x1003, default=None)) print "\tBookmark BLOB: Unknown [0x1054]: \t\t" + str( b.get(0x1054, default=None)) print "\tBookmark BLOB: Unknown [0x1055]: \t\t" + str( b.get(0x1055, default=None)) print "\tBookmark BLOB: Unknown [0x1056]: \t\t" + str( b.get(0x1056, default=None)) print "\tBookmark BLOB: Unknown [0x1101]: \t\t" + str( b.get(0x1101, default=None)) print "\tBookmark BLOB: Unknown [0x1102]: \t\t" + str( b.get(0x1102, default=None)) print "\tBookmark BLOB: TOC Path [0x2000]: \t\t" + str( b.get(0x2000, default=None)) print "\tBookmark BLOB: Unknown [0x2070]: \t\t" + str( b.get(0x2070, default=None)) print "\tBookmark BLOB: File Reference Flag [0xd001]: \t" + str( b.get(0xd001, default=None)) print "\tBookmark BLOB: Creation Options [0xd010]: \t" + str( b.get(0xd010, default=None)) print "\tBookmark BLOB: URL Length Array [0xe003]: \t" + str( b.get(0xe003, default=None)) print "\tBookmark BLOB: Localized Name (?) [0xf017]: \t" + str( b.get(0xf017, default=None)) print "\tBookmark BLOB: Unknown [0xf022]: \t\t" + str( b.get(0xf022, default=None)) count = "1" if b.get(0xf020, default=None): filename = "ICNS_file_" + count + ".icns" saveICNS = open(filename, 'w') saveICNS.write(b.get(0xf020, default=None).bytes) saveICNS.close() print "\tBookmark BLOB: ICNS (Icon) File [0xf020]: \tICNS File Saved in: " + filename count += count except: pass try: a = Alias.from_bytes(blob) print "\tAlias BLOB: Alias Version: \t\t" + str(a.version) print "\tAlias BLOB: Target Filename: \t\t" + a.target.filename print "\tAlias BLOB: Target File CNID: \t\t" + str(a.target.cnid) print "\tAlias BLOB: Target Carbon Path: \t" + a.target.carbon_path print "\tAlias BLOB: Target POSIX Path: \t\t" + a.target.posix_path print "\tAlias BLOB: Target Creation Date: \t" + str( a.target.creation_date) print "\tAlias BLOB: Target Creator Code: \t" + a.target.creator_code print "\tAlias BLOB: Target Type Code: \t\t" + a.target.type_code print "\tAlias BLOB: Target Folder Name: \t" + a.target.folder_name print "\tAlias BLOB: Target Folder CNID: \t" + str( a.target.folder_cnid) if a.target.kind == 0: print "\tAlias BLOB: Target Kind: \t\tFile" elif a.target.kind == 1: print "\tAlias BLOB: Target Kind: \t\tFolder" print "" print "\tAlias BLOB: Levels From: \t\t" + str(a.target.levels_from) print "\tAlias BLOB: Levels To: \t\t\t" + str(a.target.levels_to) print "\tAlias BLOB: User Home Prefix Length: \t" + str( a.target.user_home_prefix_len) print "" print "\tAlias BLOB: Volume Name: \t\t" + a.volume.name print "\tAlias BLOB: Volume Creation Date: \t" + str( a.volume.creation_date) print "\tAlias BLOB: Volume Filesystem Type: \t" + a.volume.fs_type print "\tAlias BLOB: Volume Disk Type: \t\t" + str( a.volume.disk_type) print "\tAlias BLOB: Volume Attribute Flags: \t" + str( a.volume.attribute_flags) print "\tAlias BLOB: Volume Filesystem ID: \t" + str( a.volume.fs_id) print "" print "\tAlias BLOB: Volume AppleShare Information: \t" + a.volume.appleshare_info print "\tAlias BLOB: Volume Driver Name: \t" + str( a.volume.driver_name) print "\tAlias BLOB: Volume POSIX Path: \t" + str( a.volume.posix_path) print "\tAlias BLOB: Volume Disk Image Alias: \t" + a.volume.disk_image_alias print "\tAlias BLOB: Volume Creation Date: \t" + str( a.volume.creation_date) print "\tAlias BLOB: Volume Dialup Information: \t" + str( a.volume.dialup_info) print "\tAlias BLOB: Volume Network Mount Information: \t" + str( a.volume.network_mount_info) print "" print "\tAlias BLOB: Extra: \t" + str(a.extra) print "\tAlias BLOB: App Info: \t" + str(a.appinfo) print "\tAlias BLOB: Apple Share Info - Server: \t" + str( a.AppleShareInfo.server) print "\tAlias BLOB: Apple Share Info - User: \t" + str( a.AppleShareInfo.user) print "\tAlias BLOB: Apple Share Info - Zone \t" + str( a.AppleShareInfo.zone) except: pass print "----------------------------------------------------------------------------"
from mac_alias import Bookmark import sys import biplist if len(sys.argv) != 3: print 'Usage: python create-dsstore.py <VolumeName> <Application>' exit(1) volume = sys.argv[1] app = sys.argv[2] image_file = volume + '/.background/background.png' alias = Alias.for_file(image_file) bookmark = Bookmark.for_file(image_file) with DSStore.open(volume + '/.DS_Store', 'w+') as ds: ds['.']['bwsp'] = { 'ShowStatusBar': False, 'WindowBounds': '{{320, 320}, {400, 200}}', 'ContainerShowSidebar': False, 'SidebarWidth': 0, 'ShowTabView': False, 'PreviewPaneVisibility': False, 'ShowToolbar': False, 'ShowPathbar': False, 'ShowSidebar': False } ds['.']['icvp'] = { 'gridSpacing': 50.0,
def BLOBParser_human(blob): #As described in: #http://mac-alias.readthedocs.io/en/latest/bookmark_fmt.html #http://mac-alias.readthedocs.io/en/latest/alias_fmt.html if args.blob_parse_human == True: print "----------------------------------------------------------------------------" print "Human Readable Parsed BLOB:" try: b = Bookmark.from_bytes(blob) print "\tBookmark BLOB: Volume Name [0x2010]: \t\t" + b.get(0x2010,default=None) print "\tBookmark BLOB: Volume Path [0x2002]: \t\t" + str(b.get(0x2002,default=None)) print "\tBookmark BLOB: Volume Flags [0x2020]: \t\t" + str(b.get(0x2020,default=None)) print "\tBookmark BLOB: Volume is Root FS [0x2030]: \t" + str(b.get(0x2030,default=None)) print "\tBookmark BLOB: Volume UUID [0x2011]: \t\t" + str(b.get(0x2011,default=None)) print "\tBookmark BLOB: Volume Size [0x2012]: \t\t" + str(b.get(0x2012,default=None)) print "\tBookmark BLOB: Volume Creation Date [0x2013]: \t" + str(b.get(0x2013,default=None)) print "\tBookmark BLOB: Volume URL [0x2005]: \t\t" + str(b.get(0x2005,default=None)) print "\tBookmark BLOB: Volume Bookmark [0x2040]: \t" + str(b.get(0x2040,default=None)) print "\tBookmark BLOB: Volume Mount Point [0x2050]: \t" + str(b.get(0x2050,default=None)) print "\tBookmark BLOB: Security Extension [0xf080]: \t" + str(b.get(0xf080,default=None)) print "\tBookmark BLOB: Security Extension [0xf081]: \t" + str(b.get(0xf081,default=None)) print "" print "\tBookmark BLOB: Target Path [0x1004]: \t\t" + str(b.get(0x1004,default=None)) print "\tBookmark BLOB: Target CNID Path [0x1005]: \t" + str(b.get(0x1005,default=None)) print "\tBookmark BLOB: Containing Folder Index [0xc001]:" + str(b.get(0xc001,default=None)) print "\tBookmark BLOB: Target Creation Date [0x1040]: \t" + str(b.get(0x1040,default=None)) print "\tBookmark BLOB: Target Flags [0x1010]: \t\t" + str(b.get(0x1010,default=None)) print "\tBookmark BLOB: Target Filename [0x1020]: \t" + str(b.get(0x1020,default=None)) print "" print "\tBookmark BLOB: Creator Username [0xc011]: \t" + str(b.get(0xc011,default=None)) print "\tBookmark BLOB: Creator UID [0xc012]: \t\t" + str(b.get(0xc012,default=None)) print "" print "\tBookmark BLOB: Unknown [0x1003]: \t\t" + str(b.get(0x1003,default=None)) print "\tBookmark BLOB: Unknown [0x1054]: \t\t" + str(b.get(0x1054,default=None)) print "\tBookmark BLOB: Unknown [0x1055]: \t\t" + str(b.get(0x1055,default=None)) print "\tBookmark BLOB: Unknown [0x1056]: \t\t" + str(b.get(0x1056,default=None)) print "\tBookmark BLOB: Unknown [0x1101]: \t\t" + str(b.get(0x1101,default=None)) print "\tBookmark BLOB: Unknown [0x1102]: \t\t" + str(b.get(0x1102,default=None)) print "\tBookmark BLOB: TOC Path [0x2000]: \t\t" + str(b.get(0x2000,default=None)) print "\tBookmark BLOB: Unknown [0x2070]: \t\t" + str(b.get(0x2070,default=None)) print "\tBookmark BLOB: File Reference Flag [0xd001]: \t" + str(b.get(0xd001,default=None)) print "\tBookmark BLOB: Creation Options [0xd010]: \t" + str(b.get(0xd010,default=None)) print "\tBookmark BLOB: URL Length Array [0xe003]: \t" + str(b.get(0xe003,default=None)) print "\tBookmark BLOB: Localized Name (?) [0xf017]: \t" + str(b.get(0xf017,default=None)) print "\tBookmark BLOB: Unknown [0xf022]: \t\t" + str(b.get(0xf022,default=None)) if b.get(0xf020,default=None): icon_uuid = uuid.uuid4() print icon_uuid filename = "ICNS_file_" + str(icon_uuid) + ".icns" saveICNS = open(filename,'w') saveICNS.write(b.get(0xf020,default=None).bytes) saveICNS.close() print "\tBookmark BLOB: ICNS (Icon) File [0xf020]: \tICNS File Saved in: " + filename except: pass try: a = Alias.from_bytes(blob) print "\tAlias BLOB: Alias Version: \t\t" + str(a.version) print "\tAlias BLOB: Target Filename: \t\t" + a.target.filename print "\tAlias BLOB: Target File CNID: \t\t" + str(a.target.cnid) print "\tAlias BLOB: Target Carbon Path: \t" + a.target.carbon_path print "\tAlias BLOB: Target POSIX Path: \t\t" + a.target.posix_path print "\tAlias BLOB: Target Creation Date: \t" + str(a.target.creation_date) print "\tAlias BLOB: Target Creator Code: \t" + a.target.creator_code print "\tAlias BLOB: Target Type Code: \t\t" + a.target.type_code print "\tAlias BLOB: Target Folder Name: \t" + a.target.folder_name print "\tAlias BLOB: Target Folder CNID: \t" + str(a.target.folder_cnid) if a.target.kind == 0: print "\tAlias BLOB: Target Kind: \t\tFile" elif a.target.kind == 1: print "\tAlias BLOB: Target Kind: \t\tFolder" print "" print "\tAlias BLOB: Levels From: \t\t" + str(a.target.levels_from) print "\tAlias BLOB: Levels To: \t\t\t" + str(a.target.levels_to) print "\tAlias BLOB: User Home Prefix Length: \t" + str(a.target.user_home_prefix_len) print "" print "\tAlias BLOB: Volume Name: \t\t" + a.volume.name print "\tAlias BLOB: Volume Creation Date: \t" + str(a.volume.creation_date) print "\tAlias BLOB: Volume Filesystem Type: \t" + a.volume.fs_type print "\tAlias BLOB: Volume Disk Type: \t\t" + str(a.volume.disk_type) print "\tAlias BLOB: Volume Attribute Flags: \t" + str(a.volume.attribute_flags) print "\tAlias BLOB: Volume Filesystem ID: \t" + str(a.volume.fs_id) print "" print "\tAlias BLOB: Volume AppleShare Information: \t" + a.volume.appleshare_info print "\tAlias BLOB: Volume Driver Name: \t" + str(a.volume.driver_name) print "\tAlias BLOB: Volume POSIX Path: \t" + str(a.volume.posix_path) print "\tAlias BLOB: Volume Disk Image Alias: \t" + a.volume.disk_image_alias print "\tAlias BLOB: Volume Creation Date: \t" + str(a.volume.creation_date) print "\tAlias BLOB: Volume Dialup Information: \t" + str(a.volume.dialup_info) print "\tAlias BLOB: Volume Network Mount Information: \t" + str(a.volume.network_mount_info) print "" print "\tAlias BLOB: Extra: \t" + str(a.extra) print "\tAlias BLOB: App Info: \t" + str(a.appinfo) print "\tAlias BLOB: Apple Share Info - Server: \t" + str(a.AppleShareInfo.server) print "\tAlias BLOB: Apple Share Info - User: \t" + str(a.AppleShareInfo.user) print "\tAlias BLOB: Apple Share Info - Zone \t" + str(a.AppleShareInfo.zone) except: pass print "----------------------------------------------------------------------------"
def test_simple_bookmark(): b = Bookmark.for_file('/Applications') assert b is not None
def build_dmg( # noqa; C901 filename, volume_name, settings_file=None, settings={}, defines={}, lookForHiDPI=True, detach_retries=5, callback=quiet_callback): options = { # Default settings 'filename': filename, 'volume_name': volume_name, 'format': 'UDBZ', 'compression_level': None, 'size': None, 'files': [], 'symlinks': {}, 'hide': [], 'hide_extensions': [], 'icon': None, 'badge_icon': None, 'background': None, 'show_status_bar': False, 'show_tab_view': False, 'show_toolbar': False, 'show_pathbar': False, 'show_sidebar': False, 'sidebar_width': 180, 'arrange_by': None, 'grid_offset': (0, 0), 'grid_spacing': 100.0, 'scroll_position': (0.0, 0.0), 'show_icon_preview': False, 'show_item_info': False, 'label_pos': 'bottom', 'text_size': 16.0, 'icon_size': 128.0, 'include_icon_view_settings': 'auto', 'include_list_view_settings': 'auto', 'list_icon_size': 16.0, 'list_text_size': 12.0, 'list_scroll_position': (0, 0), 'list_sort_by': 'name', 'list_use_relative_dates': True, 'list_calculate_all_sizes': False, 'list_columns': ('name', 'date-modified', 'size', 'kind', 'date-added'), 'list_column_widths': { 'name': 300, 'date-modified': 181, 'date-created': 181, 'date-added': 181, 'date-last-opened': 181, 'size': 97, 'kind': 115, 'label': 100, 'version': 75, 'comments': 300, }, 'list_column_sort_directions': { 'name': 'ascending', 'date-modified': 'descending', 'date-created': 'descending', 'date-added': 'descending', 'date-last-opened': 'descending', 'size': 'descending', 'kind': 'ascending', 'label': 'ascending', 'version': 'ascending', 'comments': 'ascending', }, 'window_rect': ((100, 100), (640, 280)), 'default_view': 'icon-view', 'icon_locations': {}, 'license': None, 'defines': defines } callback({'type': 'build::started'}) # Execute the settings file if settings_file: callback({ 'type': 'operation::start', 'operation': 'settings::load', }) # We now support JSON settings files using appdmg's format if settings_file.endswith('.json'): load_json(settings_file, options) else: load_settings(settings_file, options) callback({ 'type': 'operation::finished', 'operation': 'settings::load', }) # Add any overrides options.update(settings) # Set up the finder data bounds = options['window_rect'] bounds_string = '{{%s, %s}, {%s, %s}}' % (bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]) bwsp = { 'ShowStatusBar': options['show_status_bar'], 'WindowBounds': bounds_string, 'ContainerShowSidebar': False, 'PreviewPaneVisibility': False, 'SidebarWidth': options['sidebar_width'], 'ShowTabView': options['show_tab_view'], 'ShowToolbar': options['show_toolbar'], 'ShowPathbar': options['show_pathbar'], 'ShowSidebar': options['show_sidebar'] } arrange_options = { 'name': 'name', 'date-modified': 'dateModified', 'date-created': 'dateCreated', 'date-added': 'dateAdded', 'date-last-opened': 'dateLastOpened', 'size': 'size', 'kind': 'kind', 'label': 'label', } icvp = { 'viewOptionsVersion': 1, 'backgroundType': 0, 'backgroundColorRed': 1.0, 'backgroundColorGreen': 1.0, 'backgroundColorBlue': 1.0, 'gridOffsetX': float(options['grid_offset'][0]), 'gridOffsetY': float(options['grid_offset'][1]), 'gridSpacing': float(options['grid_spacing']), 'arrangeBy': str(arrange_options.get(options['arrange_by'], 'none')), 'showIconPreview': bool(options['show_icon_preview']), 'showItemInfo': bool(options['show_item_info']), 'labelOnBottom': options['label_pos'] == 'bottom', 'textSize': float(options['text_size']), 'iconSize': float(options['icon_size']), 'scrollPositionX': float(options['scroll_position'][0]), 'scrollPositionY': float(options['scroll_position'][1]) } background = options['background'] columns = { 'name': 'name', 'date-modified': 'dateModified', 'date-created': 'dateCreated', 'date-added': 'dateAdded', 'date-last-opened': 'dateLastOpened', 'size': 'size', 'kind': 'kind', 'label': 'label', 'version': 'version', 'comments': 'comments' } default_widths = { 'name': 300, 'date-modified': 181, 'date-created': 181, 'date-added': 181, 'date-last-opened': 181, 'size': 97, 'kind': 115, 'label': 100, 'version': 75, 'comments': 300, } default_sort_directions = { 'name': 'ascending', 'date-modified': 'descending', 'date-created': 'descending', 'date-added': 'descending', 'date-last-opened': 'descending', 'size': 'descending', 'kind': 'ascending', 'label': 'ascending', 'version': 'ascending', 'comments': 'ascending', } lsvp = { 'viewOptionsVersion': 1, 'sortColumn': columns.get(options['list_sort_by'], 'name'), 'textSize': float(options['list_text_size']), 'iconSize': float(options['list_icon_size']), 'showIconPreview': options['show_icon_preview'], 'scrollPositionX': options['list_scroll_position'][0], 'scrollPositionY': options['list_scroll_position'][1], 'useRelativeDates': options['list_use_relative_dates'], 'calculateAllSizes': options['list_calculate_all_sizes'], } lsvp['columns'] = {} cndx = {} for n, column in enumerate(options['list_columns']): cndx[column] = n width = options['list_column_widths'].get(column, default_widths[column]) asc = 'ascending' == options['list_column_sort_directions'].get( column, default_sort_directions[column]) lsvp['columns'][columns[column]] = { 'index': n, 'width': width, 'identifier': columns[column], 'visible': True, 'ascending': asc } n = len(options['list_columns']) for k in columns: if cndx.get(k, None) is None: cndx[k] = n width = default_widths[k] asc = 'ascending' == default_sort_directions[k] lsvp['columns'][columns[column]] = { 'index': n, 'width': width, 'identifier': columns[column], 'visible': False, 'ascending': asc } n += 1 default_view = options['default_view'] views = { 'icon-view': b'icnv', 'column-view': b'clmv', 'list-view': b'Nlsv', 'coverflow': b'Flwv' } icvl = (b'type', views.get(default_view, 'icnv')) include_icon_view_settings = default_view == 'icon-view' \ or options['include_icon_view_settings'] not in \ ('auto', 'no', 0, False, None) include_list_view_settings = default_view in ('list-view', 'coverflow') \ or options['include_list_view_settings'] not in \ ('auto', 'no', 0, False, None) filename = options['filename'] volume_name = options['volume_name'] # Construct a writeable image to start with dirname, basename = os.path.split(os.path.realpath(filename)) if not basename.endswith('.dmg'): basename += '.dmg' writableFile = tempfile.NamedTemporaryFile(dir=dirname, prefix='.temp', suffix=basename) callback({ 'type': 'operation::start', 'operation': 'size::calculate', }) total_size = options['size'] if total_size is None: # Start with a size of 128MB - this way we don't need to calculate the # size of the background image, volume icon, and .DS_Store file (and # 128 MB should be well sufficient for even the most outlandish image # sizes, like an uncompressed 5K multi-resolution TIFF) total_size = 128 * 1024 * 1024 def roundup(x, n): return x if x % n == 0 else x + n - x % n for path in options['files']: if isinstance(path, tuple): path = path[0] if not os.path.islink(path) and os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): for f in filenames: fp = os.path.join(dirpath, f) total_size += roundup(os.lstat(fp).st_size, 4096) else: total_size += roundup(os.lstat(path).st_size, 4096) for name, target in options['symlinks'].items(): total_size += 4096 total_size = str(max(total_size / 1000, 1024)) + 'K' callback({ 'type': 'operation::finished', 'operation': 'size::calculate', 'size': total_size, }) callback({ 'type': 'command::start', 'command': 'hdiutil::create', }) ret, output = hdiutil('create', '-ov', '-volname', volume_name, '-fs', 'HFS+', '-fsargs', '-c c=64,a=16,e=16', '-size', total_size, writableFile.name) callback({ 'type': 'command::finished', 'command': 'hdiutil::create', 'ret': ret, 'output': output }) if ret: raise DMGError(callback, 'Unable to create disk image') callback({ 'type': 'command::start', 'command': 'hdiutil::attach', }) # IDME was deprecated in macOS 10.15/Catalina; as a result, use of -noidme # started raising a warning. if MACOS_VERSION >= (10, 15): ret, output = hdiutil('attach', '-nobrowse', '-owners', 'off', writableFile.name) else: ret, output = hdiutil('attach', '-nobrowse', '-owners', 'off', '-noidme', writableFile.name) callback({ 'type': 'command::finished', 'command': 'hdiutil::attach', 'ret': ret, 'output': output, }) if ret: raise DMGError(callback, 'Unable to attach disk image') callback({ 'type': 'operation::start', 'operation': 'dmg::create', }) try: for info in output['system-entities']: if info.get('mount-point', None): device = info['dev-entry'] mount_point = info['mount-point'] icon = options['icon'] if badge: badge_icon = options['badge_icon'] else: badge_icon = None icon_target_path = os.path.join(mount_point, '.VolumeIcon.icns') if icon: shutil.copyfile(icon, icon_target_path) elif badge_icon: badge.badge_disk_icon(badge_icon, icon_target_path) if icon or badge_icon: subprocess.call(['/usr/bin/SetFile', '-a', 'C', mount_point]) background_bmk = None callback({ 'type': 'operation::start', 'operation': 'background::create', }) if not isinstance(background, str): pass elif colors.isAColor(background): c = colors.parseColor(background).to_rgb() icvp['backgroundType'] = 1 icvp['backgroundColorRed'] = float(c.r) icvp['backgroundColorGreen'] = float(c.g) icvp['backgroundColorBlue'] = float(c.b) else: if os.path.isfile(background): # look to see if there are HiDPI resources available if lookForHiDPI is True: name, extension = os.path.splitext( os.path.basename(background)) orderedImages = [background] imageDirectory = os.path.dirname(background) if imageDirectory == '': imageDirectory = '.' for candidateName in os.listdir(imageDirectory): hasScale = re.match( r'^(?P<name>.+)@(?P<scale>\d+)x(?P<extension>\.\w+)$', candidateName) if (hasScale and name == hasScale.group('name') and extension == hasScale.group('extension')): scale = int(hasScale.group('scale')) if len(orderedImages) < scale: orderedImages += [None] * (scale - len(orderedImages)) orderedImages[scale - 1] = os.path.join( imageDirectory, candidateName) if len(orderedImages) > 1: # compile the grouped tiff backgroundFile = tempfile.NamedTemporaryFile( suffix='.tiff') background = backgroundFile.name output = tempfile.TemporaryFile(mode='w+') try: subprocess.check_call( ['/usr/bin/tiffutil', '-cathidpicheck'] + list(filter(None, orderedImages)) + ['-out', background], stdout=output, stderr=output) except Exception as e: output.seek(0) raise ValueError( 'unable to compile combined HiDPI file "%s" got error: %s\noutput: %s' % (background, str(e), output.read())) _, kind = os.path.splitext(background) path_in_image = os.path.join(mount_point, '.background' + kind) shutil.copyfile(background, path_in_image) elif pkg_resources.resource_exists( 'dmgbuild', 'resources/' + background + '.tiff'): tiffdata = pkg_resources.resource_string( 'dmgbuild', 'resources/' + background + '.tiff') path_in_image = os.path.join(mount_point, '.background.tiff') with open(path_in_image, 'wb') as f: f.write(tiffdata) else: raise ValueError('background file "%s" not found' % background) alias = Alias.for_file(path_in_image) background_bmk = Bookmark.for_file(path_in_image) icvp['backgroundType'] = 2 icvp['backgroundImageAlias'] = alias.to_bytes() callback({ 'type': 'operation::finished', 'operation': 'background::create', }) callback({ 'type': 'operation::start', 'operation': 'files::add', 'total': len(options['files']), }) for f in options['files']: if isinstance(f, tuple): f_in_image = os.path.join(mount_point, f[1]) f = f[0] else: basename = os.path.basename(f.rstrip('/')) f_in_image = os.path.join(mount_point, basename) callback({ 'type': 'operation::start', 'operation': 'file::add', 'file': f_in_image, }) # use system ditto command to preserve code signing, etc. subprocess.call(['/usr/bin/ditto', f, f_in_image]) callback({ 'type': 'operation::finished', 'operation': 'file::add', 'file': f_in_image, }) callback({ 'type': 'operation::finished', 'operation': 'files::add', }) callback({ 'type': 'operation::start', 'operation': 'symlinks::add', 'total': len(options['symlinks']), }) for name, target in options['symlinks'].items(): name_in_image = os.path.join(mount_point, name) callback({ 'type': 'operation::start', 'operation': 'symlink::add', 'file': name_in_image, 'target': target, }) os.symlink(target, name_in_image) callback({ 'type': 'operation::finished', 'operation': 'symlink::add', 'file': name_in_image, 'target': target, }) callback({ 'type': 'operation::finished', 'operation': 'symlinks::add', }) callback({ 'type': 'operation::start', 'operation': 'extensions::hide', }) to_hide = [] for name in options['hide_extensions']: name_in_image = os.path.join(mount_point, name) to_hide.append(name_in_image) if to_hide: subprocess.call(['/usr/bin/SetFile', '-a', 'E'] + to_hide) to_hide = [] for name in options['hide']: name_in_image = os.path.join(mount_point, name) to_hide.append(name_in_image) if to_hide: subprocess.call(['/usr/bin/SetFile', '-a', 'V'] + to_hide) callback({ 'type': 'operation::finished', 'operation': 'extensions::hide', }) userfn = options.get('create_hook', None) if callable(userfn): userfn(mount_point, options) callback({ 'type': 'operation::start', 'operation': 'dsstore::create', }) image_dsstore = os.path.join(mount_point, '.DS_Store') with DSStore.open(image_dsstore, 'w+') as d: d['.']['vSrn'] = ('long', 1) d['.']['bwsp'] = bwsp if include_icon_view_settings: d['.']['icvp'] = icvp if background_bmk: d['.']['pBBk'] = background_bmk if include_list_view_settings: d['.']['lsvp'] = lsvp d['.']['icvl'] = icvl for k, v in options['icon_locations'].items(): d[k]['Iloc'] = v callback({ 'type': 'operation::finished', 'operation': 'dsstore::create', }) # Delete .Trashes, if it gets created shutil.rmtree(os.path.join(mount_point, '.Trashes'), True) except Exception: # Always try to detach hdiutil('detach', '-force', device, plist=False) raise callback({ 'type': 'operation::finished', 'operation': 'dmg::create', }) # Flush writes before attempting to detach. subprocess.check_call(('sync', '--file-system', mount_point)) for tries in range(detach_retries): callback({ 'type': 'command::start', 'command': 'hdiutil::detach', }) ret, output = hdiutil('detach', device, plist=False) callback({ 'type': 'command::finished', 'command': 'hdiutil::detach', 'ret:': ret, 'output:': output, }) if not ret: break time.sleep(1) if ret: hdiutil('detach', '-force', device, plist=False) raise DMGError(callback, 'Unable to detach device cleanly') callback({ 'type': 'command::start', 'command': 'hdiutil::resize', }) # Shrink the output to the minimum possible size ret, output = hdiutil('resize', '-quiet', '-sectors', 'min', writableFile.name, plist=False) callback({ 'type': 'command::finished', 'command': 'hdiutil::resize', 'ret': ret, 'output': output, }) if ret: raise DMGError(callback, 'Unable to shrink') callback({ 'type': 'operation::start', 'operation': 'dmg::shrink', }) key_prefix = {'UDZO': 'zlib', 'UDBZ': 'bzip2', 'ULFO': 'lzfse'} compression_level = options['compression_level'] if options['format'] in key_prefix and compression_level: compression_args = [ '-imagekey', key_prefix[options['format']] + '-level=' + str(compression_level) ] else: compression_args = [] callback({ 'type': 'command::start', 'command': 'hdiutil::convert', }) ret, output = hdiutil('convert', writableFile.name, '-format', options['format'], '-ov', '-o', filename, *compression_args) callback({ 'type': 'command::finished', 'command': 'hdiutil::convert', 'ret': ret, 'output': output, }) if ret: raise DMGError(callback, 'Unable to convert') callback({ 'type': 'operation::finished', 'operation': 'dmg::shrink', }) if options['license']: callback({ 'type': 'operation::start', 'command': 'dmg::addlicense', }) callback({ 'type': 'command::start', 'command': 'hdiutil::unflatten', }) ret, output = hdiutil('unflatten', '-quiet', filename, plist=False) callback({ 'type': 'command::finished', 'command': 'hdiutil::unflatten', 'ret': ret, 'output': output, }) if ret: raise DMGError(callback, 'Unable to unflatten to add license') callback({ 'type': 'command::start', 'command': 'hdiutil::flatten', }) licensing.add_license(filename, options['license']) ret, output = hdiutil('flatten', '-quiet', filename, plist=False) callback({ 'type': 'command::finished', 'command': 'hdiutil::flatten', 'ret': ret, 'output': output, }) if ret: raise DMGError(callback, 'Unable to flatten after adding license') callback({ 'type': 'operation::finished', 'command': 'dmg::addlicense', }) callback({'type': 'build::finished'})