[Pythonmac-SIG] Make a dmg file from files/directories for Panther

Robert White kranki2 at earthlink.net
Thu Feb 12 10:37:40 EST 2004


I have developed the following script to create a dmg file from 
files/directories indicated.  The script runs only in Panther and I 
test for that.  I am enclosing it for your comments.  I am relatively 
new to MacOSX and Python so this is a good way for me to learn.  If 
this is not the proper way to get code reviewed, then I apoligize now, 
but please indicate that to me and a better way of doing it.  Thanks 
for your help.  Feel free to use the script for whatever purposes you 
like if you find it helpful.


-----Script------
#!/usr/bin/env python

"""mkdmg.py -- Copy files to a .dmg file

This copies the specified files and directories to a newly
created compressed DMG file for Mac OS X 10.3 and above.


!!! USE AT YOUR OWN RISK !!!
"""

__version__ = 1.0
__license__ = "none"




######################################################################
#                               Imports
######################################################################

import      os
import      sys
import      datetime
import      glob
import      fnmatch
import      shutil
import      string
import      copy
import      getopt
import      commands
import      optparse
import      tempfile
import      time
import      rmwCommon
if sys.platform == 'darwin':
     import          Carbon
     import          macostools




######################################################################
#                           Global Data
######################################################################


fDebug = 0
fSkip  = 0




######################################################################
#                           dmgFS Class
######################################################################

class dmgFS:
     """A class for a Sparse Disk Image (dmg) for Mac OS X.

     This is intended to create a OS X Disk Image File (with extension 
.dmg)
     containing archives of arbitrary files.
     """

     def __del__( self ):
         "Delete the object."
         if fDebug:
             print "dmgFS::del()"

         # Unmount it if necessary.
         if self.fMounted:
             self.detachSparseImage( )
             self.fMounted = 0

         # Delete it if it shows being created.
         if self.fCreated:
             self.destroySparseImage( )
             self.fCreated = 0


     def __init__( self, szArchive="mkdmg" ):
         "Initialize the object."
         if fDebug:
             print "dmgFS::init(%s)" % ( szArchive )

         # setup Temporary dmg file.
#       self.szTempDir    = tempfile.gettempdir( )
         self.szTempDir    = "$HOME/Desktop"
         self.szTempFile   = szArchive
         self.szTempPath   = os.path.join( self.szTempDir, 
(self.szTempFile + ".dmg") )
         self.szTempPath   = os.path.normpath( self.szTempPath )
         if fDebug:
             print "\tszTempPath=\"%s\"" % ( self.szTempPath )
         self.szSparsePath = os.path.join( self.szTempDir, (szArchive + 
".dmg.sparseimage") )
         self.szSparsePath = os.path.normpath( self.szSparsePath )
         if fDebug:
             print "\tszSparsePath=\"%s\"" % ( self.szSparsePath )
         oDT = datetime.datetime.utcnow( )
         szDT = oDT.isoformat( ' ' )
         szWrk = "-" + szDT[0:4] + szDT[5:7] + szDT[8:10]
         szWrk = szWrk + szDT[11:13] + szDT[14:16] + szDT[17:19]
         szWrk = szWrk + "-z.dmg"
         self.szFinalPath = os.path.join( self.szTempDir, (szArchive + 
szWrk) )
         self.szFinalPath = os.path.normpath( self.szFinalPath )
         if fDebug:
             print "\tszFinalPath=\"%s\"" % ( self.szFinalPath )
         # variables set later
         self.listPaths = [ ]
         self.fCreated = 0
         self.fMounted = 0
         self.iSizeK = 0


     def addPath( self, szPath ):
         "Add a Path to the Sparse Image."
         if fDebug:
             print "addPath(%s)" % ( szPath )
         szWork = os.path.normpath( szPath )
         szWork = os.path.expanduser( szWork )
         szWork = os.path.expandvars( szWork )
         szWork = os.path.abspath( szWork )
         if fDebug:
             print "\tabs.path=", szWork

         # Figure out the source name.
         if os.path.exists( szWork ):
             pass
         else:
             if fDebug:
                 print "\tfSkip =", fSkip
             if fSkip:
                 return
             print "ERROR - %s does not exist to be copied!" % ( szPath )
             raise ValueError
