mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-31 00:32:48 +00:00
1119 lines
28 KiB
C++
1119 lines
28 KiB
C++
/* ASEPRITE
|
|
* Copyright (C) 2001-2013 David Capello
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* Some of the original code to handle PIDLs come from the
|
|
MiniExplorer example of the Vaca library:
|
|
http://vaca.sourceforge.net/
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <cstdio>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <allegro.h>
|
|
|
|
// define this macro to solve the problem of for_each_file Allegro
|
|
// routine which does not support to wrap 64-bits pointers in its
|
|
// user-data parameter
|
|
#define WORKAROUND_64BITS_SUPPORT
|
|
|
|
// in Windows we can use PIDLS
|
|
#if defined ALLEGRO_WINDOWS
|
|
// uncomment this if you don't want to use PIDLs in windows
|
|
#define USE_PIDLS
|
|
#endif
|
|
|
|
#if defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX || defined ALLEGRO_DJGPP || defined ALLEGRO_MINGW32
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#if defined ALLEGRO_UNIX || defined ALLEGRO_MACOSX || defined ALLEGRO_MINGW32
|
|
#include <sys/unistd.h>
|
|
#endif
|
|
|
|
#if defined USE_PIDLS
|
|
#include <winalleg.h>
|
|
#include <shlobj.h>
|
|
#include <shlwapi.h>
|
|
#endif
|
|
|
|
#include "base/path.h"
|
|
#include "file_system.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef USE_PIDLS
|
|
// ..using Win32 Shell (PIDLs)
|
|
|
|
#define IS_FOLDER(fi) \
|
|
(((fi)->attrib & SFGAO_FOLDER) == SFGAO_FOLDER)
|
|
|
|
#define MYPC_CSLID "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
|
|
|
|
#else
|
|
// ..using Allegro (for_each_file)
|
|
|
|
#define IS_FOLDER(fi) \
|
|
(((fi)->attrib & FA_DIREC) == FA_DIREC)
|
|
|
|
#if (DEVICE_SEPARATOR != 0) && (DEVICE_SEPARATOR != '\0')
|
|
#define HAVE_DRIVES
|
|
#else
|
|
#define CASE_SENSITIVE
|
|
#endif
|
|
|
|
#ifndef FA_ALL
|
|
#define FA_ALL FA_RDONLY | FA_DIREC | FA_ARCH | FA_HIDDEN | FA_SYSTEM
|
|
#endif
|
|
#define FA_TO_SHOW FA_RDONLY | FA_DIREC | FA_ARCH | FA_SYSTEM
|
|
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef MAX_PATH
|
|
#define MAX_PATH 4096
|
|
#endif
|
|
|
|
#define NOTINITIALIZED "{__not_initialized_path__}"
|
|
|
|
// a position in the file-system
|
|
class FileItem : public IFileItem
|
|
{
|
|
public:
|
|
base::string keyname;
|
|
base::string filename;
|
|
base::string displayname;
|
|
FileItem* parent;
|
|
FileItemList children;
|
|
unsigned int version;
|
|
bool removed;
|
|
#ifdef USE_PIDLS
|
|
LPITEMIDLIST pidl; // relative to parent
|
|
LPITEMIDLIST fullpidl; // relative to the Desktop folder
|
|
// (like a full path-name, because the
|
|
// desktop is the root on Windows)
|
|
SFGAOF attrib;
|
|
#else
|
|
int attrib;
|
|
#endif
|
|
|
|
FileItem(FileItem* parent);
|
|
~FileItem();
|
|
|
|
void insertChildSorted(FileItem* child);
|
|
int compare(const FileItem& that) const;
|
|
|
|
bool operator<(const FileItem& that) const { return compare(that) < 0; }
|
|
bool operator>(const FileItem& that) const { return compare(that) > 0; }
|
|
bool operator==(const FileItem& that) const { return compare(that) == 0; }
|
|
bool operator!=(const FileItem& that) const { return compare(that) != 0; }
|
|
|
|
// IFileItem interface
|
|
|
|
bool isFolder() const;
|
|
bool isBrowsable() const;
|
|
|
|
base::string getKeyName() const;
|
|
base::string getFileName() const;
|
|
base::string getDisplayName() const;
|
|
|
|
IFileItem* getParent() const;
|
|
const FileItemList& getChildren();
|
|
|
|
bool hasExtension(const base::string& csv_extensions);
|
|
|
|
BITMAP* getThumbnail();
|
|
void setThumbnail(BITMAP* thumbnail);
|
|
|
|
};
|
|
|
|
typedef std::map<base::string, FileItem*> FileItemMap;
|
|
typedef std::map<base::string, BITMAP*> ThumbnailMap;
|
|
|
|
// the root of the file-system
|
|
static FileItem* rootitem = NULL;
|
|
static FileItemMap* fileitems_map;
|
|
static ThumbnailMap* thumbnail_map;
|
|
static unsigned int current_file_system_version = 0;
|
|
|
|
#ifdef USE_PIDLS
|
|
static IMalloc* shl_imalloc = NULL;
|
|
static IShellFolder* shl_idesktop = NULL;
|
|
#else
|
|
#ifdef WORKAROUND_64BITS_SUPPORT
|
|
static FileItem* for_each_child_callback_param;
|
|
#endif
|
|
#endif
|
|
|
|
/* a more easy PIDLs interface (without using the SH* & IL* routines of W2K) */
|
|
#ifdef USE_PIDLS
|
|
static void update_by_pidl(FileItem* fileitem);
|
|
static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail);
|
|
static UINT get_pidl_size(LPITEMIDLIST pidl);
|
|
static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl);
|
|
static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl);
|
|
static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl);
|
|
static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl);
|
|
static void free_pidl(LPITEMIDLIST pidl);
|
|
static base::string get_key_for_pidl(LPITEMIDLIST pidl);
|
|
|
|
static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST pidl, bool create_if_not);
|
|
static void put_fileitem(FileItem* fileitem);
|
|
#else
|
|
static FileItem* get_fileitem_by_path(const base::string& path, bool create_if_not);
|
|
static void for_each_child_callback(const char *filename, int attrib, int param);
|
|
static base::string remove_backslash_if_needed(const base::string& filename);
|
|
static base::string get_key_for_filename(const base::string& filename);
|
|
static void put_fileitem(FileItem* fileitem);
|
|
#endif
|
|
|
|
FileSystemModule* FileSystemModule::m_instance = NULL;
|
|
|
|
/**
|
|
* Initializes the file-system module to navigate the file-system.
|
|
*/
|
|
FileSystemModule::FileSystemModule()
|
|
{
|
|
ASSERT(m_instance == NULL);
|
|
m_instance = this;
|
|
|
|
fileitems_map = new FileItemMap;
|
|
thumbnail_map = new ThumbnailMap;
|
|
|
|
#ifdef USE_PIDLS
|
|
/* get the IMalloc interface */
|
|
SHGetMalloc(&shl_imalloc);
|
|
|
|
/* get desktop IShellFolder interface */
|
|
SHGetDesktopFolder(&shl_idesktop);
|
|
#endif
|
|
|
|
// first version of the file system
|
|
++current_file_system_version;
|
|
|
|
// get the root element of the file system (this will create
|
|
// the 'rootitem' FileItem)
|
|
getRootFileItem();
|
|
|
|
PRINTF("File system module installed\n");
|
|
}
|
|
|
|
/**
|
|
* Shutdowns the file-system module.
|
|
*/
|
|
FileSystemModule::~FileSystemModule()
|
|
{
|
|
PRINTF("File system module: uninstalling\n");
|
|
ASSERT(m_instance == this);
|
|
|
|
for (FileItemMap::iterator
|
|
it=fileitems_map->begin(); it!=fileitems_map->end(); ++it) {
|
|
delete it->second;
|
|
}
|
|
fileitems_map->clear();
|
|
|
|
for (ThumbnailMap::iterator
|
|
it=thumbnail_map->begin(); it!=thumbnail_map->end(); ++it) {
|
|
destroy_bitmap(it->second);
|
|
}
|
|
thumbnail_map->clear();
|
|
|
|
#ifdef USE_PIDLS
|
|
// relase desktop IShellFolder interface
|
|
shl_idesktop->Release();
|
|
|
|
// release IMalloc interface
|
|
shl_imalloc->Release();
|
|
shl_imalloc = NULL;
|
|
#endif
|
|
|
|
delete fileitems_map;
|
|
delete thumbnail_map;
|
|
|
|
PRINTF("File system module: uninstalled\n");
|
|
m_instance = NULL;
|
|
}
|
|
|
|
FileSystemModule* FileSystemModule::instance()
|
|
{
|
|
return m_instance;
|
|
}
|
|
|
|
/**
|
|
* Marks all FileItems as deprecated to be refresh the next time they
|
|
* are queried through @ref FileItem#getChildren.
|
|
*
|
|
* @see FileItem#getChildren
|
|
*/
|
|
void FileSystemModule::refresh()
|
|
{
|
|
++current_file_system_version;
|
|
}
|
|
|
|
IFileItem* FileSystemModule::getRootFileItem()
|
|
{
|
|
FileItem* fileitem;
|
|
|
|
if (rootitem)
|
|
return rootitem;
|
|
|
|
fileitem = new FileItem(NULL);
|
|
rootitem = fileitem;
|
|
|
|
//PRINTF("FS: Creating root fileitem %p\n", rootitem);
|
|
|
|
#ifdef USE_PIDLS
|
|
{
|
|
// get the desktop PIDL
|
|
LPITEMIDLIST pidl = NULL;
|
|
|
|
if (SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl) != S_OK) {
|
|
// TODO do something better
|
|
ASSERT(false);
|
|
exit(1);
|
|
}
|
|
fileitem->pidl = pidl;
|
|
fileitem->fullpidl = pidl;
|
|
fileitem->attrib = SFGAO_FOLDER;
|
|
shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&pidl, &fileitem->attrib);
|
|
|
|
update_by_pidl(fileitem);
|
|
}
|
|
#else
|
|
{
|
|
const char* root;
|
|
|
|
#if defined HAVE_DRIVES
|
|
root = "C:\\";
|
|
#else
|
|
root = "/";
|
|
#endif
|
|
|
|
fileitem->filename = root;
|
|
fileitem->displayname = root;
|
|
fileitem->attrib = FA_DIREC;
|
|
}
|
|
#endif
|
|
|
|
// insert the file-item in the hash-table
|
|
put_fileitem(fileitem);
|
|
return fileitem;
|
|
}
|
|
|
|
/**
|
|
* Returns the FileItem through the specified @a path.
|
|
*
|
|
* @warning You have to call path.fix_separators() before.
|
|
*/
|
|
IFileItem* FileSystemModule::getFileItemFromPath(const base::string& path)
|
|
{
|
|
IFileItem* fileitem = NULL;
|
|
|
|
//PRINTF("FS: get_fileitem_from_path(%s)\n", path.c_str());
|
|
|
|
#ifdef USE_PIDLS
|
|
{
|
|
ULONG cbEaten;
|
|
WCHAR wStr[MAX_PATH];
|
|
LPITEMIDLIST fullpidl = NULL;
|
|
SFGAOF attrib = SFGAO_FOLDER;
|
|
|
|
if (path.empty()) {
|
|
fileitem = getRootFileItem();
|
|
//PRINTF("FS: > %p (root)\n", fileitem);
|
|
return fileitem;
|
|
}
|
|
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
|
|
path.c_str(), path.size()+1, wStr, MAX_PATH);
|
|
if (shl_idesktop->ParseDisplayName(NULL, NULL,
|
|
wStr, &cbEaten,
|
|
&fullpidl,
|
|
&attrib) != S_OK) {
|
|
//PRINTF("FS: > (null)\n");
|
|
return NULL;
|
|
}
|
|
|
|
fileitem = get_fileitem_by_fullpidl(fullpidl, true);
|
|
free_pidl(fullpidl);
|
|
}
|
|
#else
|
|
{
|
|
base::string buf = remove_backslash_if_needed(path);
|
|
fileitem = get_fileitem_by_path(buf, true);
|
|
}
|
|
#endif
|
|
|
|
//PRINTF("FS: get_fileitem_from_path(%s) -> %p\n", path.c_str(), fileitem);
|
|
|
|
return fileitem;
|
|
}
|
|
|
|
bool FileSystemModule::dirExists(const base::string& path)
|
|
{
|
|
struct al_ffblk info;
|
|
int ret;
|
|
base::string path2 = base::join_path(path, "*.*");
|
|
|
|
ret = al_findfirst(path2.c_str(), &info, FA_ALL);
|
|
al_findclose(&info);
|
|
|
|
return (ret == 0);
|
|
}
|
|
|
|
// ======================================================================
|
|
// FileItem class (IFileItem implementation)
|
|
// ======================================================================
|
|
|
|
bool FileItem::isFolder() const
|
|
{
|
|
return IS_FOLDER(this);
|
|
}
|
|
|
|
bool FileItem::isBrowsable() const
|
|
{
|
|
ASSERT(this->filename != NOTINITIALIZED);
|
|
|
|
#ifdef USE_PIDLS
|
|
return IS_FOLDER(this)
|
|
&& (base::get_file_extension(this->filename) != "zip")
|
|
&& ((!this->filename.empty() && (*this->filename.begin()) != ':') ||
|
|
(this->filename == MYPC_CSLID));
|
|
#else
|
|
return IS_FOLDER(this);
|
|
#endif
|
|
}
|
|
|
|
base::string FileItem::getKeyName() const
|
|
{
|
|
ASSERT(this->keyname != NOTINITIALIZED);
|
|
|
|
return this->keyname;
|
|
}
|
|
|
|
base::string FileItem::getFileName() const
|
|
{
|
|
ASSERT(this->filename != NOTINITIALIZED);
|
|
|
|
return this->filename;
|
|
}
|
|
|
|
base::string FileItem::getDisplayName() const
|
|
{
|
|
ASSERT(this->displayname != NOTINITIALIZED);
|
|
|
|
return this->displayname;
|
|
}
|
|
|
|
IFileItem* FileItem::getParent() const
|
|
{
|
|
if (this == rootitem)
|
|
return NULL;
|
|
else {
|
|
ASSERT(this->parent);
|
|
return this->parent;
|
|
}
|
|
}
|
|
|
|
const FileItemList& FileItem::getChildren()
|
|
{
|
|
// Is the file-item a folder?
|
|
if (IS_FOLDER(this) &&
|
|
// if the children list is empty, or the file-system version
|
|
// change (it's like to say: the current this->children list
|
|
// is outdated)...
|
|
(this->children.empty() ||
|
|
current_file_system_version > this->version)) {
|
|
FileItemList::iterator it;
|
|
FileItem* child;
|
|
|
|
// we have to mark current items as deprecated
|
|
for (it=this->children.begin();
|
|
it!=this->children.end(); ++it) {
|
|
child = static_cast<FileItem*>(*it);
|
|
child->removed = true;
|
|
}
|
|
|
|
//PRINTF("FS: Loading files for %p (%s)\n", fileitem, fileitem->displayname);
|
|
#ifdef USE_PIDLS
|
|
{
|
|
IShellFolder* pFolder = NULL;
|
|
|
|
if (this == rootitem)
|
|
pFolder = shl_idesktop;
|
|
else
|
|
shl_idesktop->BindToObject(this->fullpidl,
|
|
NULL,
|
|
IID_IShellFolder,
|
|
(LPVOID *)&pFolder);
|
|
|
|
if (pFolder != NULL) {
|
|
IEnumIDList *pEnum = NULL;
|
|
ULONG c, fetched;
|
|
|
|
/* get the interface to enumerate subitems */
|
|
pFolder->EnumObjects(win_get_window(),
|
|
SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnum);
|
|
|
|
if (pEnum != NULL) {
|
|
LPITEMIDLIST itempidl[256];
|
|
SFGAOF attribs[256];
|
|
|
|
/* enumerate the items in the folder */
|
|
while (pEnum->Next(256, itempidl, &fetched) == S_OK && fetched > 0) {
|
|
/* request the SFGAO_FOLDER attribute to know what of the
|
|
item is a folder */
|
|
for (c=0; c<fetched; ++c) {
|
|
attribs[c] = SFGAO_FOLDER;
|
|
pFolder->GetAttributesOf(1, (LPCITEMIDLIST *)itempidl, attribs+c);
|
|
}
|
|
|
|
/* generate the FileItems */
|
|
for (c=0; c<fetched; ++c) {
|
|
LPITEMIDLIST fullpidl = concat_pidl(this->fullpidl,
|
|
itempidl[c]);
|
|
|
|
child = get_fileitem_by_fullpidl(fullpidl, false);
|
|
if (!child) {
|
|
child = new FileItem(this);
|
|
|
|
child->pidl = itempidl[c];
|
|
child->fullpidl = fullpidl;
|
|
child->attrib = attribs[c];
|
|
|
|
update_by_pidl(child);
|
|
put_fileitem(child);
|
|
}
|
|
else {
|
|
ASSERT(child->parent == this);
|
|
free_pidl(fullpidl);
|
|
free_pidl(itempidl[c]);
|
|
}
|
|
|
|
this->insertChildSorted(child);
|
|
}
|
|
}
|
|
|
|
pEnum->Release();
|
|
}
|
|
|
|
if (pFolder != shl_idesktop)
|
|
pFolder->Release();
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
char buf[MAX_PATH], path[MAX_PATH], tmp[32];
|
|
|
|
ustrcpy(path, this->filename.c_str());
|
|
put_backslash(path);
|
|
|
|
replace_filename(buf,
|
|
path,
|
|
uconvert_ascii("*.*", tmp),
|
|
sizeof(buf));
|
|
|
|
#ifdef WORKAROUND_64BITS_SUPPORT
|
|
// we cannot use the for_each_file's 'param' to wrap a 64-bits pointer
|
|
for_each_child_callback_param = this;
|
|
for_each_file(buf, FA_TO_SHOW, for_each_child_callback, 0);
|
|
#else
|
|
for_each_file(buf, FA_TO_SHOW,
|
|
for_each_child_callback,
|
|
(int)this);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// check old file-items (maybe removed directories or file-items)
|
|
for (it=this->children.begin();
|
|
it!=this->children.end(); ) {
|
|
child = static_cast<FileItem*>(*it);
|
|
if (child->removed) {
|
|
it = this->children.erase(it);
|
|
|
|
fileitems_map->erase(fileitems_map->find(child->keyname));
|
|
delete child;
|
|
}
|
|
else
|
|
++it;
|
|
}
|
|
|
|
// now this file-item is updated
|
|
this->version = current_file_system_version;
|
|
}
|
|
|
|
return this->children;
|
|
}
|
|
|
|
bool FileItem::hasExtension(const base::string& csv_extensions)
|
|
{
|
|
ASSERT(this->filename != NOTINITIALIZED);
|
|
|
|
return base::has_file_extension(this->filename, csv_extensions);
|
|
}
|
|
|
|
BITMAP* FileItem::getThumbnail()
|
|
{
|
|
ThumbnailMap::iterator it = thumbnail_map->find(this->filename);
|
|
if (it != thumbnail_map->end())
|
|
return it->second;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
void FileItem::setThumbnail(BITMAP* thumbnail)
|
|
{
|
|
// destroy the current thumbnail of the file (if exists)
|
|
ThumbnailMap::iterator it = thumbnail_map->find(this->filename);
|
|
if (it != thumbnail_map->end()) {
|
|
destroy_bitmap(it->second);
|
|
thumbnail_map->erase(it);
|
|
}
|
|
|
|
// insert the new one in the map
|
|
thumbnail_map->insert(std::make_pair(this->filename, thumbnail));
|
|
}
|
|
|
|
FileItem::FileItem(FileItem* parent)
|
|
{
|
|
//PRINTF("FS: Creating %p fileitem with parent %p\n", this, parent);
|
|
|
|
this->keyname = NOTINITIALIZED;
|
|
this->filename = NOTINITIALIZED;
|
|
this->displayname = NOTINITIALIZED;
|
|
this->parent = parent;
|
|
this->version = current_file_system_version;
|
|
this->removed = false;
|
|
#ifdef USE_PIDLS
|
|
this->pidl = NULL;
|
|
this->fullpidl = NULL;
|
|
this->attrib = 0;
|
|
#else
|
|
this->attrib = 0;
|
|
#endif
|
|
}
|
|
|
|
FileItem::~FileItem()
|
|
{
|
|
PRINTF("FS: Destroying FileItem() with parent %p\n", parent);
|
|
|
|
#ifdef USE_PIDLS
|
|
if (this->fullpidl && this->fullpidl != this->pidl) {
|
|
free_pidl(this->fullpidl);
|
|
this->fullpidl = NULL;
|
|
}
|
|
|
|
if (this->pidl) {
|
|
free_pidl(this->pidl);
|
|
this->pidl = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileItem::insertChildSorted(FileItem* child)
|
|
{
|
|
// this file-item wasn't removed from the last lookup
|
|
child->removed = false;
|
|
|
|
// if the fileitem is already in the list we can go back
|
|
if (std::find(children.begin(), children.end(), child) != children.end())
|
|
return;
|
|
|
|
bool inserted = false;
|
|
|
|
for (FileItemList::iterator
|
|
it=children.begin(); it!=children.end(); ++it) {
|
|
if (*static_cast<FileItem*>(*it) > *child) {
|
|
children.insert(it, child);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!inserted)
|
|
children.push_back(child);
|
|
}
|
|
|
|
/**
|
|
* Compares two FileItems.
|
|
*
|
|
* Based on 'ustricmp' of Allegro. It makes sure that eg "foo.bar"
|
|
* comes before "foo-1.bar", and also that "foo9.bar" comes before
|
|
* "foo10.bar".
|
|
*/
|
|
int FileItem::compare(const FileItem& that) const
|
|
{
|
|
if (IS_FOLDER(this)) {
|
|
if (!IS_FOLDER(&that))
|
|
return -1;
|
|
}
|
|
else if (IS_FOLDER(&that))
|
|
return 1;
|
|
|
|
{
|
|
int c1, c2;
|
|
int x1, x2;
|
|
char *t1, *t2;
|
|
const char* s1 = this->displayname.c_str();
|
|
const char* s2 = that.displayname.c_str();
|
|
|
|
for (;;) {
|
|
c1 = utolower(ugetxc(&s1));
|
|
c2 = utolower(ugetxc(&s2));
|
|
|
|
if ((c1 >= '0') && (c1 <= '9') && (c2 >= '0') && (c2 <= '9')) {
|
|
x1 = ustrtol(s1 - ucwidth(c1), &t1, 10);
|
|
x2 = ustrtol(s2 - ucwidth(c2), &t2, 10);
|
|
if (x1 != x2)
|
|
return x1 - x2;
|
|
else if (t1 - s1 != t2 - s2)
|
|
return (t2 - s2) - (t1 - s1);
|
|
s1 = t1;
|
|
s2 = t2;
|
|
}
|
|
else if (c1 != c2) {
|
|
if (!c1)
|
|
return -1;
|
|
else if (!c2)
|
|
return 1;
|
|
else if (c1 == '.')
|
|
return -1;
|
|
else if (c2 == '.')
|
|
return 1;
|
|
return c1 - c2;
|
|
}
|
|
|
|
if (!c1)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// PIDLS: Only for Win32
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef USE_PIDLS
|
|
|
|
/* updates the names of the file-item through its PIDL */
|
|
static void update_by_pidl(FileItem* fileitem)
|
|
{
|
|
STRRET strret;
|
|
TCHAR pszName[MAX_PATH];
|
|
IShellFolder *pFolder = NULL;
|
|
|
|
if (fileitem == rootitem)
|
|
pFolder = shl_idesktop;
|
|
else {
|
|
ASSERT(fileitem->parent);
|
|
shl_idesktop->BindToObject(fileitem->parent->fullpidl,
|
|
NULL,
|
|
IID_IShellFolder,
|
|
(LPVOID *)&pFolder);
|
|
}
|
|
|
|
/****************************************/
|
|
/* get the file name */
|
|
|
|
if (pFolder != NULL &&
|
|
pFolder->GetDisplayNameOf(fileitem->pidl,
|
|
SHGDN_NORMAL | SHGDN_FORPARSING,
|
|
&strret) == S_OK) {
|
|
StrRetToBuf(&strret, fileitem->pidl, pszName, MAX_PATH);
|
|
fileitem->filename = pszName;
|
|
}
|
|
else if (shl_idesktop->GetDisplayNameOf(fileitem->fullpidl,
|
|
SHGDN_NORMAL | SHGDN_FORPARSING,
|
|
&strret) == S_OK) {
|
|
StrRetToBuf(&strret, fileitem->fullpidl, pszName, MAX_PATH);
|
|
fileitem->filename = pszName;
|
|
}
|
|
else
|
|
fileitem->filename = "ERR";
|
|
|
|
/****************************************/
|
|
/* get the name to display */
|
|
|
|
if (fileitem->isFolder() &&
|
|
pFolder &&
|
|
pFolder->GetDisplayNameOf(fileitem->pidl,
|
|
SHGDN_INFOLDER,
|
|
&strret) == S_OK) {
|
|
StrRetToBuf(&strret, fileitem->pidl, pszName, MAX_PATH);
|
|
fileitem->displayname = pszName;
|
|
}
|
|
else if (fileitem->isFolder() &&
|
|
shl_idesktop->GetDisplayNameOf(fileitem->fullpidl,
|
|
SHGDN_INFOLDER,
|
|
&strret) == S_OK) {
|
|
StrRetToBuf(&strret, fileitem->fullpidl, pszName, MAX_PATH);
|
|
fileitem->displayname = pszName;
|
|
}
|
|
else {
|
|
fileitem->displayname = base::get_file_name(fileitem->filename);
|
|
}
|
|
|
|
if (pFolder != NULL && pFolder != shl_idesktop) {
|
|
pFolder->Release();
|
|
}
|
|
}
|
|
|
|
static LPITEMIDLIST concat_pidl(LPITEMIDLIST pidlHead, LPITEMIDLIST pidlTail)
|
|
{
|
|
LPITEMIDLIST pidlNew;
|
|
UINT cb1, cb2;
|
|
|
|
ASSERT(pidlHead);
|
|
ASSERT(pidlTail);
|
|
|
|
cb1 = get_pidl_size(pidlHead) - sizeof(pidlHead->mkid.cb);
|
|
cb2 = get_pidl_size(pidlTail);
|
|
|
|
pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(cb1 + cb2);
|
|
if (pidlNew) {
|
|
CopyMemory(pidlNew, pidlHead, cb1);
|
|
CopyMemory(((LPSTR)pidlNew) + cb1, pidlTail, cb2);
|
|
}
|
|
|
|
return pidlNew;
|
|
}
|
|
|
|
static UINT get_pidl_size(LPITEMIDLIST pidl)
|
|
{
|
|
UINT cbTotal = 0;
|
|
|
|
if (pidl) {
|
|
cbTotal += sizeof(pidl->mkid.cb); /* null terminator */
|
|
|
|
while (pidl) {
|
|
cbTotal += pidl->mkid.cb;
|
|
pidl = get_next_pidl(pidl);
|
|
}
|
|
}
|
|
|
|
return cbTotal;
|
|
}
|
|
|
|
static LPITEMIDLIST get_next_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
if (pidl != NULL && pidl->mkid.cb > 0) {
|
|
pidl = (LPITEMIDLIST)(((LPBYTE)(pidl)) + pidl->mkid.cb);
|
|
if (pidl->mkid.cb > 0)
|
|
return pidl;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static LPITEMIDLIST get_last_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
LPITEMIDLIST pidlLast = pidl;
|
|
LPITEMIDLIST pidlNew = NULL;
|
|
|
|
while (pidl) {
|
|
pidlLast = pidl;
|
|
pidl = get_next_pidl(pidl);
|
|
}
|
|
|
|
if (pidlLast) {
|
|
ULONG sz = get_pidl_size(pidlLast);
|
|
pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
|
|
CopyMemory(pidlNew, pidlLast, sz);
|
|
}
|
|
|
|
return pidlNew;
|
|
}
|
|
|
|
static LPITEMIDLIST clone_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
ULONG sz = get_pidl_size(pidl);
|
|
LPITEMIDLIST pidlNew = (LPITEMIDLIST)shl_imalloc->Alloc(sz);
|
|
|
|
CopyMemory(pidlNew, pidl, sz);
|
|
|
|
return pidlNew;
|
|
}
|
|
|
|
static LPITEMIDLIST remove_last_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
LPITEMIDLIST pidlFirst = pidl;
|
|
LPITEMIDLIST pidlLast = pidl;
|
|
|
|
while (pidl) {
|
|
pidlLast = pidl;
|
|
pidl = get_next_pidl(pidl);
|
|
}
|
|
|
|
if (pidlLast)
|
|
pidlLast->mkid.cb = 0;
|
|
|
|
return pidlFirst;
|
|
}
|
|
|
|
static void free_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
shl_imalloc->Free(pidl);
|
|
}
|
|
|
|
static base::string get_key_for_pidl(LPITEMIDLIST pidl)
|
|
{
|
|
#if 0
|
|
char *key = base_malloc(get_pidl_size(pidl)+1);
|
|
UINT c, i = 0;
|
|
|
|
while (pidl) {
|
|
for (c=0; c<pidl->mkid.cb; ++c) {
|
|
if (pidl->mkid.abID[c])
|
|
key[i++] = pidl->mkid.abID[c];
|
|
else
|
|
key[i++] = 1;
|
|
}
|
|
pidl = get_next_pidl(pidl);
|
|
}
|
|
key[i] = 0;
|
|
|
|
return key;
|
|
#else
|
|
STRRET strret;
|
|
TCHAR pszName[MAX_PATH];
|
|
char key[4096];
|
|
int len;
|
|
|
|
ustrcpy(key, empty_string);
|
|
|
|
// Go pidl by pidl from the fullpidl to the root (desktop)
|
|
//PRINTF("FS: ***\n");
|
|
pidl = clone_pidl(pidl);
|
|
while (pidl->mkid.cb > 0) {
|
|
if (shl_idesktop->GetDisplayNameOf(pidl,
|
|
SHGDN_INFOLDER | SHGDN_FORPARSING,
|
|
&strret) == S_OK) {
|
|
StrRetToBuf(&strret, pidl, pszName, MAX_PATH);
|
|
|
|
//PRINTF("FS: + %s\n", pszName);
|
|
|
|
len = ustrlen(pszName);
|
|
if (len > 0) {
|
|
if (*key) {
|
|
if (pszName[len-1] != '\\') {
|
|
memmove(key+len+1, key, ustrlen(key)+1);
|
|
key[len] = '\\';
|
|
}
|
|
else
|
|
memmove(key+len, key, ustrlen(key)+1);
|
|
}
|
|
else
|
|
key[len] = 0;
|
|
|
|
memcpy(key, pszName, len);
|
|
}
|
|
}
|
|
remove_last_pidl(pidl);
|
|
}
|
|
free_pidl(pidl);
|
|
|
|
//PRINTF("FS: =%s\n***\n", key);
|
|
return key;
|
|
#endif
|
|
}
|
|
|
|
static FileItem* get_fileitem_by_fullpidl(LPITEMIDLIST fullpidl, bool create_if_not)
|
|
{
|
|
FileItemMap::iterator it = fileitems_map->find(get_key_for_pidl(fullpidl));
|
|
if (it != fileitems_map->end())
|
|
return it->second;
|
|
|
|
if (!create_if_not)
|
|
return NULL;
|
|
|
|
// new file-item
|
|
FileItem* fileitem = new FileItem(NULL);
|
|
fileitem->fullpidl = clone_pidl(fullpidl);
|
|
|
|
fileitem->attrib = SFGAO_FOLDER;
|
|
HRESULT hr = shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST *)&fileitem->fullpidl,
|
|
&fileitem->attrib);
|
|
if (hr == S_OK) {
|
|
LPITEMIDLIST parent_fullpidl = clone_pidl(fileitem->fullpidl);
|
|
remove_last_pidl(parent_fullpidl);
|
|
|
|
fileitem->pidl = get_last_pidl(fileitem->fullpidl);
|
|
fileitem->parent = get_fileitem_by_fullpidl(parent_fullpidl, true);
|
|
|
|
free_pidl(parent_fullpidl);
|
|
}
|
|
|
|
update_by_pidl(fileitem);
|
|
put_fileitem(fileitem);
|
|
|
|
//PRINTF("FS: fileitem %p created %s with parent %p\n", fileitem, fileitem->keyname.c_str(), fileitem->parent);
|
|
|
|
return fileitem;
|
|
}
|
|
|
|
/**
|
|
* Inserts the @a fileitem in the hash map of items.
|
|
*/
|
|
static void put_fileitem(FileItem* fileitem)
|
|
{
|
|
ASSERT(fileitem->filename != NOTINITIALIZED);
|
|
ASSERT(fileitem->keyname == NOTINITIALIZED);
|
|
|
|
fileitem->keyname = get_key_for_pidl(fileitem->fullpidl);
|
|
|
|
ASSERT(fileitem->keyname != NOTINITIALIZED);
|
|
|
|
#ifdef DEBUGMODE
|
|
FileItemMap::iterator it = fileitems_map->find(get_key_for_pidl(fileitem->fullpidl));
|
|
ASSERT(it == fileitems_map->end());
|
|
#endif
|
|
|
|
// insert this file-item in the hash-table
|
|
fileitems_map->insert(std::make_pair(fileitem->keyname, fileitem));
|
|
}
|
|
|
|
#else
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Allegro for_each_file: Portable
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
static FileItem* get_fileitem_by_path(const base::string& path, bool create_if_not)
|
|
{
|
|
if (path.empty())
|
|
return rootitem;
|
|
|
|
FileItemMap::iterator it = fileitems_map->find(get_key_for_filename(path));
|
|
if (it != fileitems_map->end())
|
|
return it->second;
|
|
|
|
if (!create_if_not)
|
|
return NULL;
|
|
|
|
// get the attributes of the file
|
|
int attrib = 0;
|
|
if (!file_exists(path.c_str(), FA_ALL, &attrib)) {
|
|
if (!FileSystemModule::instance()->dirExists(path))
|
|
return NULL;
|
|
attrib = FA_DIREC;
|
|
}
|
|
|
|
// new file-item
|
|
FileItem* fileitem = new FileItem(NULL);
|
|
|
|
fileitem->filename = path;
|
|
fileitem->displayname = base::get_file_name(path);
|
|
fileitem->attrib = attrib;
|
|
|
|
// get the parent
|
|
{
|
|
base::string parent_path = remove_backslash_if_needed(base::join_path(base::get_file_path(path), ""));
|
|
fileitem->parent = get_fileitem_by_path(parent_path, true);
|
|
}
|
|
|
|
put_fileitem(fileitem);
|
|
|
|
return fileitem;
|
|
}
|
|
|
|
static void for_each_child_callback(const char *filename, int attrib, int param)
|
|
{
|
|
#ifdef WORKAROUND_64BITS_SUPPORT
|
|
FileItem* fileitem = for_each_child_callback_param;
|
|
#else
|
|
FileItem* fileitem = (FileItem*)param;
|
|
#endif
|
|
FileItem* child;
|
|
const char *filename_without_path = get_filename(filename);
|
|
|
|
if (*filename_without_path == '.' &&
|
|
(ustrcmp(filename_without_path, ".") == 0 ||
|
|
ustrcmp(filename_without_path, "..") == 0))
|
|
return;
|
|
|
|
child = get_fileitem_by_path(filename, false);
|
|
if (!child) {
|
|
ASSERT(fileitem != NULL);
|
|
child = new FileItem(fileitem);
|
|
|
|
child->filename = filename;
|
|
child->displayname = filename_without_path;
|
|
child->attrib = attrib;
|
|
|
|
put_fileitem(child);
|
|
}
|
|
else {
|
|
ASSERT(child->parent == fileitem);
|
|
}
|
|
|
|
fileitem->insertChildSorted(child);
|
|
}
|
|
|
|
static base::string remove_backslash_if_needed(const base::string& filename)
|
|
{
|
|
if (!filename.empty() && base::is_path_separator(*(filename.end()-1))) {
|
|
int len = filename.size();
|
|
#ifdef HAVE_DRIVES
|
|
// if the name is C:\ or something like that, the backslash isn't
|
|
// removed
|
|
if (len == 3 && filename[1] == ':')
|
|
return filename;
|
|
#else
|
|
// this is just the root '/' slash
|
|
if (len == 1)
|
|
return filename;
|
|
#endif
|
|
return base::remove_path_separator(filename);
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
static base::string get_key_for_filename(const base::string& filename)
|
|
{
|
|
base::string buf(filename);
|
|
|
|
#if !defined CASE_SENSITIVE
|
|
buf.tolower();
|
|
#endif
|
|
buf = base::fix_path_separators(buf);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void put_fileitem(FileItem* fileitem)
|
|
{
|
|
ASSERT(fileitem->filename != NOTINITIALIZED);
|
|
ASSERT(fileitem->keyname == NOTINITIALIZED);
|
|
|
|
fileitem->keyname = get_key_for_filename(fileitem->filename);
|
|
|
|
ASSERT(fileitem->keyname != NOTINITIALIZED);
|
|
|
|
// insert this file-item in the hash-table
|
|
fileitems_map->insert(std::make_pair(fileitem->keyname, fileitem));
|
|
}
|
|
|
|
#endif
|