Switch to side-by-side view

--- a/src/utils/pathut.cpp
+++ b/src/utils/pathut.cpp
@@ -19,17 +19,22 @@
 #include "autoconfig.h"
 
 #include <stdio.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
+#include "safefcntl.h"
+#include "safeunistd.h"
+#include "dirent.h"
+#include "cstr.h"
+#ifdef _WIN32
+#include "safewindows.h"
+#else
 #include <sys/param.h>
 #include <pwd.h>
+#include <sys/file.h>
+#endif
 #include <math.h>
 #include <errno.h>
 #include <sys/types.h>
-#include <sys/file.h>
 #include "safesysstat.h"
-#include <glob.h>
+#include "ptmutex.h"
 
 // Let's include all files where statfs can be defined and hope for no
 // conflict...
@@ -52,15 +57,104 @@
 #include <sstream>
 #include <stack>
 #include <set>
-using namespace std;
+#include <vector>
 
 #include "pathut.h"
 #include "transcode.h"
 #include "wipedir.h"
 #include "md5ut.h"
 
+using namespace std;
+
+#ifdef _WIN32
+/// Convert \ separators to /
+void path_slashize(string& s)
+{
+    for (string::size_type i = 0; i < s.size(); i++) {
+        if (s[i] == '\\')
+            s[i] = '/';
+    }
+}
+static bool path_strlookslikedrive(const string& s)
+{
+    return s.size() == 2 && isalpha(s[0]) && s[1] == ':';
+}
+
+static bool path_hasdrive(const string& s)
+{
+    if (s.size() >= 2 && isalpha(s[0]) && s[1] == ':')
+        return true;
+    return false;
+}
+static bool path_isdriveabs(const string& s)
+{
+    if (s.size() >= 3 && isalpha(s[0]) && s[1] == ':' && s[2] == '/')
+        return true;
+    return false;
+}
+
+#include <Shlwapi.h>
+#pragma comment(lib, "shlwapi.lib")
+
+string path_tchartoutf8(TCHAR *text)
+{
+#ifdef UNICODE
+    // Simple C
+    // const size_t size = ( wcslen(text) + 1 ) * sizeof(wchar_t);
+    // wcstombs(&buffer[0], text, size);
+    // std::vector<char> buffer(size);
+    // Or:
+    // Windows API
+    std::vector<char> buffer;
+    int size = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL);
+    if (size > 0) {
+        buffer.resize(size);
+        WideCharToMultiByte(CP_UTF8, 0, text, -1, 
+                            &buffer[0], int(buffer.size()), NULL, NULL);
+    } else {
+        return string();
+    }
+    return string(&buffer[0]);
+#else
+    return text;
+#endif
+}
+
+string path_thisexecpath()
+{
+    TCHAR text[MAX_PATH];
+    DWORD length = GetModuleFileName(NULL, text, MAX_PATH);
+#ifdef NTDDI_WIN8_future
+    PathCchRemoveFileSpec(text, MAX_PATH);
+#else
+    PathRemoveFileSpec(text);
+#endif
+    string path = path_tchartoutf8(text);
+    if (path.empty())
+        path = "c:/";
+
+    return path;
+}
+
+string path_wingettempfilename(TCHAR *pref)
+{
+    TCHAR buf[(MAX_PATH +1)*sizeof(TCHAR)];
+    TCHAR dbuf[(MAX_PATH +1)*sizeof(TCHAR)];
+    GetTempPath(MAX_PATH+1, dbuf);
+    GetTempFileName(dbuf, pref, 0, buf);
+    // Windows will have created a temp file, we delete it.
+    string filename = path_tchartoutf8(buf);
+    unlink(filename.c_str());
+    return filename;
+}
+#endif
+
+
 bool fsocc(const string &path, int *pc, long long *blocks)
 {
+#ifdef _WIN32
+	return false;
+#else
 #ifdef sun
     struct statvfs buf;
     if (statvfs(path.c_str(), &buf) != 0) {
@@ -94,6 +188,7 @@
 	}
     }
     return true;
+#endif
 }
 
 const string& tmplocation()
@@ -103,15 +198,61 @@
         const char *tmpdir = getenv("RECOLL_TMPDIR");
         if (tmpdir == 0) 
             tmpdir = getenv("TMPDIR");
