File: mergeall-products/unzipped/docetc/miscnotes/mac-appledouble-resourceforks-live.txt

[Feb-2017]

Summary=> Demo AppleDouble ._* resource-fork files under Python's POSIX toolset.
          In all cases, these files appear for data files stored on non-Mac 
          filesystems only; the Mac's own filesystems store resources natively.
          
          Finder automatically creates resource-fork files on non-Mac drives, 
          and binds them to the data file logically - operations on a data file 
          are applied to the resource fork too (e.g., deletes, moves).

          Python's POSIX-based toolset is more mixed: open() does not silently add
          a resource fork for new data files, and os.remove() for a resource file 
          doesn't delete the data file.  When run on the Mac, these behaviors are 
          crucial for mergeall's -skipcruft (which copies data files only), and 
          the nuke-cruft-files script (which deletes resource files only).  

          On the other hand, Python's os.remove() and os.rename() for a data file 
          on a non-Mac drive are automatically applied by the Mac's system libraries
          to the resource fork file too (just like Finder).  This can potentially
          lead to errors in some programs that process a folder's files individually.

          For more background:
              https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats
	      https://en.wikipedia.org/wiki/Resource_fork



#-------------------------------------------------------------------------------------
# Preliminaries (assumed by all tests ahead)
#-------------------------------------------------------------------------------------

# Copy file off Mac drive - via main data fork filename
>>> f = open('/MY-STUFF/Sheets/cashflow.xlsb', 'rb')
>>> b = f.read()
>>> f.close()

# There is no resource fork file on this Mac drive
>>> [x for x in os.listdir('/MY-STUFF/Sheets') if 'cashflow.xlsb' in x]
['cashflow.xlsb']
>>> import glob
>>> glob.glob('/MY-STUFF/Sheets/*cashflow.*')
['/MY-STUFF/Sheets/cashflow.xlsb', '/MY-STUFF/Sheets/endyr02-cashflow.XLS']
>>> glob.glob('/MY-STUFF/Sheets/.*cashflow.*')  # see glob note ahead
[]


#-------------------------------------------------------------------------------------
# MAC DRIVE (HFS+)
#------------------------------------------------------------------------------------- 

Summary=> On native Mac filesystem drives, no ._* AppleDouble files are 
          created or special-cased (these pertain to non-Mac drives only).


>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store'] 
>>> f = open('/Users/blue/Desktop/temp/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()

  # Python POSIX write creates data file only on Mac HFS+ drive

>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store', 'cashflow.xlsb']
>>> os.path.getmtime('/Users/blue/Desktop/temp/test/cashflow.xlsb')
1480791888.0
 
  # Open to view only: creates Excel temp, but no ._* resource fork on HFS

>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store', 'cashflow.xlsb', '~$cashflow.xlsb']
>>> os.path.getmtime('/Users/blue/Desktop/temp/test/cashflow.xlsb')
1480791888.0

  # Close, don't save: removes Excel temp 

>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store', 'cashflow.xlsb']
>>> os.path.getmtime('/Users/blue/Desktop/temp/test/cashflow.xlsb')
1480791888.0

  # Open and save: still no distinct resource fork file on HFS+ 

>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store', 'cashflow.xlsb']
>>> os.path.getmtime('/Users/blue/Desktop/temp/test/cashflow.xlsb')
1480792056.0
 
  # Delete .xlsb file: just a single file here

>>> os.listdir('/Users/blue/Desktop/temp/test')
['.DS_Store']


#-------------------------------------------------------------------------------------
# WINDOWS DRIVE (FAT32)
#-------------------------------------------------------------------------------------

Summary=> AppleDouble ._* files are silently created on non-Mac drives 
          when used by Mac apps, even if not copied over from Mac sources.


  # Create data file only, from Python 

>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]
>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()

  # Python POSIX write creates data-fork file only on FAT.
  # *EXAMPLE*: This is what happens for mergeall's -skipcruft.

>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb']
>>> os.path.getmtime('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
1480791672.0
 
  # Open to view in Excel: suffices to create a ._* resource fork on FAT (!).
  # The 1 file has become 4: data + temp, and ._* AppleDoubles of each.

>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '~$cashflow.xlsb', '._~$cashflow.xlsb', '._cashflow.xlsb']
>>> os.path.getmtime('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
1480791672.0

  # Close, don't save: removes Excel ~* temp and its double, but not ._* resource fork.
  # Note that Finder never shows the ._* AppleDouble resource fork files (despite a
  # defaults setting to enable this), but they are always returned by os.listdir().

>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '._cashflow.xlsb']
>>> os.path.getmtime('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
1480791672.0

  # Open and save: but a write is not needed to create the resource ._* (!)

>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '._cashflow.xlsb']
>>> os.path.getmtime('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
1480791788.0

  # Delete .xlsb file in Finder: automatically removes the truly-hidden ._* too (!)

>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]


