6

As a "clean up" after my script's main purpose is complete, a function is called to recursively look through each folder and remove all files that end in a pre-determined set of extensions.

I during my testing, I discovered that some files with a file extension in the list of ones to delete actually throw an error: [Errno 1] Operation not permitted: '/location/of/locked/file.png. Looking at the file itself, it appears to be Locked (on mac).

  1. How would I go about removing the locked attribute (should it exist) from every file/folder using Python, then delete the file if it ends in the extension?
    Preferably this can all be done in the same function below, as it takes a long time to traverse the input directory - handling each only once is the way to go.
  2. How does this affect the script's integrity on Windows?
    I have taken care of programming it in a way that makes it compatible between the OSs but (to my knowledge) the locked attribute does not exist on Windows the way it does on mac and could cause unknown side-effects.

REMOVE_FILETYPES = ('.png', '.jpg', '.jpeg', '.pdf')

def cleaner(currentPath):
  if not os.path.isdir(currentPath):
    if currentPath.endswith(REMOVE_FILETYPES) or os.path.basename(currentPath).startswith('.'):
      try:
        os.remove(currentPath)
        print('REMOVED: \"{removed}\"'.format(removed = currentPath))
      except BaseException as e:
        print('ERROR: Could not remove: \"{failed}\"'.format(failed = str(e)))
      finally:
        return True
    return False
    
  if all([cleaner(os.path.join(currentPath, file)) for file in os.listdir(currentPath)]):
    try:
      os.rmdir(currentPath)
      print('REMOVED: \"{removed}\"'.format(removed = currentPath))
    except:
      print('ERROR: Could not remove: \"{failed}\"'.format(failed = currentPath))
    finally:
      return True
  return False

cleaner(r'/path/to/parent/dir')

I would really appreciate if somebody could show me how to integrate such functionality into the sub-routine. Cheers.


EDIT: Removed error handling as per request

def cleaner(currentPath):
    if sys.platform == 'darwin':
        os.system('chflags nouchg {}'.format(currentPath))
    if not os.path.isdir(currentPath):
        if currentPath.endswith(REMOVE_FILETYPES) or os.path.basename(currentPath).startswith('.'):
            try:
                os.remove(currentPath)
                print('REMOVED: \"{removed}\"'.format(removed=currentPath))
            except PermissionError:
                if sys.platform == 'darwin':
                    os.system('chflags nouchg {}'.format(currentPath))
                    os.remove(currentPath)
    if all([cleaner(os.path.join(currentPath, file)) for file in os.listdir(currentPath)]) and not currentPath == SOURCE_DIR:
        os.rmdir(currentPath)
        print('REMOVED: \"{removed}\"'.format(removed=currentPath))
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
ProGrammer
  • 976
  • 2
  • 10
  • 27

1 Answers1

2

You can unlock the file with the chflags command:

os.system('chflags nouchg {}'.format(filename))

(There is a function os.chflags, but the flag associated with the locked status is not a regular one, but what the os module documentation calls a "user-defined" flag, as you can see by looking at os.stat(locked_filename).st_flags.)

To solve your problem I'd add the chflags command above to a specific except: for the error you get trying to remove a locked file, along with a platform check:

try:
    os.remove(currentPath)
    print('REMOVED: \"{removed}\"'.format(removed = currentPath))
except PermissionError:
    if sys.platform == 'darwin':
        os.system('chflags nouchg {}'.format(currentPath))
        os.remove(currentPath)
    else:
        raise
except BaseException as e:
    ...
Nathan Vērzemnieks
  • 5,495
  • 1
  • 11
  • 23
  • I suppose it's possible you're getting a different exception? What exception do you see when trying to remove the files? – Nathan Vērzemnieks Feb 08 '18 at 16:27
  • The exception `"[Errno 66] Directory not empty: '/path/to/folder"` is thrown (in the last `if`) my test directory is still set up the way I mentioned it above with a locked file and the folder on the same level, and a locked file inside the folder. Being recursive, I expect the file in the folder to unlock, deeming the folder empty (as the file extension is in the list), hence deleting the folder, going up a level to the starting directory and unlocking then deleting the other locked file - essentially leaving nothing behind. Yet, none of the files unlock, its as though nothing happened. – ProGrammer Feb 08 '18 at 21:30
  • I think you must be missing some exceptions somewhere. The `finally: return True` you're doing is pretty weird: it's basically saying that a file was removed when it wasn't. And it suppresses any exceptions that happen. This makes it awfully hard to debug! I'd suggest getting rid of that, in fact, getting rid of all your `try:`/`except:` handling except for the unlocking. This should help you find the root problem. – Nathan Vērzemnieks Feb 08 '18 at 23:09
  • Just added what the code looks like now. Throws `[Errno 1] Operation not permitted: /path/to/image.png` on the locked image and breaks. Does the fact that the server that hosts this directory is on a Windows share have anything to do with it? I am lost here because it works from Terminal using: `chflags -R nouchg /PATH/TO/DIRECTORY/WITH/LOCKED/FILES/` which unlocks all at once. Could you look at the edit I made. If all else fails, is there a way to translate this into the code so that it just unlocks all before the main part of the script starts, then by the time it ends, none are looked? – ProGrammer Feb 08 '18 at 23:41
  • You certainly could do that `-R` when you do `chflags` at the beginning. As it stands, it sounds like you're getting an `IOError` rather than the `PermissionError` I got? You could just try replacing `PermissionError` with `IOError` in the `except PermissionError:` part. – Nathan Vērzemnieks Feb 08 '18 at 23:51
  • And, for future reference - the fact that you're operating on a network share is pretty significant here. Do include details like that in future questions :) – Nathan Vērzemnieks Feb 08 '18 at 23:51