-        if (tmpdir == 0)
-            tmpdir = "/tmp";
-        stmpdir = string(tmpdir);
-    }
+        if (tmpdir == 0) 
+            tmpdir = getenv("TMP");
+        if (tmpdir == 0) 
+            tmpdir = getenv("TEMP");
+        if (tmpdir == 0) {
+#ifdef _WIN32
+            TCHAR bufw[(MAX_PATH+1)*sizeof(TCHAR)];
+            GetTempPath(MAX_PATH+1, bufw);
+            stmpdir = path_tchartoutf8(bufw);
+#else
+            stmpdir = "/tmp";
+#endif
+        } else {
+            stmpdir = tmpdir;
+        }
+        stmpdir = path_canon(stmpdir);
+    }
+
     return stmpdir;
 }
 
+// Location for sample config, filters, etc. (e.g. /usr/share/recoll/)
+const string& path_sharedatadir()
+{
+    static string datadir;
+    if (datadir.empty()) {
+#ifdef _WIN32
+        datadir = path_cat(path_thisexecpath(), "Share");
+#else
+        const char *cdatadir = getenv("RECOLL_DATADIR");
+        if (cdatadir == 0) {
+            // If not in environment, use the compiled-in constant. 
+            datadir = RECOLL_DATADIR;
+        } else {
+            datadir = cdatadir;
+        }
+#endif
+    }
+    return datadir;
+}
+
+string path_PATHsep()
+{
+    static const string w(";");
+    static const string u(":");
+#ifdef _WIN32
+    return w;
+#else
+    return u;
+#endif
+}
+
 bool maketmpdir(string& tdir, string& reason)
 {
+#ifndef _WIN32
     tdir = path_cat(tmplocation(), "rcltmpXXXXXX");
 
     char *cp = strdup(tdir.c_str());
@@ -120,6 +261,14 @@
 	tdir.erase();
 	return false;
     }
+
+    // There is a race condition between name computation and
+    // mkdir. try to make sure that we at least don't shoot ourselves
+    // in the foot
+#if !defined(HAVE_MKDTEMP) || defined(_WIN32)
+    static PTMutexInit mlock;
+    PTMutexLocker lock(mlock);
+#endif
 
     if (!
 #ifdef HAVE_MKDTEMP
@@ -136,8 +285,19 @@
     }	
     tdir = cp;
     free(cp);