#-------------------------------------------------------------------------------------
# WARNING: glob() doesn't return .*, but os.listdir() does
#-------------------------------------------------------------------------------------

Summary=> Be cautious with glob() results on Unix; they differ from os.listdir().

# After write file, open in Excel
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '._cashflow.xlsb']
>>> 
>>> import glob
>>> glob.glob('/Volumes/KINGSTONFAT/test/*')
['/Volumes/KINGSTONFAT/test/cashflow.xlsb']
>>>
>>> glob.glob('/Volumes/KINGSTONFAT/test/*cashflow.*')
['/Volumes/KINGSTONFAT/test/cashflow.xlsb']
>>>
>>> glob.glob('/Volumes/KINGSTONFAT/test/.*cashflow.*')
['/Volumes/KINGSTONFAT/test/._cashflow.xlsb']
>>> 
>>> [x for x in os.listdir('/Volumes/KINGSTONFAT/test') if 'cashflow.xlsb' in x]
['cashflow.xlsb', '._cashflow.xlsb']


#-------------------------------------------------------------------------------------
# WINDOWS DRIVE: what happens when files are deleted from Python instead of Finder?
#-------------------------------------------------------------------------------------

Summary=> Mac POSIX libs auto-delete ._* files with data file just like Finder,
          but not vice-versa.  This may cause some programs failures.


  # Create and remove data file only: normal

>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]
>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb']
>>> os.remove('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]


  # Delete *data file* after resource fork created by opening in Excel

>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb']

# open in Excel, close but don't save file
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '._cashflow.xlsb']

