lxn / win

A Windows API wrapper package for the Go Programming Language
Other
1.21k stars 315 forks source link

Shell: Add SHFileOperationW #62

Open xoviat opened 5 years ago

xoviat commented 5 years ago

Adds SHFileOperationW, which allows programs to use the windows recycle bin.

zx2c4 commented 5 years ago

Is there a corresponding lxn/walk commit for this? IIRC, this is a somewhat tricky interface to use because it requires creating double-null-terminated strings.

xoviat commented 5 years ago

lxn/walk doesn't use this. However, I have implemented an example of this in the "use syscall mechanism on windows" linked above:

func getShortPathName(path string) (string, error) {
    p, err := syscall.UTF16FromString(path)
    if err != nil {
        return "", err
    }
    b := p // GetShortPathName says we can reuse buffer
    n := uint32(len(b))
    for {
        n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
        if err != nil {
            return "", err
        }
        if n <= uint32(len(b)) {
            return syscall.UTF16ToString(b[:n]), nil
        }
        b = make([]uint16, n)
    }
}

...
    filePath, err := getShortPathName(filePath)
    if err != nil {
        return "", err
    }

    fileop := win.SHFILEOPSTRUCT{
        Hwnd:                  win.HWND(0),
        WFunc:                 win.FO_DELETE,
        PFrom:                 syscall.StringToUTF16Ptr(filePath),
        PTo:                   nil,
        FFlags:                win.FOF_ALLOWUNDO | win.FOF_NOCONFIRMATION | win.FOF_NOERRORUI | win.FOF_SILENT,
        FAnyOperationsAborted: win.BOOL(0),
        HNameMappings:         0,
        LpszProgressTitle:     syscall.StringToUTF16Ptr(""),
    }

    result := win.SHFileOperation(&fileop)
    if result != 0 {
        return "", errors.New("File operation returned code " + strconv.Itoa(int(result)))
    }
zx2c4 commented 5 years ago

Why are you passing the short filename? Also, you're not doing the double null termination like msdn says you must. Perhaps you're just relying on the same-buffer-usage of your getShortFilePathName call? And then hoping the remaining bytes after the first null aren't real paths and somewhere in memory is two nulls?

xoviat commented 5 years ago

You're correct that my initial implementation was less than optimal, but it passed the tests for random paths on my computer, so I assumed that it was good enough. You can see my revised implementation below. But regardless, the interface here is the same. Are you suggesting that changes be made to the lxn/win interface, or that an example is included in the comments? The reason that I proposed this here is that I try to share code across the entire ecosystem, and lxn/win seems to be the place to put windows apis.

func DoubleNullTerminatedUTF16PtrFromString(s string) *uint16 {
    return &(utf16.Encode([]rune(s + "\x00\x00"))[0])
}

func MoveToTrash(filePath string) (string, error) {
    if result := win.SHFileOperation(&win.SHFILEOPSTRUCT{
        Hwnd:                  win.HWND(0),
        WFunc:                 win.FO_DELETE,
        PFrom:                 DoubleNullTerminatedUTF16PtrFromString(filePath),
        PTo:                   nil,
        FFlags:                win.FOF_ALLOWUNDO | win.FOF_NOCONFIRMATION | win.FOF_NOERRORUI | win.FOF_SILENT,
        FAnyOperationsAborted: win.BOOL(0),
        HNameMappings:         0,
        LpszProgressTitle:     DoubleNullTerminatedUTF16PtrFromString(""), // Note: double-null termination not required
    }); result != 0 {
        return "", errors.New("File operation returned code " + strconv.Itoa(int(result)))
    }

    return "", nil
}
xoviat commented 5 years ago

@lxn is there a way that I could get this merged?

lxn commented 5 years ago

Please add yourself to the AUTHORS file.

xoviat commented 5 years ago

@lxn Done.

xoviat commented 4 years ago

@lxn Is there further action that you require?