-
-#ifndef HAVE_MKDTEMP
+#else // _WIN32
+    // There is a race condition between name computation and
+    // mkdir. try to make sure that we at least don't shoot ourselves
+    // in the foot
+    static PTMutexInit mlock;
+    PTMutexLocker lock(mlock);
+    tdir = path_wingettempfilename(TEXT("rcltmp"));
+#endif
+
+    // At this point the directory does not exist yet except if we used
+    // mkdtemp
+
+#if !defined(HAVE_MKDTEMP) || defined(_WIN32)
     if (mkdir(tdir.c_str(), 0700) < 0) {
 	reason = string("maketmpdir: mkdir ") + tdir + " failed";
 	tdir.erase();
@@ -151,6 +311,14 @@
 TempFileInternal::TempFileInternal(const string& suffix)
     : m_noremove(false)
 {
+    // Because we need a specific suffix, can't use mkstemp
+    // well. There is a race condition between name computation and
+    // file creation. try to make sure that we at least don't shoot
+    // our own selves in the foot. maybe we'll use mkstemps one day.
+    static PTMutexInit mlock;
+    PTMutexLocker lock(mlock);
+
+#ifndef _WIN32
     string filename = path_cat(tmplocation(), "rcltmpfXXXXXX");
     char *cp = strdup(filename.c_str());
     if (!cp) {
@@ -158,8 +326,8 @@
 	return;
     }
 
-    // Yes using mkstemp this way is awful (bot the suffix adding and
-    // using mkstemp() just to avoid the warnings)
+    // Using mkstemp this way is awful (bot the suffix adding and
+    // using mkstemp() instead of mktemp just to avoid the warnings)
     int fd;
     if ((fd = mkstemp(cp)) < 0) {
 	free(cp);
@@ -168,9 +336,11 @@
     }
     close(fd);
     unlink(cp);
-    
     filename = cp;
     free(cp);
+#else
+    string filename = path_wingettempfilename(TEXT("recoll"));
+#endif
 
     m_filename = filename + suffix;
     if (close(open(m_filename.c_str(), O_CREAT|O_EXCL, 0600)) != 0) {
@@ -214,29 +384,39 @@
     return true;
 }
 
-void path_catslash(string &s) {
+void path_catslash(string &s)
+{
+#ifdef WIN32
+    path_slashize(s);
+#endif
     if (s.empty() || s[s.length() - 1] != '/')
 	s += '/';
 }
 
-string path_cat(const string &s1, const string &s2) {
+string path_cat(const string &s1, const string &s2)
+{
     string res = s1;
     path_catslash(res);
     res +=  s2;
     return res;
 }
 
-string path_getfather(const string &s) {
+string path_getfather(const string &s)
+{
     string father = s;
+#ifdef WIN32
+    path_slashize(father);
+#endif
 
     // ??
     if (father.empty())
 	return "./";
 
+    if (path_isroot(father))
+        return father;
+    
     if (father[father.length() - 1] == '/') {
-	// Input ends with /. Strip it, handle special case for root
-	if (father.length() == 1)
-	    return father;
+	// Input ends with /. Strip it, root special case was tested above
 	father.erase(father.length()-1);
     }
 
@@ -249,8 +429,12 @@
     return father;
 }
 
-string path_getsimple(const string &s) {
+string path_getsimple(const string &s)
+{
     string simple = s;
+#ifdef WIN32
+    path_slashize(simple);
+#endif
 
     if (simple.empty())
 	return simple;
@@ -285,6 +469,28 @@
 
 string path_home()
 {
+#ifdef _WIN32
+    string dir;
+    const char *cp = getenv("USERPROFILE");
+    if (cp != 0) {
+        dir = cp;
+    }
+    if (dir.empty()) {
+        cp = getenv("HOMEDRIVE");
+        if (cp != 0) {
+            const char *cp1 = getenv("HOMEPATH");
+            if (cp1 != 0) {
+                dir = string(cp) + string(cp1);
+            }
+        }
+    }
+    if (dir.empty()) {
+        dir = "C:\\";
+    }
+    dir = path_canon(dir);
+    path_catslash(dir);
+    return dir;
+#else
     uid_t uid = getuid();
 
     struct passwd *entry = getpwuid(uid);
@@ -299,6 +505,26 @@
     string homedir = entry->pw_dir;
     path_catslash(homedir);
     return homedir;
+#endif
+}
+
+// The default place to store the default config and other stuff (e.g webqueue)
+string path_homedata()
+{
+#ifdef _WIN32
+    const char *cp = getenv("LOCALAPPDATA");
+    string dir;
+    if (cp != 0) {
+        dir = path_canon(cp);
+    }
+    if (dir.empty()) {
+        dir = path_cat(path_home(), "AppData/Local/");
+    }
+    return dir;
+#else
+    // We should use an xdg-conforming location, but, history...
+    return path_home();
+#endif
 }
 
 string path_tildexpand(const string &s) 
@@ -306,31 +532,67 @@
     if (s.empty() || s[0] != '~')
 	return s;
     string o = s;
+#ifdef WIN32
+    path_slashize(o);
+#endif
+    
     if (s.length() == 1) {
 	o.replace(0, 1, path_home());
     } else if  (s[1] == '/') {
 	o.replace(0, 2, path_home());
     } else {
 	string::size_type pos = s.find('/');
-	int l = (pos == string::npos) ? s.length() - 1 : pos - 1;
+        string::size_type l = (pos == string::npos) ? s.length() - 1 : pos - 1;
+#ifdef _WIN32
+        // Dont know what this means. Just replace with HOME
+        o.replace(0, l+1, path_home());
+#else
 	struct passwd *entry = getpwnam(s.substr(1, l).c_str());
 	if (entry)
 	    o.replace(0, l+1, entry->pw_dir);
+#endif
     }
     return o;
 }
 
+bool path_isroot(const string& path)
+{
+    if (path.size() == 1 && path[0] == '/')
+        return true;
+#ifdef _WIN32
+    if (path.size() == 3 && isalpha(path[0]) && path[1] == ':' &&
+        (path[2] == '/' || path[2] == '\\'))
+        return true;
+#endif
+    return false;
+}
+
+bool path_isabsolute(const string &path)
+{
+    if (!path.empty() && (path[0] == '/'
+#ifdef _WIN32
+                          || path_isdriveabs(path)
+#endif
+            )) {
+        return true;
+    } 
+    return false;
+}
+    
 string path_absolute(const string &is)
 {
     if (is.length() == 0)
 	return is;
     string s = is;
-    if (s[0] != '/') {
+    if (!path_isabsolute(s)) {
 	char buf[MAXPATHLEN];
 	if (!getcwd(buf, MAXPATHLEN)) {
 	    return string();
 	}
-	s = path_cat(string(buf), s); 
+	s = path_cat(string(buf), s);
+#ifdef _WIN32
+        path_slashize(s);
+#endif
     }
     return s;
 }
@@ -341,7 +603,15 @@
     if (is.length() == 0)
 	return is;
     string s = is;
-    if (s[0] != '/') {
+#ifdef _WIN32
+    path_slashize(s);
+    // fix possible path from file: absolute url
+    if (s.size() && s[0] == '/' && path_hasdrive(s.substr(1))) {
+        s = s.substr(1);
+    }
+#endif
+
+    if (!path_isabsolute(s)) {
 	char buf[MAXPATHLEN];
 	const char *cwdp = buf;
 	if (cwd) {
@@ -370,7 +640,13 @@
     if (!cleaned.empty()) {
 	for (vector<string>::const_iterator it = cleaned.begin(); 
 	     it != cleaned.end(); it++) {
-	    ret += "/";
+            ret += "/";
+#ifdef _WIN32
+            if (it == cleaned.begin() && path_strlookslikedrive(*it)) {
+                // Get rid of just added initial "/"
+                ret.clear();
+            }
+#endif
 	    ret += *it;
 	}
     } else {
@@ -387,6 +663,10 @@
     path = "/";
     for (vector<string>::const_iterator it = elems.begin(); 
 	 it != elems.end(); it++){
+#ifdef _WIN32
+        if (it == elems.begin() && path_strlookslikedrive(*it))
+            path = "";
+#endif
 	path += *it;
 	// Not using path_isdir() here, because this cant grok symlinks
 	// If we hit an existing file, no worry, mkdir will just fail.
@@ -398,21 +678,6 @@
 	path += "/";
     }
     return true;
-}
-
-vector<string> path_dirglob(const string &dir, const string pattern)
-{
-    vector<string> res;
-    glob_t mglob;
-    string mypat=path_cat(dir, pattern);
-    if (glob(mypat.c_str(), 0, 0, &mglob)) {
-	return res;
-    }
-    for (int i = 0; i < int(mglob.gl_pathc); i++) {
-	res.push_back(mglob.gl_pathv[i]);
-    }
-    globfree(&mglob);
-    return res;
 }
 
 bool path_isdir(const string& path)
@@ -553,6 +818,16 @@
         string("http://") + parenturl;
 }
 
+
+string path_defaultrecollconfsubdir()
+{
+#ifdef _WIN32
+    return "Recoll";
+#else
+    return ".recoll";
+#endif
+}
+
 // Convert to file path if url is like file:
 // Note: this only works with our internal pseudo-urls which are not
 // encoded/escaped
@@ -562,6 +837,14 @@
         url = url.substr(7, string::npos);
     else
         return string();
+
+#ifdef _WIN32
+    // Absolute file urls are like: file:///c:/mydir/...
+    // Get rid of the initial '/'
+    if (url.size() >= 3 && url[0] == '/' && isalpha(url[1]) && url[2] == ':') {
+        url = url.substr(1);
+    }
+#endif
 
     // Removing the fragment part. This is exclusively used when
     // executing a viewer for the recoll manual, and we only strip the
@@ -574,6 +857,17 @@
     }
 
     return url;
+}
+
+string path_pathtofileurl(const string& path)
+{
+	// We're supposed to receive a canonic absolute path, but on windows we
+	// may need to add a '/' in front of the drive spec
+	string url(cstr_fileu);
+	if (path.empty() || path[0] != '/')
+		url.push_back('/');
+	url += path;
+	return url;
 }
 
 bool urlisfileurl(const string& url)
@@ -687,6 +981,9 @@
 	return -1;
     }
 #else
+#ifdef _WIN32
+	return 0;
+#else
     int operation = LOCK_EX | LOCK_NB;
     if (flock(m_fd, operation) == -1) {
 	int serrno = errno;
@@ -695,6 +992,7 @@
 	m_reason = "flock failed";
 	return -1;
     }
+#endif // ! win32
 #endif // ! sun
 
     if (ftruncate(m_fd, 0) != 0) {
@@ -815,6 +1113,7 @@
     path_home();
     tmplocation();
     thumbnailsdir();
+    path_sharedatadir();
 }
 
 
@@ -933,7 +1232,7 @@
   pidfile.remove();
 #endif
 
-#if 1
+#if 0
   if (argc > 1) {
       cerr <<  "Usage: thumbpath <filepath>" << endl;
       exit(1);
@@ -965,6 +1264,19 @@
     cout << "Suffix: [" << ext << "]" << endl;
     return 0;
 #endif
+
+#if 1
+    if (argc != 1) {
+	cerr << "Usage: trpathut url" << endl;
+	exit(1);
+    }
+    string url = *argv++;argc--;
+
+    cout << "File: [" << fileurltolocalpath(url) << "]\n";
+    return 0;
+#endif
+
+
 }
 
 #endif // TEST_PATHUT