#        iSize = os.path.getsize( szWork )
         oDS = rmwCommon.DirectorySize( szWork )
         iSizeK = (oDS.size() + 1024 - 1) / 1024
         if fDebug:
             print "\tos.path.size =", iSizeK
         if os.path.isdir( szWork ):
             if szWork[-1] == "/":
                 szWork = szWork[:-2]
             if fDebug:
                 print "\tDirectory = %s" % ( szWork )
         elif os.path.isfile( szWork ):
             if fDebug:
                 print "\tFile = %s" % ( szWork )
         else:
             print "ERROR - %s is not a directory or file!" % ( szPath )
             raise ValueError
         self.iSizeK += iSizeK
         self.listPaths.append( szWork )


     def attachSparseImage( self ):
         "Attach the Sparse Image."
         if fDebug:
             print "attachSparseImage()"

         szCmdA = "hdiutil attach %s" % ( self.szSparsePath )
         szCmdB = " 2>/dev/null >/dev/null"
         szCmd = szCmdA + szCmdB
         szCmd = szCmdA
         if fDebug:
             print "\tExecuting %s..." % ( szCmd )
         tupleResult = commands.getstatusoutput( szCmd )
         if fDebug:
             print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
         if tupleResult[0] == 0:
             self.fMounted = 1
         else:
             print "ERROR - Could not mount %s." % ( self.szSparsePath )
             raise OSError
         listWork = tupleResult[1].split()
         if fDebug:
             print "\tlistWork = ", listWork
         self.szDevice = listWork[4]
         if fDebug:
             print "\tszDevice = ", self.szDevice
         self.szVolume = listWork[10]
         if fDebug:
             print "\tszVolume = ", self.szVolume
         return 0


     def compressSparseImage( self ):
         "Compress the Sparse Image."
         if fDebug:
             print "compressSparseImage()"

         szCmdA = "hdiutil convert \"%s\" -format UDZO" % ( 
self.szSparsePath )
         szCmdB = " -o \"%s\" -imagekey zlib-devel=9" % ( 
self.szFinalPath )
         szCmdC = " 2>/dev/null >/dev/null"
         szCmd = szCmdA + szCmdB + szCmdC
         szCmd = szCmdA + szCmdB
         if fDebug:
             print "\tExecuting %s..." % ( szCmd )
         tupleResult = commands.getstatusoutput( szCmd )
         if fDebug:
             print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
         if tupleResult[0] == 0:
             pass
         else:
             print "ERROR - Could not compress SparseImage!"
             raise OSError


     def copyPaths( self ):
         "Copy Paths to the Sparse Image."
         if fDebug:
             print "copyPaths( )"
         if self.fMounted:
             pass
         else:
             print "ERROR - SparseImage is not mounted!"
             raise OSError

         # Copy all the paths.
         for szPath in self.listPaths:
             self.copyPathToSparseImage( szPath )


     def copyPathToSparseImage( self, szPath ):
         "Copy a Path to the Sparse Image."
         if fDebug:
             print "copyPathToSparseImage(%s)" % ( szPath )
         if self.fMounted:
             pass
         else:
             print "ERROR - SparseImage is not mounted!"
             raise OSError

         # Figure out the source name.
         if os.path.exists( szPath ):
             pass
         else:
             print "ERROR - %s does not exist to be copied!" % ( szPath )
             raise NameError
         if fDebug:
             print "\tos.path.size =", os.path.getsize( szPath )
         if os.path.isdir( szPath ):
             if szPath[-1] == "/":
                 szPath = szPath[:-2]
             if fDebug:
                 print "\tDirectory = %s" % ( szPath )
             szSrc = szPath
             szDest = os.path.join( self.szVolume, os.path.split( szPath 
)[1] )
         elif os.path.isfile( szPath ):
             if fDebug:
                 print "\tFile = %s" % ( szPath )
             szSrc = szPath
             szDest = os.path.join( self.szVolume, os.path.split( szPath 
)[1] )
         else:
             print "ERROR - object, %s, needs to be a file or 