# automatically removes reource fork too (!)
>>> os.remove('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]


  # Delete *resource file* after it is created by Excel open
 
>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close() 
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb']

# open and close in Excel (save or not)
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb', '._cashflow.xlsb']

# does not remove data file if resource file removed (!)  
# *EXAMPLE*: This is what happens in nuke-cruft-files.py

>>> os.remove('/Volumes/KINGSTONFAT/test/._cashflow.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
['cashflow.xlsb']

# clean up data file: the ._* special-casing seems too implicit and error-prone
>>> os.remove('/Volumes/KINGSTONFAT/test/cashflow.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]


[Why it can matter: if a script runs through an os.listdir() result 
to delete files on a non-Mac filesystem drive, some ._*  resource 
fork files may no longer be present when reached - and will fail.]



#-------------------------------------------------------------------------------------
# WINDOWS DRIVE: renames (moves) bring the ._* along too (and even rename them!)
#-------------------------------------------------------------------------------------

Summary=> Mac POSIX libs auto-move ._* files with data file just like Finder.
          This is true even if the data file's name is changed in the process.


  # Moves bring along AppleDouble automatically

>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> os.listdir('/Volumes/KINGSTONFAT/test')      # after initial Python write
['cashflow.xlsb']
>>> os.listdir('/Volumes/KINGSTONFAT/test')      # after open in Excel
['cashflow.xlsb', '._cashflow.xlsb']
>>> os.listdir('/Volumes/KINGSTONFAT/test2')     # target dir is empty
[]

>>> os.rename('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 
              '/Volumes/KINGSTONFAT/test2/cashflow.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]
>>> os.listdir('/Volumes/KINGSTONFAT/test2')     # brings AppleDouble along...
['cashflow.xlsb', '._cashflow.xlsb']


  # Ditto, even if moving to a new name (!)
 
>>> f = open('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> os.listdir('/Volumes/KINGSTONFAT/test')      # post Python write
['cashflow.xlsb'] 
>>> os.listdir('/Volumes/KINGSTONFAT/test')      # post Excel open
['cashflow.xlsb', '._cashflow.xlsb']
>>> os.listdir('/Volumes/KINGSTONFAT/test2')
[]

>>> os.rename('/Volumes/KINGSTONFAT/test/cashflow.xlsb', 
              '/Volumes/KINGSTONFAT/test2/SPAM.xlsb')
>>> os.listdir('/Volumes/KINGSTONFAT/test')
[]
>>> os.listdir('/Volumes/KINGSTONFAT/test2')     # buy one, get one free?
['SPAM.xlsb', '._SPAM.xlsb']



#-------------------------------------------------------------------------------------
# AND Windows and Linux may or may not handle ._* file the same way... (test me)
#-------------------------------------------------------------------------------------



#-------------------------------------------------------------------------------------
# RESOURCE FORKS ROUND-TRIPPED to non-Mac drives (use dot_clean if not merged auto)
#-------------------------------------------------------------------------------------

Summary=> Some programs may copy AppleDouble resource-fork files to Mac drives,
          but not merge them to the main data fork file (including mergeall).
          If this happens, Mac's dot_clean can merge the two in an entire tree.
          Note that mergeall doesn't make "._*" files: this is only possible if 
          they have been added to an archive by Mac apps/Finder in other ways
          (e.g., merging from a non-Mac drive used as mergeall's FROM).

>>> macdrive = '/Users/blue/Desktop/temp/test/'      # HFS+ (Mac Extended)
>>> fatdrive = '/Volumes/KINGSTONFAT/test/'          # FAT32 (flashdrive)

  # Copy a file to a macdrive folder (and open in excel there or not)

>>> f = open('/MY-STUFF/Sheets/cashflow.xlsb', 'rb')
>>> b = f.read()
>>> f.close()
>>> 
>>> f = open(macdrive + 'cf.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> 
>>> os.listdir(macdrive)
['.DS_Store', 'cf.xlsb']
>>> os.listdir(fatdrive)
[]

  # Copy from macdrive to fatdrive in Finder

>>> os.listdir(fatdrive)
['cf.xlsb']
 
  # Open/view on fatdrive in Excel: makes AppleDouble resource fork 

>>> os.listdir(fatdrive)
['cf.xlsb', '._cf.xlsb']

  # Remove from fatdrive in Finder or Python: both files gone! (see above) 

>>> os.remove(fatdrive + 'cf.xlsb')
>>> os.listdir(fatdrive)
[]
 
  # Copy from macdrive to fatdrive in python
 
>>> os.listdir(macdrive)
['.DS_Store', 'cf.xlsb']
>>> os.listdir(fatdrive)
[]
>>> f = open(macdrive + 'cf.xlsb', 'rb')
>>> b = f.read()
>>> f.close()
>>> 
>>> f = open(fatdrive + 'cf.xlsb', 'wb')
>>> f.write(b)
397387
>>> f.close()
>>> 
>>> os.listdir(fatdrive)
['cf.xlsb']
>>> os.listdir(macdrive)
['.DS_Store', 'cf.xlsb']

  # Open on fatdrive in Excel: makes "._*"
 
>>> os.listdir(fatdrive)
['cf.xlsb', '._cf.xlsb']
>>> os.listdir(macdrive)
['.DS_Store', 'cf.xlsb']
 
  # Delete from macdrive in Finder: one file only
 
>>> os.listdir(fatdrive)
['cf.xlsb', '._cf.xlsb']
>>> os.listdir(macdrive)
['.DS_Store']
 
  # Copy from fatdrive to macdrive in Finder
 
>>> os.listdir(fatdrive)
['cf.xlsb', '._cf.xlsb']
>>> os.listdir(macdrive)        # copies resource too (presumably!)
['.DS_Store', 'cf.xlsb']

  # Delete from macdrive in Python: one file only

>>> os.remove(macdrive + 'cf.xlsb')
>>> os.listdir(fatdrive)
['cf.xlsb', '._cf.xlsb']
>>> os.listdir(macdrive)
['.DS_Store']

  # Copy to macdrive in python (EXAMPLE: what mergeall does)

>>> for file in os.listdir(fatdrive):
...     f = open(macdrive + file, 'wb')
...     f.write(open(fatdrive + file, 'rb').read())
...     f.close()
...     print(file)
... 
397387
cf.xlsb
4096
._cf.xlsb
>>>
>>> os.listdir(fatdrive)       # <= BOTH files copied over, not merged!
['cf.xlsb', '._cf.xlsb']
>>> os.listdir(macdrive)
['._cf.xlsb', '.DS_Store', 'cf.xlsb'] 
>>> ^D

  # Merge from the Terminal shell via Mac's dot_merge

~$ ls -a /Volumes/KINGSTONFAT/test
.		..		._cf.xlsb	cf.xlsb
~$ ls -a /Users/blue/Desktop/temp/test
.		..		.DS_Store	._cf.xlsb	cf.xlsb

~$ dot_clean -m /Users/blue/Desktop/temp/test

  # Still split on fatdrive, but merged on macdrive

~$ ls -a /Volumes/KINGSTONFAT/test
.		..		._cf.xlsb	cf.xlsb
~$ ls -a /Users/blue/Desktop/temp/test
.		..		.DS_Store	cf.xlsb

  # CAUTION: will run on fatdrive too, but resource is silently dropped
 
~$ dot_clean -m /Volumes/KINGSTONFAT/test         # <= Don't do this
~$ ls -a /Volumes/KINGSTONFAT/test
.	..	cf.xlsb
~$ ls -a /Users/blue/Desktop/temp/test
.		..		.DS_Store	cf.xlsb


#
# Bonus: how to inspect a resource fork from the shell..
#
~$ ls -l /MY-STUFF/Sheets/cashflow.xlsb
-rw-r--r--@ 1 blue  staff  397387 Dec  2 10:02 /MY-STUFF/Sheets/cashflow.xlsb
~$ ls -l /MY-STUFF/Sheets/cashflow.xlsb/..namedfork/rsrc
-rw-r--r--  1 blue  staff  0 Dec  2 10:02 /MY-STUFF/Sheets/cashflow.xlsb/..namedfork/rsrc
~$ cat /MY-STUFF/Sheets/cashflow.xlsb/..namedfork/rsrc
~$

[end]



[Home page] Books Code Blog Python Author Train Find ©M.Lutz