frmdstryr / pywinutils

Copy move and delete files using window's built in copy dialog (with progress window).
12 stars 4 forks source link

Move operation fails when destination path does not exist #1

Open Utumno opened 8 years ago

Utumno commented 8 years ago

I am trying to adapt your code to copy/move to arbitrary filenames. If the destination filename does not exist it seems one needs to create a context and a IFileSystemBindData instance that contains the filename to avoid a file not exits error (see http://stackoverflow.com/q/11902068/281545, http://stackoverflow.com/questions/18576103/shparsedisplayname-when-path-doesnt-exists) - but for the life of me I can't see how. This is my version of your code:


try:
    import pythoncom
    IFileOperation = shell.IID_IFileOperation # AttributeError on pywin 218

    def _initialize_com(flags):
        pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation, None,
                                         pythoncom.CLSCTX_INPROC_SERVER,
                                         IFileOperation)
        pfo.SetOperationFlags(flags)
        return pfo

    def copy_or_move(src, dst, do_copy, flags):
        """@see http://msdn.microsoft.com/en-us/library/bb775799(v=vs.85).aspx
        for flags available"""
        # Do not confirm the creation of a new folder if the operation requires one to be created
        flags |= shellcon.FOF_NOCONFIRMMKDIR
        pfo = _initialize_com(flags)
        oper = pfo.CopyItem if do_copy else pfo.MoveItem
        # Set the destination paths. If they do not exist BLOWS
        for s, d in zip(src, dst):
            d = shell.SHCreateItemFromParsingName(d, ctx,
                                                  shell.IID_IShellItem) # BLOWS HERE
            s = shell.SHCreateItemFromParsingName(s, None,
                                                  shell.IID_IShellItem)
            oper(s, d) # Schedule an operation to be performed
        return _execute(pfo)

    def copy(src, dst, flags):
        return copy_or_move(src, dst, do_copy=True, flags=flags)

    def move(src, dst, flags):
        return copy_or_move(src, dst, do_copy=False, flags=flags)

    def delete(path, flags):
        pfo = _initialize_com(flags)
        for f in path:
            item = shell.SHCreateItemFromParsingName(f, None,
                                                     shell.IID_IShellItem)
            pfo.DeleteItem(item) # Schedule an operation to be performed
        return _execute(pfo)

    def _execute(pfo):
        # @see http://msdn.microsoft.com/en-us/library/bb775780(v=vs.85).aspx
        success = pfo.PerformOperations()
        # @see sdn.microsoft.com/en-us/library/bb775769(v=vs.85).aspx
        aborted = pfo.GetAnyOperationsAborted()
        return success, aborted

except (ImportError, AttributeError):
    pythoncom = None

The error:

File "bash\env.py", line 128, in copy_or_move
    shell.IID_IShellItem)
pywintypes.com_error: (-2147024894, 'The system cannot find the file specified.', None, None)

I can't find a IFileSystemBindData in pythoncom so this is probably an impasse ? Just posting this as you may know better, and it would be a nice extension of your API

pywin 220 from sourceforge includes shell.CLSID_FileOperation constant so you may want to update your readme

Thanks !

frmdstryr commented 8 years ago

Can you please share how you're using it? Comments say Does NOT create root destination folder if it doesn't exist. Also you cannot use the move method to rename files.

Utumno commented 8 years ago

Yep, I was trying to imitate SHFileOperation where one can pass an array of filenames for source and an array of filenames for destinations and have the files moved to the new filenames. That would be an interesting call to add to the API ;)

Meanwhile I realized that not only I can't pass an array of destinations (yak !) but I must pass a directory name and moreover that directory should exist. Investigating that I run across to the post I linked where the guy reports:

I got IFileOperation to copy a file into the new and cleverly-named directory D:\Some\Path\That\Did\Not\Previously\Exist, and it created all seven directories along the way

He got it by using the IFileSystemBindData he describes.

Trying to implement this I hit a wall - IFileSystemBindData is not pywin32 ? Not sure - I asked here: https://mail.python.org/pipermail/python-win32/2016-May/013710.html

Bear in mind that ctypes and pywin32 are not my cup of tea (mentioning ctypes cause of https://justicecode.wordpress.com/2008/08/13/shfileoperationw-in-python-hint-ctypes/ that may be relevant here (use ctypes instead of pywin32)). SO the above procedure was not as smooth as described - and totally frustrating.

My real issue remains the first sentence:

I am trying to adapt your code to copy/move to arbitrary filenames

to mimic SHFileOperation - I currently use it as in: https://github.com/wrye-bash/wrye-bash/blob/66d7b4d695289f6dc29142f94a6004494e3306bd/Mopy/bash/env.py#L408-L434 ( disclaimer: I refactored but didn't write that code)

frmdstryr commented 8 years ago

Have you tried RenameItem?

https://msdn.microsoft.com/en-us/library/windows/desktop/bb775795(v=vs.85).aspx

frmdstryr commented 8 years ago

Maybe a combination of move and rename would work

Utumno commented 8 years ago

Yes I had but I could not make it work. Probably some mistake of mine in passing the parameters ? The rename succeeds but then an error is thrown ( error 0x80070057 "the parameter is incorrect" ) is thrown: here is the commit I try to use RenameItems and get the error:

https://github.com/wrye-bash/wrye-bash/commit/722eee233e5993a7c1ad9d376240b8128e5a6bae https://github.com/wrye-bash/wrye-bash/commit/c064de261306504f98c92e10d988ae0b72449935

Since I could not make rename work for a simple case I did not try further - but still renaming an item and then moving or moving and then renaming have both issues - should be atomic.

Here is the (experimental) branch I used - just pushed for the sake of illustration: https://github.com/wrye-bash/wrye-bash/compare/dev...IFileOperation