/all-gists/e11b546d6ba9ebc2b604/snippet.py

https://github.com/gistable/gistable · Python · 99 lines · 89 code · 3 blank · 7 comment · 0 complexity · 73c5eb056f5979f6579810dcf400c6be MD5 · raw file

  1. # This actually uses the same API call (NetFSMountURLSync) that AppleScript uses :D
  2. # Note: As written, requires OS X 10.10+ / pyobjc 2.5.1+
  3. import objc, CoreFoundation
  4. from ctypes import c_void_p, pointer, cast
  5. # The only reason I'm doing this the XML way is because I don't have a better way (yet)
  6. # for correcting a function signature -after- it's already been imported.
  7. # The problem is the last argument is a pointer to a CFArrayRef, which works out to a
  8. # pointer to a pointer to a CFArray. pyobjc doesn't hadnle that much abstraction, so I created
  9. # a custom opaque type 'CFArrayRefRef' and manually handle the conversion to/from pointer.
  10. NetFS_bridgesupport = \
  11. """<?xml version='1.0'?>
  12. <!DOCTYPE signatures SYSTEM "file://localhost/System/Library/DTDs/BridgeSupport.dtd">
  13. <signatures version='1.0'>
  14. <depends_on path='/System/Library/Frameworks/SystemConfiguration.framework'/>
  15. <depends_on path='/System/Library/Frameworks/CoreFoundation.framework'/>
  16. <depends_on path='/System/Library/Frameworks/DiskArbitration.framework'/>
  17. <string_constant name='kNAUIOptionKey' nsstring='true' value='UIOption'/>
  18. <string_constant name='kNAUIOptionNoUI' nsstring='true' value='NoUI'/>
  19. <string_constant name='kNetFSAllowSubMountsKey' nsstring='true' value='AllowSubMounts'/>
  20. <string_constant name='kNetFSMountAtMountDirKey' nsstring='true' value='MountAtMountDir'/>
  21. <opaque name='CFArrayRefRef' type='^{CFArrayRefRef=}' />
  22. <function name='NetFSMountURLSync'>
  23. <arg type='^{__CFURL=}'/>
  24. <arg type='^{__CFURL=}'/>
  25. <arg type='^{__CFString=}'/>
  26. <arg type='^{__CFString=}'/>
  27. <arg type='^{__CFDictionary=}'/>
  28. <arg type='^{__CFDictionary=}'/>
  29. <arg type='^{CFArrayRefRef=}'/>
  30. <retval type='i'/>
  31. </function>
  32. </signatures>"""
  33. # This is fun - lets you refer dict keys like dict.keyname
  34. class attrdict(dict):
  35. __getattr__ = dict.__getitem__
  36. __setattr__ = dict.__setitem__
  37. # Create 'NetFS' framework object from XML above
  38. NetFS = attrdict()
  39. objc.parseBridgeSupport(NetFS_bridgesupport, NetFS, objc.pathForFramework('NetFS.framework'))
  40. class ArrayPair(object):
  41. def __init__(self):
  42. super(type(self), self).__init__()
  43. # Build a pointer to a null array (which OS X will replace anyways)
  44. self.cArray = pointer(c_void_p(None))
  45. # Now we cast it to our custom opaque type
  46. self.oArray = NetFS.CFArrayRefRef(c_void_p=cast(self.cArray, c_void_p))
  47. def contents(self):
  48. # Cast the pointer cArray now points to into a objc object (CFArray/NSArray here)
  49. return [str(x) for x in objc.objc_object(c_void_p=self.cArray.contents)]
  50. def mount_share(share_path):
  51. # Mounts a share at /Volumes, returns the mount point or raises an error
  52. sh_url = CoreFoundation.CFURLCreateWithString(None, share_path, None)
  53. # Set UI to reduced interaction
  54. open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI}
  55. # Allow mounting sub-directories of root shares
  56. mount_options = {NetFS.kNetFSAllowSubMountsKey: True}
  57. # Build our connected pointers for our results
  58. mountpaths = ArrayPair()
  59. # Attempt to mount!
  60. output = NetFS.NetFSMountURLSync(sh_url, None, None, None, open_options, mount_options, mountpaths.oArray)
  61. # Check if it worked
  62. if output != 0:
  63. raise Exception('Error mounting url "%s": %s' % (share_path, output))
  64. # Oh cool, it worked - return the resulting mount point path
  65. return mountpaths.contents()[0]
  66. def mount_share_at_path(share_path, mount_path):
  67. # Mounts a share at the specified path, returns the mount point or raises an error
  68. sh_url = CoreFoundation.CFURLCreateWithString(None, share_path, None)
  69. mo_url = CoreFoundation.CFURLCreateWithString(None, mount_path, None)
  70. # Set UI to reduced interaction
  71. open_options = {NetFS.kNAUIOptionKey: NetFS.kNAUIOptionNoUI}
  72. # Allow mounting sub-directories of root shares
  73. # Also specify the share should be mounted directly at (not under) mount_path
  74. mount_options = {
  75. NetFS.kNetFSAllowSubMountsKey: True,
  76. NetFS.kNetFSMountAtMountDirKey: True,
  77. }
  78. # Build our connected pointers for our results
  79. mountpaths = ArrayPair()
  80. # Attempt to mount!
  81. output = NetFS.NetFSMountURLSync(sh_url, mo_url, None, None, open_options, mount_options, mountpaths.oArray)
  82. # Check if it worked
  83. if output != 0:
  84. raise Exception('Error mounting url "%s" at path "%s": %s' % (share_path, mount_path, output))
  85. # Oh cool, it worked - return the resulting mount point path
  86. return mountpaths.contents()[0]
  87. # Example usage:
  88. mounted_at = mount_share('afp://server.local/sharename')
  89. mounted_at = mount_share_at_path('afp://server2.local/moreshare', '/custom/mount/location')