/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2011-2017, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */
 
#include "FileManager.h"
 
#include <new>
 
#include <AutoDeleter.h>
#include <AutoLocker.h>
 
#include "LocatableDirectory.h"
#include "LocatableFile.h"
#include "SourceFile.h"
#include "StringUtils.h"
#include "TeamFileManagerSettings.h"
 
 
// #pragma mark - EntryPath
 
 
struct FileManager::EntryPath {
	const char*	directory;
	const char*	name;
 
	EntryPath(const char* directory, const char* name)
		:
		directory(directory),
		name(name)
	{
	}
 
	EntryPath(const BString& directory, const BString& name)
		:
		directory(directory.Length() > 0 ? directory.String() : NULL),
		name(name.String())
	{
	}
 
	EntryPath(const LocatableEntry* entry)
		:
		directory(NULL),
		name(entry->Name())
	{
		LocatableDirectory* parent = entry->Parent();
		if (parent != NULL && strlen(parent->Path()) > 0)
			directory = parent->Path();
	}
 
	EntryPath(const EntryPath& other)
		:
		directory(other.directory),
		name(other.name)
	{
	}
 
	size_t HashValue() const
	{
		return StringUtils::HashValue(directory)
			^ StringUtils::HashValue(name);
	}
 
	bool operator==(const EntryPath& other) const
	{
		if (directory != other.directory
			&& (directory == NULL || other.directory == NULL
				|| strcmp(directory, other.directory) != 0)) {
			return false;
		}
 
		return strcmp(name, other.name) == 0;
	}
};
 
 
// #pragma mark - EntryHashDefinition
 
 
struct FileManager::EntryHashDefinition {
	typedef EntryPath		KeyType;
	typedef	LocatableEntry	ValueType;
 
	size_t HashKey(const EntryPath& key) const
	{
		return key.HashValue();
	}
 
	size_t Hash(const LocatableEntry* value) const
	{
		return HashKey(EntryPath(value));
	}
 
	bool Compare(const EntryPath& key, const LocatableEntry* value) const
	{
		return EntryPath(value) == key;
	}
 
	LocatableEntry*& GetLink(LocatableEntry* value) const
	{
		return value->fNext;
	}
};
 
 
// #pragma mark - Domain
 
 
class FileManager::Domain : private LocatableEntryOwner {
public:
	Domain(FileManager* manager, bool isLocal)
		:
		fManager(manager),
		fIsLocal(isLocal)
	{
	}
 
	~Domain()
	{
		LocatableEntry* entry = fEntries.Clear(true);
		while (entry != NULL) {
			LocatableEntry* next = entry->fNext;
			entry->ReleaseReference();
			entry = next;
		}
	}
 
	status_t Init()
	{
		status_t error = fEntries.Init();
		if (error != B_OK)
			return error;
 
		return B_OK;
	}
 
	LocatableFile* GetFile(const BString& directoryPath,
		const BString& relativePath)
	{
		if (directoryPath.Length() == 0 || relativePath[0] == '/')
			return GetFile(relativePath);
		return GetFile(BString(directoryPath) << '/' << relativePath);
	}
 
	LocatableFile* GetFile(const BString& path)
	{
		BString directoryPath;
		BString name;
		_SplitPath(path, directoryPath, name);
		LocatableFile* file = _GetFile(directoryPath, name);
		if (file == NULL)
			return NULL;
 
		// try to auto-locate the file
		if (LocatableDirectory* directory = file->Parent()) {
			if (directory->State() == LOCATABLE_ENTRY_UNLOCATED) {
				// parent not yet located -- try locate with the entry's path
				BString path;
				file->GetPath(path);
				_LocateEntry(file, path, true, true);
			} else {
				// parent already located -- locate the entry in the parent
				BString locatedDirectoryPath;
				if (directory->GetLocatedPath(locatedDirectoryPath))
					_LocateEntryInParentDir(file, locatedDirectoryPath, true);
			}
		}
 
		return file;
	}
 
	void EntryLocated(const BString& path, const BString& locatedPath)
	{
		BString directory;
		BString name;
		_SplitPath(path, directory, name);
 
		LocatableEntry* entry = _LookupEntry(EntryPath(directory, name));
		if (entry == NULL)
			return;
 
		_LocateEntry(entry, locatedPath, false, true);
	}
 
private:
	virtual bool Lock()
	{
		return fManager->Lock();
	}
 