directory!" % (szPath)
             raise ValueError

         # Copy the data to the Sparse Image.
         szCmdA = "ditto -rsrcFork \"%s\" \"%s\"" % ( szSrc, szDest )
         szCmdB = " 2>/dev/null >/dev/null"
         szCmd = szCmdA + szCmdB
         szCmd = szCmdA
         if fDebug:
             print "\tExecuting %s..." % ( szCmd )
         tupleResult = commands.getstatusoutput( szCmd )
         if fDebug:
             print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
         if tupleResult[0] == 0:
             pass
         else:
             print "ERROR - Copy %s to SparseImage FAILED!"
             raise OSError


     def createSparseImage( self ):
         "Create the Sparse Image."
         if fDebug:
             print "createSparseImage(%d)" % ( self.iSizeK )
         if self.iSizeK > 0:
             pass
         else:
             print "ERROR - No files or directories to add to dmg!"
             raise ValueError
         iSizeM = ((2 * self.iSizeK) + 1024 - 1) / 1024

         szCmdA = "hdiutil create \"%s\"" % ( self.szTempPath )
         szCmdB = " -volname \"%s\" " % ( self.szTempFile )
         szCmdC = " -megabytes %d -type SPARSE -fs HFS+ -autostretch" % 
( iSizeM )
         szCmdD = " 2>/dev/null >/dev/null"
#        szCmd = szCmdA + szCmdB + szCmdC + szCmdD
         szCmd = szCmdA + szCmdB + szCmdC
         if fDebug:
             print "\tExecuting %s..." % ( szCmd )
         tupleResult = commands.getstatusoutput( szCmd )
         if fDebug:
             print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
         if tupleResult[0] == 0:
             self.fCreated = 1
         else:
             print "ERROR - Could not create SparseImage!"
             raise OSError


     def destroySparseImage( self ):
         "Destroy the Sparse Image."
         if fDebug:
             print "destroySparseImage()"
         if self.fCreated:
             pass
         else:
             print "ERROR - SparseImage does not exist so it cant be 
destroyed!"
             raise StandardError

         # Cant destroy it if it is mounted.
         if self.fMounted:
             print "ERROR - Tried to destroy mounted SparseImage!"
             raise StandardError

         # Remove the file.
         szCmd = "rm \"%s\"" % ( self.szSparsePath )
         if fDebug:
             print "\tExecuting %s..." % ( szCmd )
         tupleResult = commands.getstatusoutput( szCmd )
         if fDebug:
             print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
         if tupleResult[0] == 0:
             self.fCreated = 0
         else:
             print "ERROR - Could not destroy SparseImage!"
             raise OSError


     def detachSparseImage( self ):
         "Detach the Sparse Image."
         if fDebug:
             print "detachSparseImage()"
         if self.fMounted:
             pass
         else:
             print "ERROR - SparseImage is not mounted!"
             raise StandardError

         szCmdA = "hdiutil detach -debug -verbose %s" % ( self.szDevice )
         szCmdB = " 2>/dev/null >/dev/null"
         szCmd = szCmdA + szCmdB
         szCmd = szCmdA
         # Unfortunately, the previous operations against the dmg are 
somewhat
         # queued w/o a means of knowing when they are done.  So, we 
will wait
         # for successful completion of the detach for a specified 
