1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 /* Some of the original code to handle PIDLs come from the
8    MiniExplorer example of the Vaca library:
9    https://github.com/dacap/vaca
10    Copyright (C) by David Capello (MIT License)
11  */
12 
13 #ifdef HAVE_CONFIG_H
14 #include "config.h"
15 #endif
16 
17 #include "app/file_system.h"
18 
19 #include "base/fs.h"
20 #include "base/string.h"
21 #include "she/display.h"
22 #include "she/surface.h"
23 #include "she/system.h"
24 
25 #include <algorithm>
26 #include <cstdio>
27 #include <map>
28 #include <utility>
29 #include <vector>
30 
31 #ifdef _WIN32
32   #include <windows.h>
33 
34   #include <shlobj.h>
35   #include <shlwapi.h>
36 
37   #define MYPC_CSLID  "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
38 #else
39   #include <dirent.h>
40 #endif
41 
42 //////////////////////////////////////////////////////////////////////
43 
44 #ifndef MAX_PATH
45   #define MAX_PATH 4096
46 #endif
47 
48 #define NOTINITIALIZED  "{__not_initialized_path__}"
49 
50 #define FS_TRACE(...)
51 
52 namespace app {
53 
54 // a position in the file-system
55 class FileItem : public IFileItem {
56 public:
57   std::string m_keyname;
58   std::string m_filename;
59   std::string m_displayname;
60   FileItem* m_parent;
61   FileItemList m_children;
62   unsigned int m_version;
63   bool m_removed;
64   bool m_is_folder;
65 #ifdef _WIN32
66   LPITEMIDLIST m_pidl;            // relative to parent
67   LPITEMIDLIST m_fullpidl;        // relative to the Desktop folder
68                                   // (like a full path-name, because the
69                                   // desktop is the root on Windows)
70 #endif
71 
72   FileItem(FileItem* parent);
73   ~FileItem();
74 
75   void insertChildSorted(FileItem* child);
76   int compare(const FileItem& that) const;
77 
operator <(const FileItem & that) const78   bool operator<(const FileItem& that) const { return compare(that) < 0; }
operator >(const FileItem & that) const79   bool operator>(const FileItem& that) const { return compare(that) > 0; }
operator ==(const FileItem & that) const80   bool operator==(const FileItem& that) const { return compare(that) == 0; }
operator !=(const FileItem & that) const81   bool operator!=(const FileItem& that) const { return compare(that) != 0; }
82 
83   // IFileItem interface
84 
85   bool isFolder() const;
86   bool isBrowsable() const;
87   bool isHidden() const;
88 
89   std::string keyName() const;
90   std::string fileName() const;
91   std::string displayName() const;
92 
93   IFileItem* parent() const;
94   const FileItemList& children();
95   void createDirectory(const std::string& dirname);
96 
97   bool hasExtension(const base::paths& extensions);
98 
99   she::Surface* getThumbnail();
100   void setThumbnail(she::Surface* thumbnail);
101 
102 };
103 
104 typedef std::map<std::string, FileItem*> FileItemMap;
105 typedef std::map<std::string, she::Surface*> ThumbnailMap;
106 
107 // the root of the file-system
108 static FileItem* rootitem = NULL;
109 static FileItemMap* fileitems_map;
110 static ThumbnailMap* thumbnail_map;
111 static unsigned int current_file_system_version = 0;
112 
113 #ifdef _WIN32
114   static IMalloc* shl_imalloc = NULL;
115   static IShellFolder* shl_idesktop = NULL;
116 #endif
117 
118 // A more easy PIDLs interface (without using the SH* & IL* routines of W2K)
119 #ifdef _WIN32
120   static bool is_sfgaof_folder(SFGAOF attrib);
121   static void update_by_pidl(FileItem* fileitem, SFGAOF attrib);
122   static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail);
123   static UINT get_pidl_size(LPITEMIDLIST pidl);
124   static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl);
125   static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl);
126   static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl);
127   static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl);
128   static void free_pidl(LPITEMIDLIST pidl);
129   static std::string get_key_for_pidl(LPITEMIDLIST pidl);
130 
131   static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST pidl, bool create_if_not);
132   static void put_fileitem(FileItem* fileitem);
133 #else
134   static FileItem* get_fileitem_by_path(const std::string& path, bool create_if_not);
135   static std::string remove_backslash_if_needed(const std::string& filename);
136   static std::string get_key_for_filename(const std::string& filename);
137   static void put_fileitem(FileItem* fileitem);
138 #endif
139 
140 FileSystemModule* FileSystemModule::m_instance = NULL;
141 
FileSystemModule()142 FileSystemModule::FileSystemModule()
143 {
144   ASSERT(m_instance == NULL);
145   m_instance = this;
146 
147   fileitems_map = new FileItemMap;
148   thumbnail_map = new ThumbnailMap;
149 
150 #ifdef _WIN32
151   /* get the IMalloc interface */
152   HRESULT hr = SHGetMalloc(&shl_imalloc);
153   if (hr != S_OK)
154     throw std::runtime_error("Error initializing file system. Report this problem. (SHGetMalloc failed.)");
155 
156   /* get desktop IShellFolder interface */
157   hr = SHGetDesktopFolder(&shl_idesktop);
158   if (hr != S_OK)
159     throw std::runtime_error("Error initializing file system. Report this problem. (SHGetDesktopFolder failed.)");
160 #endif
161 
162   // first version of the file system
163   ++current_file_system_version;
164 
165   // get the root element of the file system (this will create
166   // the 'rootitem' FileItem)
167   getRootFileItem();
168 }
169 
~FileSystemModule()170 FileSystemModule::~FileSystemModule()
171 {
172   ASSERT(m_instance == this);
173 
174   for (FileItemMap::iterator
175          it=fileitems_map->begin(); it!=fileitems_map->end(); ++it) {
176     delete it->second;
177   }
178   fileitems_map->clear();
179 
180   for (ThumbnailMap::iterator
181          it=thumbnail_map->begin(); it!=thumbnail_map->end(); ++it) {
182     it->second->dispose();
183   }
184   thumbnail_map->clear();
185 
186 #ifdef _WIN32
187   // relase desktop IShellFolder interface
188   shl_idesktop->Release();
189 
190   // release IMalloc interface
191   shl_imalloc->Release();
192   shl_imalloc = NULL;
193 #endif
194 
195   delete fileitems_map;
196   delete thumbnail_map;
197 
198   m_instance = NULL;
199 }
200 
instance()201 FileSystemModule* FileSystemModule::instance()
202 {
203   return m_instance;
204 }
205 
refresh()206 void FileSystemModule::refresh()
207 {
208   ++current_file_system_version;
209 }
210 
getRootFileItem()211 IFileItem* FileSystemModule::getRootFileItem()
212 {
213   FileItem* fileitem;
214 
215   if (rootitem)
216     return rootitem;
217 
218   fileitem = new FileItem(NULL);
219   rootitem = fileitem;
220 
221   //LOG("FS: Creating root fileitem %p\n", rootitem);
222 
223 #ifdef _WIN32
224   {
225     // get the desktop PIDL
226     LPITEMIDLIST pidl = NULL;
227 
228     if (SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl) != S_OK) {
229       // TODO do something better
230       ASSERT(false);
231       exit(1);
232     }
233     fileitem->m_pidl = pidl;
234     fileitem->m_fullpidl = pidl;
235 
236     SFGAOF attrib = SFGAO_FOLDER;
237     shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&pidl, &attrib);
238 
239     update_by_pidl(fileitem, attrib);
240   }
241 #else
242   {
243     const char* root = "/";
244 
245     fileitem->m_filename = root;
246     fileitem->m_displayname = root;
247     fileitem->m_is_folder = true;
248   }
249 #endif
250 
251   // insert the file-item in the hash-table
252   put_fileitem(fileitem);
253   return fileitem;
254 }
255 
getFileItemFromPath(const std::string & path)256 IFileItem* FileSystemModule::getFileItemFromPath(const std::string& path)
257 {
258   IFileItem* fileitem = NULL;
259 
260   //LOG("FS: get_fileitem_from_path(%s)\n", path.c_str());
261 
262 #ifdef _WIN32
263   {
264     ULONG cbEaten = 0UL;
265     LPITEMIDLIST fullpidl = NULL;
266     SFGAOF attrib = SFGAO_FOLDER;
267 
268     if (path.empty()) {
269       fileitem = getRootFileItem();
270       //LOG("FS: > %p (root)\n", fileitem);
271       return fileitem;
272     }
273 
274     if (shl_idesktop->ParseDisplayName
275           (NULL, NULL,
276            const_cast<LPWSTR>(base::from_utf8(path).c_str()),
277            &cbEaten, &fullpidl, &attrib) != S_OK) {
278       //LOG("FS: > (null)\n");
279       return NULL;
280     }
281 
282     fileitem = get_fileitem_by_fullpidl(fullpidl, true);
283     free_pidl(fullpidl);
284   }
285 #else
286   {
287     std::string buf = remove_backslash_if_needed(path);
288     fileitem = get_fileitem_by_path(buf, true);
289   }
290 #endif
291 
292   //LOG("FS: get_fileitem_from_path(%s) -> %p\n", path.c_str(), fileitem);
293 
294   return fileitem;
295 }
296 
297 // ======================================================================
298 // FileItem class (IFileItem implementation)
299 // ======================================================================
300 
isFolder() const301 bool FileItem::isFolder() const
302 {
303   return m_is_folder;
304 }
305 
isBrowsable() const306 bool FileItem::isBrowsable() const
307 {
308   ASSERT(m_filename != NOTINITIALIZED);
309 
310   return m_is_folder;
311 }
312 
isHidden() const313 bool FileItem::isHidden() const
314 {
315   ASSERT(m_displayname != NOTINITIALIZED);
316 
317 #ifdef _WIN32
318   return false;
319 #else
320   return m_displayname[0] == '.';
321 #endif
322 }
323 
keyName() const324 std::string FileItem::keyName() const
325 {
326   ASSERT(m_keyname != NOTINITIALIZED);
327 
328   return m_keyname;
329 }
330 
fileName() const331 std::string FileItem::fileName() const
332 {
333   ASSERT(m_filename != NOTINITIALIZED);
334 
335   return m_filename;
336 }
337 
displayName() const338 std::string FileItem::displayName() const
339 {
340   ASSERT(m_displayname != NOTINITIALIZED);
341 
342   return m_displayname;
343 }
344 
parent() const345 IFileItem* FileItem::parent() const
346 {
347   if (this == rootitem)
348     return NULL;
349   else {
350     ASSERT(m_parent);
351     return m_parent;
352   }
353 }
354 
children()355 const FileItemList& FileItem::children()
356 {
357   // Is the file-item a folder?
358   if (isFolder() &&
359       // if the children list is empty, or the file-system version
360       // change (it's like to say: the current m_children list
361       // is outdated)...
362       (m_children.empty() ||
363        current_file_system_version > m_version)) {
364     FileItemList::iterator it;
365     FileItem* child;
366 
367     // we have to mark current items as deprecated
368     for (it=m_children.begin();
369          it!=m_children.end(); ++it) {
370       child = static_cast<FileItem*>(*it);
371       child->m_removed = true;
372     }
373 
374     //LOG("FS: Loading files for %p (%s)\n", fileitem, fileitem->displayname);
375 #ifdef _WIN32
376     {
377       IShellFolder* pFolder = NULL;
378       HRESULT hr;
379 
380       if (this == rootitem)
381         pFolder = shl_idesktop;
382       else {
383         hr = shl_idesktop->BindToObject(m_fullpidl,
384           NULL, IID_IShellFolder, (LPVOID *)&pFolder);
385 
386         if (hr != S_OK)
387           pFolder = NULL;
388       }
389 
390       if (pFolder != NULL) {
391         IEnumIDList *pEnum = NULL;
392         ULONG c, fetched;
393 
394         /* get the interface to enumerate subitems */
395         hr = pFolder->EnumObjects(reinterpret_cast<HWND>(she::instance()->defaultDisplay()->nativeHandle()),
396           SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
397 
398         if (hr == S_OK && pEnum != NULL) {
399           LPITEMIDLIST itempidl[256];
400           SFGAOF attribs[256];
401 
402           /* enumerate the items in the folder */
403           while (pEnum->Next(256, itempidl, &fetched) == S_OK && fetched > 0) {
404             /* request the SFGAO_FOLDER attribute to know what of the
405                item is a folder */
406             for (c=0; c<fetched; ++c) {
407               attribs[c] = SFGAO_FOLDER;
408               pFolder->GetAttributesOf(1, (LPCITEMIDLIST *)itempidl, attribs+c);
409             }
410 
411             /* generate the FileItems */
412             for (c=0; c<fetched; ++c) {
413               LPITEMIDLIST fullpidl = concat_pidl(m_fullpidl,
414                                                   itempidl[c]);
415 
416               child = get_fileitem_by_fullpidl(fullpidl, false);
417               if (!child) {
418                 child = new FileItem(this);
419 
420                 child->m_pidl = itempidl[c];
421                 child->m_fullpidl = fullpidl;
422 
423                 update_by_pidl(child, attribs[c]);
424                 put_fileitem(child);
425               }
426               else {
427                 ASSERT(child->m_parent == this);
428                 free_pidl(fullpidl);
429                 free_pidl(itempidl[c]);
430               }
431 
432               insertChildSorted(child);
433             }
434           }
435 
436           pEnum->Release();
437         }
438 
439         if (pFolder != shl_idesktop)
440           pFolder->Release();
441       }
442     }
443 #else
444     {
445       DIR* dir = opendir(m_filename.c_str());
446       if (dir) {
447         dirent* entry;
448         while ((entry = readdir(dir)) != NULL) {
449           FileItem* child;
450           std::string fn = entry->d_name;
451           std::string fullfn = base::join_path(m_filename, fn);
452 
453           if (fn == "." || fn == "..")
454             continue;
455 
456           child = get_fileitem_by_path(fullfn, false);
457           if (!child) {
458             child = new FileItem(this);
459 
460             bool is_folder;
461             if (entry->d_type == DT_LNK) {
462               is_folder = base::is_directory(fullfn);
463             }
464             else {
465               is_folder = (entry->d_type == DT_DIR);
466             }
467 
468             child->m_filename = fullfn;
469             child->m_displayname = fn;
470             child->m_is_folder = is_folder;
471 
472             put_fileitem(child);
473           }
474           else {
475             ASSERT(child->m_parent == this);
476           }
477 
478           insertChildSorted(child);
479         }
480         closedir(dir);
481       }
482   }
483 #endif
484 
485     // check old file-items (maybe removed directories or file-items)
486     for (it=m_children.begin();
487          it!=m_children.end(); ) {
488       child = static_cast<FileItem*>(*it);
489       ASSERT(child != NULL);
490 
491       if (child && child->m_removed) {
492         it = m_children.erase(it);
493 
494         fileitems_map->erase(fileitems_map->find(child->m_keyname));
495         delete child;
496       }
497       else
498         ++it;
499     }
500 
501     // now this file-item is updated
502     m_version = current_file_system_version;
503   }
504 
505   return m_children;
506 }
507 
createDirectory(const std::string & dirname)508 void FileItem::createDirectory(const std::string& dirname)
509 {
510   base::make_directory(base::join_path(m_filename, dirname));
511 
512   // Invalidate the children list.
513   m_version = 0;
514 }
515 
hasExtension(const base::paths & extensions)516 bool FileItem::hasExtension(const base::paths& extensions)
517 {
518   ASSERT(m_filename != NOTINITIALIZED);
519 
520   return base::has_file_extension(m_filename, extensions);
521 }
522 
getThumbnail()523 she::Surface* FileItem::getThumbnail()
524 {
525   ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
526   if (it != thumbnail_map->end())
527     return it->second;
528   else
529     return NULL;
530 }
531 
setThumbnail(she::Surface * thumbnail)532 void FileItem::setThumbnail(she::Surface* thumbnail)
533 {
534   // destroy the current thumbnail of the file (if exists)
535   ThumbnailMap::iterator it = thumbnail_map->find(m_filename);
536   if (it != thumbnail_map->end()) {
537     it->second->dispose();
538     thumbnail_map->erase(it);
539   }
540 
541   // insert the new one in the map
542   thumbnail_map->insert(std::make_pair(m_filename, thumbnail));
543 }
544 
FileItem(FileItem * parent)545 FileItem::FileItem(FileItem* parent)
546 {
547   FS_TRACE("FS: Creating %p fileitem with parent %p\n", this, parent);
548 
549   m_keyname = NOTINITIALIZED;
550   m_filename = NOTINITIALIZED;
551   m_displayname = NOTINITIALIZED;
552   m_parent = parent;
553   m_version = current_file_system_version;
554   m_removed = false;
555   m_is_folder = false;
556 #ifdef _WIN32
557   m_pidl = NULL;
558   m_fullpidl = NULL;
559 #endif
560 }
561 
~FileItem()562 FileItem::~FileItem()
563 {
564   FS_TRACE("FS: Destroying FileItem() with parent %p\n", m_parent);
565 
566 #ifdef _WIN32
567   if (m_fullpidl && m_fullpidl != m_pidl) {
568     free_pidl(m_fullpidl);
569     m_fullpidl = NULL;
570   }
571 
572   if (m_pidl) {
573     free_pidl(m_pidl);
574     m_pidl = NULL;
575   }
576 #endif
577 }
578 
insertChildSorted(FileItem * child)579 void FileItem::insertChildSorted(FileItem* child)
580 {
581   // this file-item wasn't removed from the last lookup
582   child->m_removed = false;
583 
584   // if the fileitem is already in the list we can go back
585   if (std::find(m_children.begin(), m_children.end(), child) != m_children.end())
586     return;
587 
588   for (auto it=m_children.begin(), end=m_children.end(); it!=end; ++it) {
589     if (*child < *static_cast<FileItem*>(*it)) {
590       m_children.insert(it, child);
591       return;
592     }
593   }
594 
595   m_children.push_back(child);
596 }
597 
compare(const FileItem & that) const598 int FileItem::compare(const FileItem& that) const
599 {
600   if (isFolder()) {
601     if (!that.isFolder())
602       return -1;
603   }
604   else if (that.isFolder())
605     return 1;
606 
607   return base::compare_filenames(m_displayname, that.m_displayname);
608 }
609 
610 //////////////////////////////////////////////////////////////////////
611 // PIDLS: Only for Win32
612 //////////////////////////////////////////////////////////////////////
613 
614 #ifdef _WIN32
615 
calc_is_folder(std::string filename,SFGAOF attrib)616 static bool calc_is_folder(std::string filename, SFGAOF attrib)
617 {
618   return ((attrib & SFGAO_FOLDER) == SFGAO_FOLDER)
619     && (base::get_file_extension(filename) != "zip")
620     && ((!filename.empty() && (*filename.begin()) != ':') || (filename == MYPC_CSLID));
621 }
622 
623 // Updates the names of the file-item through its PIDL
update_by_pidl(FileItem * fileitem,SFGAOF attrib)624 static void update_by_pidl(FileItem* fileitem, SFGAOF attrib)
625 {
626   STRRET strret;
627   WCHAR pszName[MAX_PATH];
628   IShellFolder* pFolder = NULL;
629   HRESULT hr;
630 
631   if (fileitem == rootitem)
632     pFolder = shl_idesktop;
633   else {
634     ASSERT(fileitem->m_parent);
635     hr = shl_idesktop->BindToObject(fileitem->m_parent->m_fullpidl,
636       NULL, IID_IShellFolder, (LPVOID *)&pFolder);
637     if (hr != S_OK)
638       pFolder = NULL;
639   }
640 
641   // Get the file name
642 
643   if (pFolder != NULL &&
644       pFolder->GetDisplayNameOf(fileitem->m_pidl,
645                                 SHGDN_NORMAL | SHGDN_FORPARSING,
646                                 &strret) == S_OK) {
647     StrRetToBuf(&strret, fileitem->m_pidl, pszName, MAX_PATH);
648     fileitem->m_filename = base::to_utf8(pszName);
649   }
650   else if (shl_idesktop->GetDisplayNameOf(fileitem->m_fullpidl,
651                                           SHGDN_NORMAL | SHGDN_FORPARSING,
652                                           &strret) == S_OK) {
653     StrRetToBuf(&strret, fileitem->m_fullpidl, pszName, MAX_PATH);
654     fileitem->m_filename = base::to_utf8(pszName);
655   }
656   else
657     fileitem->m_filename = "ERR";
658 
659   // Is it a folder?
660 
661   fileitem->m_is_folder = calc_is_folder(fileitem->m_filename, attrib);
662 
663   // Get the name to display
664 
665   if (fileitem->isFolder() &&
666       pFolder &&
667       pFolder->GetDisplayNameOf(fileitem->m_pidl,
668                                 SHGDN_INFOLDER,
669                                 &strret) == S_OK) {
670     StrRetToBuf(&strret, fileitem->m_pidl, pszName, MAX_PATH);
671     fileitem->m_displayname = base::to_utf8(pszName);
672   }
673   else if (fileitem->isFolder() &&
674            shl_idesktop->GetDisplayNameOf(fileitem->m_fullpidl,
675                                           SHGDN_INFOLDER,
676                                           &strret) == S_OK) {
677     StrRetToBuf(&strret, fileitem->m_fullpidl, pszName, MAX_PATH);
678     fileitem->m_displayname = base::to_utf8(pszName);
679   }
680   else {
681     fileitem->m_displayname = base::get_file_name(fileitem->m_filename);
682   }
683 
684   if (pFolder != NULL && pFolder != shl_idesktop) {
685     pFolder->Release();
686   }
687 }
688 
concat_pidl(LPITEMIDLIST pidlHead,LPITEMIDLIST pidlTail)689 static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail)
690 {
691   LPITEMIDLIST pidlNew;
692   UINT cb1, cb2;
693 
694   ASSERT(pidlHead);
695   ASSERT(pidlTail);
696 
697   cb1 = get_pidl_size(pidlHead) - sizeof(pidlHead->mkid.cb);
698   cb2 = get_pidl_size(pidlTail);
699 
700   pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(cb1 + cb2);
701   if (pidlNew) {
702     CopyMemory(pidlNew, pidlHead, cb1);
703     CopyMemory(((LPSTR)pidlNew) + cb1, pidlTail, cb2);
704   }
705 
706   return pidlNew;
707 }
708 
get_pidl_size(LPITEMIDLIST pidl)709 static UINT get_pidl_size(LPITEMIDLIST pidl)
710 {
711   UINT cbTotal = 0;
712 
713   if (pidl) {
714     cbTotal += sizeof(pidl->mkid.cb); /* null terminator */
715 
716     while (pidl) {
717       cbTotal += pidl->mkid.cb;
718       pidl = get_next_pidl(pidl);
719     }
720   }
721 
722   return cbTotal;
723 }
724 
get_next_pidl(LPITEMIDLIST pidl)725 static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl)
726 {
727   if (pidl != NULL && pidl->mkid.cb > 0) {
728     pidl = (LPITEMIDLIST)(((LPBYTE)(pidl)) + pidl->mkid.cb);
729     if (pidl->mkid.cb > 0)
730       return pidl;
731   }
732 
733   return NULL;
734 }
735 
get_last_pidl(LPITEMIDLIST pidl)736 static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl)
737 {
738   LPITEMIDLIST pidlLast = pidl;
739   LPITEMIDLIST pidlNew = NULL;
740 
741   while (pidl) {
742     pidlLast = pidl;
743     pidl = get_next_pidl(pidl);
744   }
745 
746   if (pidlLast) {
747     ULONG sz = get_pidl_size(pidlLast);
748     pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
749     CopyMemory(pidlNew, pidlLast, sz);
750   }
751 
752   return pidlNew;
753 }
754 
clone_pidl(LPITEMIDLIST pidl)755 static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl)
756 {
757   ULONG sz = get_pidl_size(pidl);
758   LPITEMIDLIST pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
759 
760   CopyMemory(pidlNew, pidl, sz);
761 
762   return pidlNew;
763 }
764 
remove_last_pidl(LPITEMIDLIST pidl)765 static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl)
766 {
767   LPITEMIDLIST pidlFirst = pidl;
768   LPITEMIDLIST pidlLast = pidl;
769 
770   while (pidl) {
771     pidlLast = pidl;
772     pidl = get_next_pidl(pidl);
773   }
774 
775   if (pidlLast)
776     pidlLast->mkid.cb = 0;
777 
778   return pidlFirst;
779 }
780 
free_pidl(LPITEMIDLIST pidl)781 static void free_pidl(LPITEMIDLIST pidl)
782 {
783   shl_imalloc->Free(pidl);
784 }
785 
get_key_for_pidl(LPITEMIDLIST pidl)786 static std::string get_key_for_pidl(LPITEMIDLIST pidl)
787 {
788 #if 0
789   char *key = base_malloc(get_pidl_size(pidl)+1);
790   UINT c, i = 0;
791 
792   while (pidl) {
793     for (c=0; c<pidl->mkid.cb; ++c) {
794       if (pidl->mkid.abID[c])
795         key[i++] = pidl->mkid.abID[c];
796       else
797         key[i++] = 1;
798     }
799     pidl = get_next_pidl(pidl);
800   }
801   key[i] = 0;
802 
803   return key;
804 #else
805   STRRET strret;
806   WCHAR pszName[MAX_PATH];
807   WCHAR key[4096] = { 0 };
808   int len;
809 
810   // Go pidl by pidl from the fullpidl to the root (desktop)
811   //LOG("FS: ***\n");
812   pidl = clone_pidl(pidl);
813   while (pidl->mkid.cb > 0) {
814     if (shl_idesktop->GetDisplayNameOf(pidl,
815                                        SHGDN_INFOLDER | SHGDN_FORPARSING,
816                                        &strret) == S_OK) {
817       if (StrRetToBuf(&strret, pidl, pszName, MAX_PATH) != S_OK)
818         pszName[0] = 0;
819 
820       //LOG("FS: + %s\n", pszName);
821 
822       len = wcslen(pszName);
823       if (len > 0) {
824         if (*key) {
825           if (pszName[len-1] != L'\\') {
826             memmove(key+len+1, key, sizeof(WCHAR)*(wcslen(key)+1));
827             key[len] = L'\\';
828           }
829           else
830             memmove(key+len, key, sizeof(WCHAR)*(wcslen(key)+1));
831         }
832         else
833           key[len] = 0;
834 
835         memcpy(key, pszName, sizeof(WCHAR)*len);
836       }
837     }
838     remove_last_pidl(pidl);
839   }
840   free_pidl(pidl);
841 
842   //LOG("FS: =%s\n***\n", key);
843   return base::to_utf8(key);
844 #endif
845 }
846 
get_fileitem_by_fullpidl(LPITEMIDLIST fullpidl,bool create_if_not)847 static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST fullpidl, bool create_if_not)
848 {
849   FileItemMap::iterator it = fileitems_map->find(get_key_for_pidl(fullpidl));
850   if (it != fileitems_map->end())
851     return it->second;
852 
853   if (!create_if_not)
854     return NULL;
855 
856   // new file-item
857   FileItem* fileitem = new FileItem(NULL);
858   fileitem->m_fullpidl = clone_pidl(fullpidl);
859 
860   SFGAOF attrib = SFGAO_FOLDER;
861   HRESULT hr = shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&fileitem->m_fullpidl, &attrib);
862   if (hr == S_OK) {
863     LPITEMIDLIST parent_fullpidl = clone_pidl(fileitem->m_fullpidl);
864     remove_last_pidl(parent_fullpidl);
865 
866     fileitem->m_pidl = get_last_pidl(fileitem->m_fullpidl);
867     fileitem->m_parent = get_fileitem_by_fullpidl(parent_fullpidl, true);
868 
869     free_pidl(parent_fullpidl);
870   }
871 
872   update_by_pidl(fileitem, attrib);
873   put_fileitem(fileitem);
874 
875   //LOG("FS: fileitem %p created %s with parent %p\n", fileitem, fileitem->keyname.c_str(), fileitem->parent);
876 
877   return fileitem;
878 }
879 
880 /**
881  * Inserts the @a fileitem in the hash map of items.
882  */
put_fileitem(FileItem * fileitem)883 static void put_fileitem(FileItem* fileitem)
884 {
885   ASSERT(fileitem->m_filename != NOTINITIALIZED);
886   ASSERT(fileitem->m_keyname == NOTINITIALIZED);
887 
888   fileitem->m_keyname = get_key_for_pidl(fileitem->m_fullpidl);
889 
890   ASSERT(fileitem->m_keyname != NOTINITIALIZED);
891 
892 #ifdef _DEBUG
893   auto it = fileitems_map->find(get_key_for_pidl(fileitem->m_fullpidl));
894   ASSERT(it == fileitems_map->end());
895 #endif
896 
897   // insert this file-item in the hash-table
898   fileitems_map->insert(std::make_pair(fileitem->m_keyname, fileitem));
899 }
900 
901 #else
902 
903 //////////////////////////////////////////////////////////////////////
904 // POSIX functions
905 //////////////////////////////////////////////////////////////////////
906 
get_fileitem_by_path(const std::string & path,bool create_if_not)907 static FileItem* get_fileitem_by_path(const std::string& path, bool create_if_not)
908 {
909   if (path.empty())
910     return rootitem;
911 
912   FileItemMap::iterator it = fileitems_map->find(get_key_for_filename(path));
913   if (it != fileitems_map->end())
914     return it->second;
915 
916   if (!create_if_not)
917     return NULL;
918 
919   // get the attributes of the file
920   bool is_folder = false;
921   if (!base::is_file(path)) {
922     if (!base::is_directory(path))
923       return NULL;
924 
925     is_folder = true;
926   }
927 
928   // new file-item
929   FileItem* fileitem = new FileItem(NULL);
930 
931   fileitem->m_filename = path;
932   fileitem->m_displayname = base::get_file_name(path);
933   fileitem->m_is_folder = is_folder;
934 
935   // get the parent
936   {
937     std::string parent_path = remove_backslash_if_needed(base::join_path(base::get_file_path(path), ""));
938     fileitem->m_parent = get_fileitem_by_path(parent_path, true);
939   }
940 
941   put_fileitem(fileitem);
942 
943   return fileitem;
944 }
945 
remove_backslash_if_needed(const std::string & filename)946 static std::string remove_backslash_if_needed(const std::string& filename)
947 {
948   if (!filename.empty() && base::is_path_separator(*(filename.end()-1))) {
949     int len = filename.size();
950 
951     // This is just the root '/' slash
952     if (len == 1)
953       return filename;
954     else
955       return base::remove_path_separator(filename);
956   }
957   return filename;
958 }
959 
get_key_for_filename(const std::string & filename)960 static std::string get_key_for_filename(const std::string& filename)
961 {
962   std::string buf(filename);
963   buf = base::fix_path_separators(buf);
964   return buf;
965 }
966 
put_fileitem(FileItem * fileitem)967 static void put_fileitem(FileItem* fileitem)
968 {
969   ASSERT(fileitem->m_filename != NOTINITIALIZED);
970   ASSERT(fileitem->m_keyname == NOTINITIALIZED);
971 
972   fileitem->m_keyname = get_key_for_filename(fileitem->m_filename);
973 
974   ASSERT(fileitem->m_keyname != NOTINITIALIZED);
975 
976   // insert this file-item in the hash-table
977   fileitems_map->insert(std::make_pair(fileitem->m_keyname, fileitem));
978 }
979 
980 #endif
981 
982 } // namespace app
983