	virtual void Unlock()
	{
		fManager->Unlock();
	}
 
	virtual void LocatableEntryUnused(LocatableEntry* entry)
	{
		AutoLocker<FileManager> lock(fManager);
		if (fEntries.Lookup(EntryPath(entry)) == entry)
			fEntries.Remove(entry);
 
		LocatableDirectory* parent = entry->Parent();
		if (parent != NULL)
			parent->RemoveEntry(entry);
	}
 
	bool _LocateDirectory(LocatableDirectory* directory,
		const BString& locatedPath, bool implicit)
	{
		if (directory == NULL
			|| directory->State() != LOCATABLE_ENTRY_UNLOCATED) {
			return false;
		}
 
		if (!_LocateEntry(directory, locatedPath, implicit, true))
			return false;
 
		_LocateEntries(directory, locatedPath, implicit);
 
		return true;
	}
 
	bool _LocateEntry(LocatableEntry* entry, const BString& locatedPath,
		bool implicit, bool locateAncestors)
	{
		if (implicit && entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
			return false;
 
		struct stat st;
		if (stat(locatedPath, &st) != 0)
			return false;
 
		if (S_ISDIR(st.st_mode)) {
			LocatableDirectory* directory
				= dynamic_cast<LocatableDirectory*>(entry);
			if (directory == NULL)
				return false;
			entry->SetLocatedPath(locatedPath, implicit);
		} else if (S_ISREG(st.st_mode)) {
			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
			if (file == NULL)
				return false;
			entry->SetLocatedPath(locatedPath, implicit);
		}
 
		// locate the ancestor directories, if requested
		if (locateAncestors) {
			BString locatedDirectory;
			BString locatedName;
			_SplitPath(locatedPath, locatedDirectory, locatedName);
			if (locatedName == entry->Name())
				_LocateDirectory(entry->Parent(), locatedDirectory, implicit);
		}
 
		return true;
	}
 
	bool _LocateEntryInParentDir(LocatableEntry* entry,
		const BString& locatedDirectoryPath, bool implicit)
	{
		// construct the located entry path
		BString locatedEntryPath(locatedDirectoryPath);
		int32 pathLength = locatedEntryPath.Length();
		if (pathLength >= 1 && locatedEntryPath[pathLength - 1] != '/')
			locatedEntryPath << '/';
		locatedEntryPath << entry->Name();
 
		return _LocateEntry(entry, locatedEntryPath, implicit, false);
	}
 
	void _LocateEntries(LocatableDirectory* directory,
		const BString& locatedPath, bool implicit)
	{
		for (LocatableEntryList::ConstIterator it
				= directory->Entries().GetIterator();
			LocatableEntry* entry = it.Next();) {
			if (entry->State() == LOCATABLE_ENTRY_LOCATED_EXPLICITLY)
				continue;
 
			 if (_LocateEntryInParentDir(entry, locatedPath, implicit)) {
				// recurse for directories
				if (LocatableDirectory* subDir
						= dynamic_cast<LocatableDirectory*>(entry)) {
					BString locatedEntryPath;
					if (subDir->GetLocatedPath(locatedEntryPath))
						_LocateEntries(subDir, locatedEntryPath, implicit);
				}
			}
		}
	}
 
	LocatableFile* _GetFile(const BString& directoryPath, const BString& name)
	{
		BString normalizedDirPath;
		_NormalizePath(directoryPath, normalizedDirPath);
 
		// if already known return the file
		LocatableEntry* entry = _LookupEntry(EntryPath(normalizedDirPath, name));
		if (entry != NULL) {
			LocatableFile* file = dynamic_cast<LocatableFile*>(entry);
			if (file == NULL)
				return NULL;
 
			if (file->AcquireReference() == 0)
				fEntries.Remove(file);
			else
				return file;
		}
 
		// no such file yet -- create it
		LocatableDirectory* directory = _GetDirectory(normalizedDirPath);
		if (directory == NULL)
			return NULL;
 
		LocatableFile* file = new(std::nothrow) LocatableFile(this, directory,
			name);
		if (file == NULL) {
			directory->ReleaseReference();
			return NULL;
		}
 
		directory->AddEntry(file);
 
		fEntries.Insert(file);
 
		return file;
	}
 
	LocatableDirectory* _GetDirectory(const BString& path)
	{
		BString directoryPath;
		BString fileName;
		_SplitNormalizedPath(path, directoryPath, fileName);
 
		// if already know return the directory
		LocatableEntry* entry
			= _LookupEntry(EntryPath(directoryPath, fileName));
		if (entry != NULL) {
			LocatableDirectory* directory
				= dynamic_cast<LocatableDirectory*>(entry);
			if (directory == NULL)
				return NULL;
			directory->AcquireReference();
			return directory;
		}
 
		// get the parent directory
		LocatableDirectory* parentDirectory = NULL;
		if (directoryPath.Length() > 0) {
			parentDirectory = _GetDirectory(directoryPath);
			if (parentDirectory == NULL)
				return NULL;
		}
 
		// create a new directory
		LocatableDirectory* directory = new(std::nothrow) LocatableDirectory(
			this, parentDirectory, path);
		if (directory == NULL) {
			parentDirectory->ReleaseReference();
			return NULL;
		}
 
		// auto-locate, if possible
		if (fIsLocal) {
			BString dirPath;
			directory->GetPath(dirPath);
			directory->SetLocatedPath(dirPath, false);
		} else if (parentDirectory != NULL
			&& parentDirectory->State() != LOCATABLE_ENTRY_UNLOCATED) {
			BString locatedDirectoryPath;
			if (parentDirectory->GetLocatedPath(locatedDirectoryPath))
				_LocateEntryInParentDir(directory, locatedDirectoryPath, true);
		}
 
		if (parentDirectory != NULL)
			parentDirectory->AddEntry(directory);
 
		fEntries.Insert(directory);
		return directory;
	}
 
	LocatableEntry* _LookupEntry(const EntryPath& entryPath)
	{
		LocatableEntry* entry = fEntries.Lookup(entryPath);
		if (entry == NULL)
			return NULL;
 
		// if already unreferenced, remove it
		if (entry->CountReferences() == 0) {
			fEntries.Remove(entry);
			return NULL;
		}
 
		return entry;
	}
 
	void _NormalizePath(const BString& path, BString& _normalizedPath)
	{
		BString normalizedPath;
		char* buffer = normalizedPath.LockBuffer(path.Length());
		int32 outIndex = 0;
		const char* remaining = path.String();
 
		while (*remaining != '\0') {
			// collapse repeated slashes
			if (*remaining == '/') {
				buffer[outIndex++] = '/';
				remaining++;
				while (*remaining == '/')
					remaining++;
			}
 
			if (*remaining == '\0') {
				// remove trailing slash (unless it's "/" only)
				if (outIndex > 1)
					outIndex--;
				break;
			}
 
			// skip "." components
			if (*remaining == '.') {
				if (remaining[1] == '\0')
					break;
 
				if (remaining[1] == '/') {
					remaining += 2;
					while (*remaining == '/')
						remaining++;
					continue;
				}
			}
 
			// copy path component
			while (*remaining != '\0' && *remaining != '/')
				buffer[outIndex++] = *(remaining++);
		}
 
		// If the path didn't change, use the original path (BString's copy on
		// write mechanism) rather than the new string.
		if (outIndex == path.Length()) {
			_normalizedPath = path;
		} else {
			normalizedPath.UnlockBuffer(outIndex);
			_normalizedPath = normalizedPath;
		}
	}
 
	void _SplitPath(const BString& path, BString& _directory, BString& _name)
	{
		BString normalized;
		_NormalizePath(path, normalized);
		_SplitNormalizedPath(normalized, _directory, _name);
	}
 
	void _SplitNormalizedPath(const BString& path, BString& _directory,
		BString& _name)
	{
		// handle single component (including root dir) cases
		int32 lastSlash = path.FindLast('/');
		if (lastSlash < 0 || path.Length() == 1) {
			_directory = (const char*)NULL;
			_name = path;
			return;
		}
 
		// handle root dir + one component and multi component cases
		if (lastSlash == 0)
			_directory = "/";
		else
			_directory.SetTo(path, lastSlash);
		_name = path.String() + (lastSlash + 1);
	}
 
private:
	FileManager*		fManager;
	LocatableEntryTable	fEntries;
	bool				fIsLocal;
};
 
 
// #pragma mark - SourceFileEntry
 
 
struct FileManager::SourceFileEntry : public SourceFileOwner {
 
