shell32: update freedesktop's recently used document list

Damjan Jovanovic damjan.jov at gmail.com
Sun Apr 25 05:11:40 CDT 2010


Changelog:
* shell32: update freedesktop's recently used document list

Damjan Jovanovic
-------------- next part --------------
diff --git a/dlls/shell32/Makefile.in b/dlls/shell32/Makefile.in
index 5e4a0c8..5b94116 100644
--- a/dlls/shell32/Makefile.in
+++ b/dlls/shell32/Makefile.in
@@ -5,7 +5,7 @@ SRCDIR    = @srcdir@
 VPATH     = @srcdir@
 MODULE    = shell32.dll
 IMPORTLIB = shell32
-IMPORTS   = uuid shlwapi comctl32 user32 gdi32 advapi32 kernel32 ntdll
+IMPORTS   = uuid shlwapi comctl32 user32 gdi32 advapi32 kernel32 ntdll wininet
 DELAYIMPORTS = ole32 oleaut32 shdocvw version
 
 C_SRCS = \
diff --git a/dlls/shell32/shellord.c b/dlls/shell32/shellord.c
index fac7a7a..1deb096 100644
--- a/dlls/shell32/shellord.c
+++ b/dlls/shell32/shellord.c
@@ -21,9 +21,15 @@
  */
 #include "config.h"
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
 #include <string.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <time.h>
 
 #define COBJMACROS
 
@@ -47,6 +53,8 @@
 #include "shlwapi.h"
 #include "commdlg.h"
 #include "commoncontrols.h"
+#include "wininet.h"
+#include "xmldom.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(shell);
 WINE_DECLARE_DEBUG_CHANNEL(pidl);
@@ -732,6 +740,483 @@ static INT SHADD_create_add_mru_data(HANDLE mruhandle, LPCSTR doc_name, LPCSTR n
     return AddMRUData(mruhandle, buffer, *len);
 }
 
