Allow .Deleted to work on directories. Fixes #2641. Patch by leper.

This was SVN commit r18916.
This commit is contained in:
wraitii 2016-11-10 22:52:39 +00:00
parent 01b667ab86
commit 12ad190a51
6 changed files with 215 additions and 28 deletions

View File

@ -22,13 +22,13 @@
# Choices are "x86_64" or "i386" (ppc and ppc64 not supported)
export ARCH=${ARCH:="x86_64"}
OSX_VERSION=`sw_vers -productVersion | grep -Eo "^\d+.\d+"`
OSX_VERSION='10.12'
# Set SDK and mimimum required OS X version
export SYSROOT=${SYSROOT:="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX$OSX_VERSION.sdk"}
export MIN_OSX_VERSION=${MIN_OSX_VERSION:="10.7"}
# 0 A.D. release version, e.g. Alpha 21 is 0.0.21
BUNDLE_VERSION=${BUNDLE_VERSION:="0.0.X"}
BUNDLE_VERSION=${BUNDLE_VERSION:="0.0.21"}
# Define compiler as "clang", this is all Mavericks supports.
# gcc symlinks may still exist, but they are simply clang with
@ -104,10 +104,17 @@ BUNDLE_SHAREDSUPPORT=$BUNDLE_CONTENTS/SharedSupport
# TODO: Do we really want to regenerate everything? (consider if one task fails)
# Build libraries against SDK
echo "\nBuilding libraries\n"
pushd ../../libraries/osx > /dev/null
./build-osx-libs.sh $JOBS --force-rebuild >> $build_log 2>&1 || die "Libraries build script failed"
popd > /dev/null
echo "\nSymlinking libraries\n"
cd ../../
ln -s /Users/Lancelot/Desktop/git-0AD/libraries
cd binaries/data/mods/public/
ln -s /Users/Lancelot/Desktop/git-0AD/binaries/data/mods/public/art
ln -s /Users/Lancelot/Desktop/git-0AD/binaries/data/mods/public/audio
cd ../../../../build/workspaces
#echo "\nBuilding libraries\n"
#pushd ../../libraries/osx > /dev/null
#./build-osx-libs.sh $JOBS --force-rebuild >> $build_log 2>&1 || die "Libraries build script failed"
#popd > /dev/null
# Clean and update workspaces
echo "\nGenerating workspaces\n"
@ -117,6 +124,8 @@ echo "\nGenerating workspaces\n"
pushd gcc > /dev/null
echo "\nBuilding game\n"
patch -p0 -i "/Users/Lancelot/diff.patch"
patch -p0 -i "/Users/Lancelot/diff2.patch"
(make clean && CC="$CC -arch $ARCH" CXX="$CXX -arch $ARCH" make ${JOBS}) >> $build_log 2>&1 || die "Game build failed!"
popd > /dev/null
@ -211,7 +220,7 @@ PlistBuddy -c "Add :CFBundleDevelopmentRegion string English" ${INFO_PLIST}
PlistBuddy -c "Add :CFBundleInfoDictionaryVersion string 6.0" ${INFO_PLIST}
PlistBuddy -c "Add :CFBundleIconFile string 0ad" ${INFO_PLIST}
PlistBuddy -c "Add :LSMinimumSystemVersion string ${BUNDLE_MIN_OSX_VERSION}" ${INFO_PLIST}
PlistBuddy -c "Add :NSHumanReadableCopyright string Copyright © 2015 Wildfire Games" ${INFO_PLIST}
PlistBuddy -c "Add :NSHumanReadableCopyright string Copyright © 2016 Wildfire Games" ${INFO_PLIST}
# TODO: Automatically create compressed DMG with hdiutil?
# (this is a bit complicated so I do it manually for now)

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013 Wildfire Games
/* Copyright (c) 2016 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -22,8 +22,10 @@
#include "lib/self_test.h"
#include "lib/file/vfs/vfs_tree.h"
#include "lib/file/common/file_loader.h"
#include "lib/file/vfs/vfs.h"
#include "lib/file/vfs/vfs_lookup.h"
#include "lib/file/vfs/vfs_tree.h"
class MockLoader : public IFileLoader
{
@ -43,6 +45,99 @@ public:
class TestVfsTree : public CxxTest::TestSuite
{
/**
* We create a few different "mods" here to test proper .DELETED
* behavior.
*
* To check which file is used we use the priority.
*
* 1
* +--a
* +--b/
* | +--a
* | \--b/
* | \--a
* \--c/
* +--a
* \--b
*
* 2
* +--a.DELETED
* +--b/
* | +--a
* | \--b.DELETED
* +--c.DELETED
* \--c/
* +--a
* \--b
*
* 3
* +--a
* \--b/
* \--b/
* \--a
*/
void mount_mod(size_t mod, VfsDirectory& dir)
{
size_t priority = mod;
PIFileLoader loader(new MockLoader(1));
switch(mod)
{
case 1:
{
dir.AddFile(VfsFile("a", 0, 0, priority, loader));
VfsDirectory* b = dir.AddSubdirectory("b");
b->AddFile(VfsFile("a", 0, 0, priority, loader));
VfsDirectory* b_b = b->AddSubdirectory("b");
b_b->AddFile(VfsFile("a", 0, 0, priority, loader));
VfsDirectory* c = dir.AddSubdirectory("c");
c->AddFile(VfsFile("a", 0, 0, priority, loader));
c->AddFile(VfsFile("b", 0, 0, priority, loader));
break;
}
case 2:
{
dir.DeleteSubtree(VfsFile("a.DELETED", 0, 0, priority, loader));
VfsDirectory* b = dir.AddSubdirectory("b");
b->AddFile(VfsFile("a", 0, 0, priority, loader));
b->DeleteSubtree(VfsFile("b.DELETED", 0, 0, priority, loader));
dir.DeleteSubtree(VfsFile("c.DELETED", 0, 0, priority, loader));
VfsDirectory* c = dir.AddSubdirectory("c");
c->AddFile(VfsFile("a", 0, 0, priority, loader));
c->AddFile(VfsFile("b", 0, 0, priority, loader));
break;
}
case 3:
{
dir.AddFile(VfsFile("a", 0, 0, priority, loader));
VfsDirectory* b = dir.AddSubdirectory("b");
VfsDirectory* b_b = b->AddSubdirectory("b");
b_b->AddFile(VfsFile("a", 0, 0, priority, loader));
break;
}
NODEFAULT;
}
}
void check_priority(VfsDirectory& root, const VfsPath& path, size_t priority)
{
VfsDirectory* dir; VfsFile* file;
TS_ASSERT_OK(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE));
TS_ASSERT_EQUALS(file->Priority(), priority);
}
void file_does_not_exists(VfsDirectory& root, const VfsPath& path)
{
VfsDirectory* dir; VfsFile* file;
TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE), ERR::VFS_FILE_NOT_FOUND);
}
void directory_exists(VfsDirectory& root, const VfsPath& path, Status error = INFO::OK)
{
VfsDirectory* dir;
TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, nullptr, VFS_LOOKUP_SKIP_POPULATE), error);
}
public:
void test_replacement()
{
@ -62,4 +157,35 @@ public:
TS_ASSERT_EQUALS(dir.AddFile(file2)->MTime(), file2.MTime());
TS_ASSERT_EQUALS(dir.AddFile(file1)->MTime(), file2.MTime());
}
void test_deleted()
{
VfsDirectory dir;
mount_mod(1, dir);
mount_mod(2, dir);
file_does_not_exists(dir, "a");
check_priority(dir, "b/a", 2);
directory_exists(dir, "b/b/", ERR::VFS_DIR_NOT_FOUND);
directory_exists(dir, "c/");
check_priority(dir, "c/a", 2);
check_priority(dir, "c/b", 2);
dir.Clear();
mount_mod(1, dir);
mount_mod(2, dir);
mount_mod(3, dir);
check_priority(dir, "a", 3);
check_priority(dir, "b/b/a", 3);
dir.Clear();
mount_mod(1, dir);
mount_mod(3, dir);
mount_mod(2, dir);
check_priority(dir, "a", 3);
check_priority(dir, "b/b/a", 3);
dir.Clear();
}
};

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013 Wildfire Games
/* Copyright (c) 2016 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -58,13 +58,13 @@ public:
DirectoryNames subdirectoryNames; subdirectoryNames.reserve(50);
RETURN_STATUS_IF_ERR(GetDirectoryEntries(m_realDirectory->Path(), &files, &subdirectoryNames));
// to support .DELETED files inside archives safely, we need to load
// archives and loose files in a deterministic order in case they add
// and then delete the same file (or vice versa, depending on loading
// order). GetDirectoryEntries has undefined order so sort its output
std::sort(files.begin(), files.end(), CompareFileInfoByName());
std::sort(subdirectoryNames.begin(), subdirectoryNames.end());
// Since .DELETED files only remove files in lower priority mods
// loose files and archive files have no conflicts so we do not need
// to sort them.
// We add directories after they might have been removed by .DELETED
// files (as they did not contain any files at that point). The order
// of GetDirectoryEntries is undefined, but that does not really matter (TODO really?)
// so we do not need to sort its output.
RETURN_STATUS_IF_ERR(AddFiles(files));
AddSubdirectories(subdirectoryNames);
@ -75,14 +75,14 @@ private:
void AddFile(const CFileInfo& fileInfo) const
{
const VfsPath name = fileInfo.Name();
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory);
if(name.Extension() == L".DELETED")
{
m_directory->RemoveFile(name.Basename());
m_directory->DeleteSubtree(file);
if(!(m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
return;
}
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory);
m_directory->AddFile(file);
}
@ -97,14 +97,14 @@ private:
WARN_IF_ERR(vfs_Lookup(pathname, this_->m_directory, directory, 0, flags));
const VfsPath name = fileInfo.Name();
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile);
if(name.Extension() == L".DELETED")
{
directory->RemoveFile(name.Basename());
directory->DeleteSubtree(file);
if(!(this_->m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED))
return;
}
const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile);
directory->AddFile(file);
}

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013 Wildfire Games
/* Copyright (c) 2016 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -49,6 +49,13 @@ VfsDirectory::VfsDirectory()
{
}
static bool ShouldDelete(const VfsFile& file, const VfsFile& deletedFile)
{
// We only check priority here, a .DELETED file in a mod should not
// delete files in that mod. For the same reason we ignore loose
// .DELETED files next to an archive.
return file.Priority() < deletedFile.Priority();
}
static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFile)
{
@ -78,6 +85,38 @@ static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFil
}
void VfsDirectory::DeleteSubtree(const VfsFile& file)
{
ENSURE(file.Name().Extension() == L".DELETED");
const VfsPath basename = file.Name().Basename();
std::map<VfsPath, VfsFile>::iterator fit = m_files.find(basename);
if(fit != m_files.end() && ShouldDelete(fit->second, file))
m_files.erase(basename);
std::map<VfsPath, VfsDirectory>::iterator dit = m_subdirectories.find(basename);
if(dit != m_subdirectories.end() && dit->second.DeleteTree(file))
m_subdirectories.erase(dit);
}
bool VfsDirectory::DeleteTree(const VfsFile& file)
{
for(std::map<VfsPath, VfsFile>::iterator it = m_files.begin(); it != m_files.end();)
if(ShouldDelete(it->second, file))
it = m_files.erase(it);
else
++it;
for(std::map<VfsPath, VfsDirectory>::iterator it = m_subdirectories.begin(); it != m_subdirectories.end();)
if(it->second.DeleteTree(file))
it = m_subdirectories.erase(it);
else
++it;
return m_files.empty() && m_subdirectories.empty();
}
VfsFile* VfsDirectory::AddFile(const VfsFile& file)
{
std::pair<VfsPath, VfsFile> value = std::make_pair(file.Name(), file);
@ -122,7 +161,6 @@ VfsFile* VfsDirectory::GetFile(const VfsPath& name)
return &it->second;
}
VfsDirectory* VfsDirectory::GetSubdirectory(const VfsPath& name)
{
VfsSubdirectories::iterator it = m_subdirectories.find(name.string());

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2010 Wildfire Games
/* Copyright (c) 2016 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -77,12 +77,28 @@ private:
class VfsDirectory
{
/**
* remove all files with a lower priority than @p file
* and do the same for all subdirectories recursively.
* @return true if the directory is empty afterwards
**/
bool DeleteTree(const VfsFile& file);
public:
typedef std::map<VfsPath, VfsFile> VfsFiles;
typedef std::map<VfsPath, VfsDirectory> VfsSubdirectories;
VfsDirectory();
/**
* remove the given file or subdirectory according to the priority of the
* passed .DELETED file.
* CAUTION: Invalidates all previously returned pointers of the file or
* subdirectory (and contents) if those have lower priority
* than @p file.
**/
void DeleteSubtree(const VfsFile& file);
/**
* @return address of existing or newly inserted file.
**/

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2014 Wildfire Games.
/* Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -77,11 +77,9 @@ void CArchiveBuilder::Build(const OsPath& archive, bool compress)
CXeromyces xero;
for (size_t i = 0; i < m_Files.size(); ++i)
for (const VfsPath& path : m_Files)
{
Status ret;
const VfsPath path = m_Files[i];
OsPath realPath;
ret = m_VFS->GetRealPath(path, realPath);
ENSURE(ret == INFO::OK);