	FileManager*		manager;
	BString				path;
	SourceFile*			file;
	SourceFileEntry*	next;
 
	SourceFileEntry(FileManager* manager, const BString& path)
		:
		manager(manager),
		path(path),
		file(NULL)
	{
	}
 
	virtual void SourceFileUnused(SourceFile* sourceFile)
	{
		manager->_SourceFileUnused(this);
	}
 
	virtual void SourceFileDeleted(SourceFile* sourceFile)
	{
		// We have already been removed from the table, so commit suicide.
		delete this;
	}
};
 
 
// #pragma mark - SourceFileHashDefinition
 
 
struct FileManager::SourceFileHashDefinition {
	typedef BString			KeyType;
	typedef	SourceFileEntry	ValueType;
 
	size_t HashKey(const BString& key) const
	{
		return StringUtils::HashValue(key);
	}
 
	size_t Hash(const SourceFileEntry* value) const
	{
		return HashKey(value->path);
	}
 
	bool Compare(const BString& key, const SourceFileEntry* value) const
	{
		return value->path == key;
	}
 
	SourceFileEntry*& GetLink(SourceFileEntry* value) const
	{
		return value->next;
	}
};
 
 
// #pragma mark - FileManager
 
 
FileManager::FileManager()
	:
	fLock("file manager"),
	fTargetDomain(NULL),
	fSourceDomain(NULL),
	fSourceFiles(NULL)
{
}
 
 
FileManager::~FileManager()
{
	delete fTargetDomain;
	delete fSourceDomain;
 
	SourceFileEntry* entry = fSourceFiles->Clear();
	while (entry != NULL) {
		SourceFileEntry* next = entry->next;
		delete entry;
		entry = next;
	}
	delete fSourceFiles;
}
 
 
status_t
FileManager::Init(bool targetIsLocal)
{
	status_t error = fLock.InitCheck();
	if (error != B_OK)
		return error;
 
	// create target domain
	fTargetDomain = new(std::nothrow) Domain(this, targetIsLocal);
	if (fTargetDomain == NULL)
		return B_NO_MEMORY;
 
	error = fTargetDomain->Init();
	if (error != B_OK)
		return error;
 
	// create source domain
	fSourceDomain = new(std::nothrow) Domain(this, false);
	if (fSourceDomain == NULL)
		return B_NO_MEMORY;
 
	error = fSourceDomain->Init();
	if (error != B_OK)
		return error;
 
	// create source file table
	fSourceFiles = new(std::nothrow) SourceFileTable;
	if (fSourceFiles == NULL)
		return B_NO_MEMORY;
 
	error = fSourceFiles->Init();
	if (error != B_OK)
		return error;
 
	return B_OK;
}
 
 
LocatableFile*
FileManager::GetTargetFile(const BString& directory,
	const BString& relativePath)
{
	AutoLocker<FileManager> locker(this);
	return fTargetDomain->GetFile(directory, relativePath);
}
 
 
LocatableFile*
FileManager::GetTargetFile(const BString& path)
{
	AutoLocker<FileManager> locker(this);
	return fTargetDomain->GetFile(path);
}
 
 
void
FileManager::TargetEntryLocated(const BString& path,
	const BString& locatedPath)
{
	AutoLocker<FileManager> locker(this);
	fTargetDomain->EntryLocated(path, locatedPath);
}
 
 
LocatableFile*
FileManager::GetSourceFile(const BString& directory,
	const BString& relativePath)
{
	AutoLocker<FileManager> locker(this);
	LocatableFile* file = fSourceDomain->GetFile(directory, relativePath);
 
	return file;
}
 
 
LocatableFile*
FileManager::GetSourceFile(const BString& path)
{
	AutoLocker<FileManager> locker(this);
	LocatableFile* file = fSourceDomain->GetFile(path);
 
	return file;
}
 
 
status_t
FileManager::SourceEntryLocated(const BString& path,
	const BString& locatedPath)
{
	AutoLocker<FileManager> locker(this);
 
	// check if we already have this path mapped. If so,
	// first clear the mapping, as the user may be attempting
	// to correct an existing entry.
	SourceFileEntry* entry = _LookupSourceFile(path);
	if (entry != NULL)
		_SourceFileUnused(entry);
 
	fSourceDomain->EntryLocated(path, locatedPath);
 
	try {
		fSourceLocationMappings[path] = locatedPath;
	} catch (...) {
		return B_NO_MEMORY;
	}
 
	return B_OK;
}
 
 
status_t
FileManager::LoadSourceFile(LocatableFile* file, SourceFile*& _sourceFile)
{
	AutoLocker<FileManager> locker(this);
 
	// get the path
	BString path;
	BString originalPath;
	file->GetPath(originalPath);
	if (!file->GetLocatedPath(path)) {
		// see if this is a file we have a lazy mapping for.
		if (!_LocateFileIfMapped(originalPath, file)
			|| !file->GetLocatedPath(path)) {
			return B_ENTRY_NOT_FOUND;
		}
	}
 
	// we might already know the source file
	SourceFileEntry* entry = _LookupSourceFile(originalPath);
	if (entry != NULL) {
		entry->file->AcquireReference();
		_sourceFile = entry->file;
		return B_OK;
	}
 
	// create the hash table entry
	entry = new(std::nothrow) SourceFileEntry(this, originalPath);
	if (entry == NULL)
		return B_NO_MEMORY;
 
	// load the file
	SourceFile* sourceFile = new(std::nothrow) SourceFile(entry);
	if (sourceFile == NULL) {
		delete entry;
		return B_NO_MEMORY;
	}
	ObjectDeleter<SourceFile> sourceFileDeleter(sourceFile);
 
	entry->file = sourceFile;
 
	status_t error = sourceFile->Init(path);
	if (error != B_OK)
		return error;
 
	fSourceFiles->Insert(entry);
 
	_sourceFile = sourceFileDeleter.Detach();
	return B_OK;
}
 
 
status_t
FileManager::LoadLocationMappings(TeamFileManagerSettings* settings)
{
	AutoLocker<FileManager> locker(this);
	for (int32 i = 0; i < settings->CountSourceMappings(); i++) {
		BString sourcePath;
		BString locatedPath;
 
		if (settings->GetSourceMappingAt(i, sourcePath, locatedPath) != B_OK)
			return B_NO_MEMORY;
 
		try {
			fSourceLocationMappings[sourcePath] = locatedPath;
		} catch (...) {
			return B_NO_MEMORY;
		}
	}
 
	return B_OK;
}
 
 
status_t
FileManager::SaveLocationMappings(TeamFileManagerSettings* settings)
{
	AutoLocker<FileManager> locker(this);
 
	for (LocatedFileMap::const_iterator it = fSourceLocationMappings.begin();
		it != fSourceLocationMappings.end(); ++it) {
		status_t error = settings->AddSourceMapping(it->first, it->second);
		if (error != B_OK)
			return error;
	}
 
	return B_OK;
}
 
 
FileManager::SourceFileEntry*
FileManager::_LookupSourceFile(const BString& path)
{
	SourceFileEntry* entry = fSourceFiles->Lookup(path);
	if (entry == NULL)
		return NULL;
 
	// the entry might be unused already -- in that case remove it
	if (entry->file->CountReferences() == 0) {
		fSourceFiles->Remove(entry);
		return NULL;
	}
 
	return entry;
}
 
 
void
FileManager::_SourceFileUnused(SourceFileEntry* entry)
{
	AutoLocker<FileManager> locker(this);
 
	SourceFileEntry* otherEntry = fSourceFiles->Lookup(entry->path);
	if (otherEntry == entry)
		fSourceFiles->Remove(entry);
}
 
 
bool
FileManager::_LocateFileIfMapped(const BString& sourcePath,
	LocatableFile* file)
{
	// called with lock held
 
	LocatedFileMap::const_iterator it = fSourceLocationMappings.find(
		sourcePath);
	if (it != fSourceLocationMappings.end()
		&& file->State() != LOCATABLE_ENTRY_LOCATED_EXPLICITLY
		&& file->State() != LOCATABLE_ENTRY_LOCATED_IMPLICITLY) {
		fSourceDomain->EntryLocated(it->first, it->second);
		return true;
	}
 
	return false;
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: next.

V595 The 'parentDirectory' pointer was utilized before it was verified against nullptr. Check lines: 360, 369.