+static WCHAR* get_file_contents(int fd)
+{
+    WCHAR *bufferW = NULL;
+    struct stat fileinfo;
+    if (fstat(fd, &fileinfo) == 0)
+    {
+        char *buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileinfo.st_size + 1);
+        if (buffer)
+        {
+            size_t pos = 0;
+            ssize_t ret;
+            do
+            {
+                ret = read(fd, &buffer[pos], fileinfo.st_size - pos);
+                if (ret > 0)
+                    pos += ret;
+            } while ((ret > 0 || errno == EINTR) && pos < fileinfo.st_size);
+
+            if (ret >= 0)
+            {
+                INT wsize = MultiByteToWideChar(CP_ACP, 0, buffer, -1, NULL, 0);
+                bufferW = HeapAlloc(GetProcessHeap(), 0, wsize * sizeof(WCHAR));
+                if (bufferW)
+                    MultiByteToWideChar(CP_ACP, 0, buffer, -1, bufferW, -1);
+            }
+            HeapFree(GetProcessHeap(), 0, buffer);
+        }
+    }
+    return bufferW;
+}
+
+static BOOL populate_recently_used(IXMLDOMDocument *document)
+{
+    static const WCHAR RecentFilesW[] = {'R','e','c','e','n','t','F','i','l','e','s',0};
+    HRESULT hr;
+    BSTR bstr = NULL;
+    IXMLDOMElement *element = NULL;
+    BOOLEAN succeeded = FALSE;
+
+    bstr = SysAllocString(RecentFilesW);
+    if (bstr == NULL)
+        goto done;
+    hr = IXMLDOMDocument_createElement(document, bstr, &element);
+    if (FAILED(hr))
+        goto done;
+    hr = IXMLDOMDocument_putref_documentElement(document, element);
+    if (FAILED(hr))
+        goto done;
+    succeeded = TRUE;
+
+done:
+    if (bstr)
+        SysFreeString(bstr);
+    IXMLDOMElement_Release(element);
+    return succeeded;
+}
+
+static BOOL match_recent_item(BSTR uri, WCHAR *filename)
+{
+    WCHAR *path;
+    URL_COMPONENTSW components;
+    BOOL matched = FALSE;
+
+    path = HeapAlloc(GetProcessHeap(), 0, (strlenW(uri) + 1) * sizeof(WCHAR));
+    if (path)
+    {
+        memset(&components, 0, sizeof(components));
+        components.dwStructSize = sizeof(components);
+        components.lpszUrlPath = path;
+        components.dwUrlPathLength = strlenW(uri);
+        if (InternetCrackUrlW(uri, 0, ICU_DECODE|ICU_ESCAPE, &components))
+        {
+            char *asciiPath;
+            INT asciiPathLength;
+            TRACE("decoded path is %s\n", debugstr_w(path));
+            asciiPathLength = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, 0, 0);
+            asciiPath = HeapAlloc(GetProcessHeap(), 0, asciiPathLength);
+            if (asciiPath)
+            {
+                char *unix_filename = wine_get_unix_file_name(filename);
+                if (unix_filename)
+                {
+                    matched = (strcmp(asciiPath, unix_filename) == 0);
+                    HeapFree(GetProcessHeap(), 0, unix_filename);
+                }
+                HeapFree(GetProcessHeap(), 0, asciiPath);
+            }
+        }
+        HeapFree(GetProcessHeap(), 0, path);
+   }
+   return matched;
+}
+
+static void update_recent_item(IXMLDOMNode *recentItem)
+{
+    IXMLDOMNodeList *subnodes = NULL;
+    HRESULT hr;
+
+    hr = IXMLDOMNode_get_childNodes(recentItem, &subnodes);
+    if (SUCCEEDED(hr))
+    {
+        IXMLDOMNode *node = NULL;
+        for (hr = IXMLDOMNodeList_nextNode(subnodes, &node);
+             SUCCEEDED(hr) && node != NULL;
+             hr = IXMLDOMNodeList_nextNode(subnodes, &node))
+        {
+             static const WCHAR timestampW[] = {'T','i','m','e','s','t','a','m','p',0};
+             BSTR name = NULL;
+             hr = IXMLDOMNode_get_nodeName(node, &name);
+             if (SUCCEEDED(hr))
+             {
+                 if (strcmpiW(name, timestampW) == 0)
+                 {
+                     static const WCHAR fmtW[] = {'%','d',0};
+                     WCHAR buffer[50];
+                     BSTR text;
+                     snprintfW(buffer, sizeof(buffer)/sizeof(WCHAR), fmtW, time(NULL));
+                     text = SysAllocString(buffer);
+                     if (text)
+                     {
+                         IXMLDOMNode_put_text(node, text);
+                         SysFreeString(text);
+                     }
+                 }
+                 SysFreeString(name);
+             }
+             IXMLDOMNode_Release(node);
+        }
+        IXMLDOMNodeList_Release(subnodes);
+    }
+}
+
+static BOOL check_recent_item(IXMLDOMNode *recentItem, WCHAR *filename)
+{
+    IXMLDOMNodeList *subnodes = NULL;
+    HRESULT hr;
+    BOOL found = FALSE;
+
+    hr = IXMLDOMNode_get_childNodes(recentItem, &subnodes);
+    if (SUCCEEDED(hr))
+    {
+        IXMLDOMNode *node = NULL;
+        for (hr = IXMLDOMNodeList_nextNode(subnodes, &node);
+             SUCCEEDED(hr) && node != NULL && !found;
+             hr = IXMLDOMNodeList_nextNode(subnodes, &node))
+        {
+            static const WCHAR uriW[] = {'U','R','I',0};
+            BSTR name = NULL;
+            hr = IXMLDOMNode_get_nodeName(node, &name);
+            if (SUCCEEDED(hr))
+            {
+                if (strcmpiW(name, uriW) == 0)
+                {
+                    BSTR text = NULL;
+                    hr = IXMLDOMNode_get_text(node, &text);
+                    if (SUCCEEDED(hr))
+                    {
+                        found = match_recent_item(text, filename);
+                        if (found)
+                            update_recent_item(recentItem);
+                        SysFreeString(text);
+                    }
+                }
+                SysFreeString(name);
+            }
+            IXMLDOMNode_Release(node);
+        }
+        IXMLDOMNodeList_Release(subnodes);
+    }
+    return found;
+}
+
+static BOOL add_recent_item_properties(IXMLDOMDocument *document, WCHAR *filename, IXMLDOMElement *recentItem)
+{
+    static const WCHAR UriW[] = {'U','R','I',0};
+    static const WCHAR MimeTypeW[] = {'M','i','m','e','-','T','y','p','e',0};
+    static const WCHAR TimestampW[] = {'T','i','m','e','s','t','a','m','p',0};
+    static const WCHAR ApplicationOctetStreamW[] =
+        {'a','p','p','l','i','c','a','t','i','o','n','/','o','c','t','e','t','-','s','t','r','e','a','m',0};
+    static const WCHAR fileW[] = {'f','i','l','e',0};
+    static const WCHAR fmtW[] = {'%','d',0};
+    HRESULT hr;
+    char *unix_filename = NULL;
+    INT unix_filename_len;
+    WCHAR *unix_filenameW = NULL;
+    BSTR str = NULL;
+    URL_COMPONENTSW urlComponents;
+    DWORD urlSize = 4096;
+    WCHAR *url = NULL;
+    IXMLDOMElement *uriElement = NULL;
+    IXMLDOMElement *mimeTypeElement = NULL;
+    IXMLDOMElement *timestampElement = NULL;
+    IXMLDOMNode *child = NULL;
+    WCHAR buffer[50];
+    BOOL succeeded = FALSE;
+
+    unix_filename = wine_get_unix_file_name(filename);
+    if (unix_filename == NULL)
+        goto done;
+    unix_filename_len = MultiByteToWideChar(CP_ACP, 0, unix_filename, -1, NULL, 0);
+    unix_filenameW = HeapAlloc(GetProcessHeap(), 0, (unix_filename_len + 1) * sizeof(WCHAR));
+    if (unix_filenameW == NULL)
+        goto done;
+    MultiByteToWideChar(CP_ACP, 0, unix_filename, -1, unix_filenameW, -1);
+
+    str = SysAllocString(UriW);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMDocument_createElement(document, str, &uriElement);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+    memset(&urlComponents, 0, sizeof(urlComponents));
+    urlComponents.dwStructSize = sizeof(urlComponents);
+    urlComponents.lpszScheme = (WCHAR*)fileW;
+    urlComponents.lpszUrlPath = unix_filenameW;
+    urlSize = 0;
+    InternetCreateUrlW(&urlComponents, ICU_ESCAPE, NULL, &urlSize);
+    url = HeapAlloc(GetProcessHeap(), 0, (urlSize + 1) * sizeof(WCHAR));
+    if (url == NULL)
+        goto done;
+    if (!InternetCreateUrlW(&urlComponents, ICU_ESCAPE, url, &urlSize))
+        goto done;
+    str = SysAllocString(url);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMNode_put_text((IXMLDOMNode*)uriElement, str);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+
+    str = SysAllocString(MimeTypeW);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMDocument_createElement(document, str, &mimeTypeElement);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+    str = SysAllocString(ApplicationOctetStreamW);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMNode_put_text((IXMLDOMNode*)mimeTypeElement, str);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+
+    str = SysAllocString(TimestampW);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMDocument_createElement(document, str, &timestampElement);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+    snprintfW(buffer, sizeof(buffer)/sizeof(WCHAR), fmtW, time(NULL));
+    str = SysAllocString(buffer);
+    if (str == NULL)
+        goto done;
+    hr = IXMLDOMNode_put_text((IXMLDOMNode*)timestampElement, str);
+    SysFreeString(str);
+    if (FAILED(hr))
+        goto done;
+
+    hr = IXMLDOMElement_appendChild(recentItem, (IXMLDOMNode*)uriElement, &child);
+    if (FAILED(hr))
+        goto done;
+    IXMLDOMNode_Release(child);
+    hr = IXMLDOMElement_appendChild(recentItem, (IXMLDOMNode*)mimeTypeElement, &child);
+    if (FAILED(hr))
+        goto done;
+    IXMLDOMNode_Release(child);
+    hr = IXMLDOMElement_appendChild(recentItem, (IXMLDOMNode*)timestampElement, &child);
+    if (FAILED(hr))
+        goto done;
+    IXMLDOMNode_Release(child);
+    succeeded = TRUE;
+
+done:
+    HeapFree(GetProcessHeap(), 0, unix_filename);
+    HeapFree(GetProcessHeap(), 0, unix_filenameW);
+    HeapFree(GetProcessHeap(), 0, url);
+    if (uriElement)
+        IXMLDOMElement_Release(uriElement);
+    if (mimeTypeElement)
+        IXMLDOMElement_Release(mimeTypeElement);
+    if (timestampElement)
+        IXMLDOMElement_Release(timestampElement);
+    return succeeded;
+}
+
+static void add_recent_item(IXMLDOMDocument *document, IXMLDOMNode *recentFilesNode, WCHAR *filename)
+{
+    static const WCHAR recentItemW[] = {'R','e','c','e','n','t','I','t','e','m',0};
+    BSTR recentItemBstr;
+    IXMLDOMNode *child = NULL;
+
+    recentItemBstr = SysAllocString(recentItemW);
+    if (recentItemBstr)
+    {
+        HRESULT hr;
+        IXMLDOMElement *recentItem;
+        hr = IXMLDOMDocument_createElement(document, recentItemBstr, &recentItem);
+        if (SUCCEEDED(hr))
+        {
+            if (add_recent_item_properties(document, filename, recentItem))
+            {
+                hr = IXMLDOMNode_appendChild(recentFilesNode, (IXMLDOMNode*)recentItem, &child);
+                if (SUCCEEDED(hr))
+                    IXMLDOMNode_Release(child);
+            }
+            IXMLDOMNode_Release(recentItem);
+        }
+        SysFreeString(recentItemBstr);
+    }
+}
+
+static BOOL add_to_recently_used(IXMLDOMDocument *document, WCHAR *filename)
+{
+    static const WCHAR recentfilesW[] = {'R','e','c','e','n','t','F','i','l','e','s',0};
+    IXMLDOMNode *recentFilesNode = NULL;
+    BSTR recentFilesNodeName = NULL;
+    IXMLDOMNodeList *childNodes = NULL;
+    IXMLDOMNode *recentItem = NULL;
+    BOOL found = FALSE;
+    HRESULT hr;
+    BOOL ret = FALSE;
+
+    hr = IXMLDOMDocument_get_firstChild(document, &recentFilesNode);
+    if (FAILED(hr))
+    {
+        TRACE("error getting first node\n");
+        goto done;
+    }
+    hr = IXMLDOMNode_get_nodeName(recentFilesNode, &recentFilesNodeName);
+    if (FAILED(hr))
+    {
+        TRACE("error getting first node's name\n");
+        goto done;
+    }
+    if (strcmpiW(recentFilesNodeName, recentfilesW))
+    {
+        TRACE("first node isn't RecentFiles but %s\n", debugstr_w(recentFilesNodeName));
+        goto done;
+    }
+    hr = IXMLDOMNode_get_childNodes(recentFilesNode, &childNodes);
+    if (FAILED(hr))
+    {
+        TRACE("getting RecentFiles's children failed\n");
+        goto done;
+    }
+    for (hr = IXMLDOMNodeList_nextNode(childNodes, &recentItem);
+         SUCCEEDED(hr) && recentItem != NULL && !found;
+         hr = IXMLDOMNodeList_nextNode(childNodes, &recentItem))
+    {
+        static const WCHAR recentItemW[] = {'R','e','c','e','n','t','I','t','e','m',0};
+        BSTR name = NULL;
+        hr = IXMLDOMNode_get_nodeName(recentItem, &name);
+        if (SUCCEEDED(hr))
+        {
+            if (strcmpiW(name, recentItemW) == 0)
+                found = check_recent_item(recentItem, filename);
+            SysFreeString(name);
+        }
+        IXMLDOMNode_Release(recentItem);
+    }
+    if (!found)
+        add_recent_item(document, recentFilesNode, filename);
+    ret = TRUE;
+
+done:
+    if (recentFilesNode != NULL)
+        IXMLDOMNode_Release(recentFilesNode);
+    if (recentFilesNodeName != NULL)
+        SysFreeString(recentFilesNodeName);
+    if (childNodes != NULL)
+        IXMLDOMNodeList_Release(childNodes);
+    return ret;
+}
+
+static void save_recently_used(int fd, IXMLDOMDocument *document)
+{
+    BSTR bstr = NULL;
+    HRESULT hr;
+
+    hr = IXMLDOMDocument_get_xml(document, &bstr);
+    if (SUCCEEDED(hr))
+    {
+        INT size;
+        char *xml;
+        size = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, NULL, 0, 0, 0);
+        xml = HeapAlloc(GetProcessHeap(), 0, size);
+        if (xml != NULL)
+        {
+            WideCharToMultiByte(CP_UTF8, 0, bstr, -1, xml, -1, 0, 0);
+            if (lseek(fd, 0, SEEK_SET) == 0)
+            {
+                int position = 0;
+                int ret;
+                do
+                {
+                    ret = write(fd, &xml[position], size - position);
+                    if (ret >= 0)
+                        position += ret;
+                } while ((ret >= 0 || errno == EINTR) && (position < size));
+                if (ret > 0)
+                    ftruncate(fd, size - 1);
+            }
+            HeapFree(GetProcessHeap(), 0, xml);
+        }
+        SysFreeString(bstr);
+    }
+    else
+        TRACE("error getting document to save\n");
+}
+
+/* http://standards.freedesktop.org/recent-file-spec/recent-file-spec-0.2.html */
+static void create_freedesktop_recent_document(WCHAR *filename, char *recently_used)
+{
+    int fd = -1;
+    BOOL need_unlock = FALSE;
+    WCHAR *xml = NULL;
+    IXMLDOMDocument *document = NULL;
+    HRESULT hr;
+
+    fd = open(recently_used, O_CREAT | O_RDWR, 0x600);
+    if (fd < 0)
+        goto done;
+    if (lockf(fd, F_LOCK, 0) != 0)
+        goto done;
+    need_unlock = TRUE;
+    xml = get_file_contents(fd);
+    if (xml == NULL)
+        goto done;
+
+    hr = CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
+        &IID_IXMLDOMDocument, (LPVOID*)&document);
+    if (FAILED(hr))
+        goto done;
+    if (strlenW(xml) > 0)
+    {
+        BSTR xmlBstr = SysAllocString(xml);
+        if (xmlBstr)
+        {
+            VARIANT_BOOL variantBool = VARIANT_TRUE;
+            hr = IXMLDOMDocument_loadXML(document, xmlBstr, &variantBool);
+            SysFreeString(xmlBstr);
+            if (FAILED(hr) || variantBool == VARIANT_FALSE)
+            {
+                TRACE("couldn't load recently used xml\n");
+                goto done;
+            }
+        }
+        else
+            goto done;
+    }
+    else
+    {
+        if (!populate_recently_used(document))
+            goto done;
+    }
+
+    if (!add_to_recently_used(document, filename))
+        goto done;
+
+    save_recently_used(fd, document);
+
+done:
+    if (fd >= 0)
+    {
+        if (need_unlock)
+            lockf(fd, F_ULOCK, 0);
+        close(fd);
+    }
+    HeapFree(GetProcessHeap(), 0, xml);
+    if (document)
+        IXMLDOMDocument_Release(document);
+}
+
 /*************************************************************************
  * SHAddToRecentDocs				[SHELL32.@]
  *
@@ -760,6 +1245,7 @@ void WINAPI SHAddToRecentDocs (UINT uFlags,LPCVOID pv)
 
     UINT olderrormode;
     HKEY HCUbasekey;
+    WCHAR *fullpath = NULL;
     CHAR doc_name[MAX_PATH];
     CHAR link_dir[MAX_PATH];
     CHAR new_lnk_filepath[MAX_PATH];
@@ -880,15 +1366,33 @@ void WINAPI SHAddToRecentDocs (UINT uFlags,LPCVOID pv)
     switch (uFlags)
     {
     case SHARD_PIDL:
-        SHGetPathFromIDListA(pv, doc_name);
+        fullpath = HeapAlloc(GetProcessHeap(), 0, MAX_PATH);
+        if (fullpath == NULL)
+            goto fail;
+        if (!SHGetPathFromIDListA(pv, doc_name))
+            goto fail;
+        if (!SHGetPathFromIDListW(pv, fullpath))
+            goto fail;
         break;
 
     case SHARD_PATHA:
+    {
+        int size;
         lstrcpynA(doc_name, pv, MAX_PATH);
+        size = MultiByteToWideChar(CP_ACP, 0, doc_name, -1, NULL, 0);
+        fullpath = HeapAlloc(GetProcessHeap(), 0, size * sizeof(WCHAR));
+        if (fullpath == NULL)
+            goto fail;
+        MultiByteToWideChar(CP_ACP, 0, doc_name, -1, fullpath, -1);
         break;
+    }
 
     case SHARD_PATHW:
         WideCharToMultiByte(CP_ACP, 0, pv, -1, doc_name, MAX_PATH, NULL, NULL);
+        fullpath = HeapAlloc(GetProcessHeap(), 0, (strlenW((WCHAR*)pv) + 1) * sizeof(WCHAR));
+        if (fullpath == NULL)
+            goto fail;
+        strcpyW(fullpath, (WCHAR*)pv);
         break;
 
     default:
@@ -1077,10 +1581,29 @@ void WINAPI SHAddToRecentDocs (UINT uFlags,LPCVOID pv)
 	}
     }
 
+    /* ***  JOB 3: Add a shortcut to freedesktop.org's recent documents  *** */
+
+    {
+        char *home;
+        char *recently_used;
+
+        home = getenv("HOME");
+        if (home == NULL)
+            goto fail;
+        recently_used = HeapAlloc(GetProcessHeap(), 0, strlen(home) + 1 + 14 + 1);
+        if (recently_used)
+        {
+            sprintf(recently_used, "%s/.recently-used", home);
+            create_freedesktop_recent_document(fullpath, recently_used);
+            HeapFree(GetProcessHeap(), 0, recently_used);
+        }
+    }
+
  fail:
     CoUninitialize();
 
     /* all done */
+    HeapFree(GetProcessHeap(), 0, fullpath);
     RegCloseKey(HCUbasekey);
     return;
 }


More information about the wine-patches mailing list