period of
         # 10 mins (arbitrarily chosen) before aborting the detach.
         for i in range( 60 ):
             cWait = 10
             if fDebug:
                 print "\tWaiting for %d seconds..." % (cWait)
             time.sleep( cWait )
             if fDebug:
                 print "\tExecuting %s..." % ( szCmd )
             tupleResult = commands.getstatusoutput( szCmd )
             if fDebug:
                 print "\tResult = %s, %s..." % ( tupleResult[0], 
tupleResult[1] )
             if tupleResult[0] == 0:
                 break
         if tupleResult[0] == 0:
             self.fMounted = 0
         else:
             print "ERROR - Could not unmount SparseImage!"
             raise OSError


     def run( self ):
         "run creating the compressed image."
         if fDebug:
             print "run()"

         self.createSparseImage( );
         self.attachSparseImage( );
         self.copyPaths( )
         self.detachSparseImage( );
         self.compressSparseImage( );




######################################################################
#                       Command-line interface
######################################################################

def         main():
     "Command-line interface."
     global      fDebug
     global      fSkip

     szUsage = "usage: %prog [options] arg1 arg2 ..."
     oCmdPrs = optparse.OptionParser( usage=szUsage )
     oCmdPrs.add_option( "-d", "--debug", action="store_true",
                         dest="fDebug", default=False,
                         help="Set debug mode"
     )
     oCmdPrs.add_option( "-f", "--file",
                         dest="filename", metavar="FILE",
                         help="Read input file with one directory or 
file per line"
     )
     oCmdPrs.add_option( "-s", "--skip", action="store_true",
                         dest="fSkip", default=False,
                         help="Allow files or directories that don't 
exist to be skipped"
     )
     (oOptions, oArgs) = oCmdPrs.parse_args( )
     if oOptions.fDebug:
         fDebug = 1
         print "In DEBUG Mode..."
     if oOptions.fSkip:
         fSkip = 1
         print "In File/Directory skip Mode..."
     if fDebug:
         print "platform=%s" % sys.platform
         if oOptions.filename:
             print "-f name =", oOptions.filename
     if sys.platform == "darwin":
         pass
     else:
         oCmdPrs.error( "MacOSX 10.3 or above required" )

     if len(oArgs) > 0:
         pass
     elif oOptions.filename:
         pass
     else:
         oCmdPrs.error( "too few arguments" )

     try:
         oDmg = dmgFS( )
         if oOptions.filename:
             szWork = os.path.normpath( oOptions.filename )
             szWork = os.path.expanduser( szWork )
             szWork = os.path.expandvars( szWork )
             szWork = os.path.abspath( szWork )
             if fDebug:
                 print "\tabs.path=", szWork
             if os.path.exists( szWork ):
                 pass
             else:
                 print "ERROR - Could not file %s (%s)!" % 
(oOptions.filename, szWork)
                 raise StandardError
             fileInput = open( szWork, "r" )
             for szLine in fileInput:
                 szLine = szLine.strip( )
                 if szLine == "":
                     continue
                 if szLine[0] == '#':
                     continue
                 oDmg.addPath( szLine )
         for szPath in oArgs:
             oDmg.addPath( szPath )
         oDmg.run( )
     finally:
         oDmg = None




if __name__ == "__main__":
     tupleName = os.uname( )
     if tupleName[0] == "Darwin":
         iRel = tupleName[2].split('.')
         if iRel[0] > 6:
             main( )
         else:
             print "Error - mkdmg only works in Darwin 7.0 and above!"
             exit( 4 )
     else:
         print "Error - mkdmg only works in Darwin 7.0 and above!"
         exit( 4 )

-----End of Script-----
-----Sample --file input file-----
# Define the Files/Directories to be backed up.
~/.xchat2
~/bin
~/Desktop
~/Documents
~/Library/Mail
~/Library/Safari/Bookmarks.plist
#~/Movies
#~/Music
#~/Pictures
~/rmwDPorts
~/rmwHtml
~/rmwLocal
~/rmwTest
-----End of Sample input file-----




More information about the Pythonmac-SIG mailing list