/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.persistence.internal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
/**
* Helper class for methods around {@link SaveTransaction}s that are also needed outside of the save transaction.
*
*/
public class SaveTransactionHelper {
private static final Logger logger = LoggerFactory.getLogger(SaveTransactionHelper.class);
private final StoragePathProvider storagePathProvider;
public SaveTransactionHelper(StoragePathProvider storagePathProvider) {
this.storagePathProvider = storagePathProvider;
}
public void cleanupSaveTransactionDirectory() throws IOException {
Path directory = storagePathProvider.getUnfinishedSaveTransactionPath();
if (!Files.exists(directory)) {
return;
}
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Merges all outstanding changes into the save game. If this operation gets interrupted it can be started again
* without any file corruption when the file system supports atomic moves.
* <br><br>
* The write lock for the save directory should be acquired before this method gets called.
*/
public void mergeChanges() throws IOException {
final Path sourceDirectory = storagePathProvider.getUnmergedChangesPath();
final Path targetDirectory = storagePathProvider.getStoragePathDirectory();
Files.walkFileTree(sourceDirectory, new SimpleFileVisitor<Path>() {
boolean atomicNotPossibleLogged;
@Override
public FileVisitResult preVisitDirectory(Path sourceSubDir, BasicFileAttributes attrs) throws IOException {
Path targetSubDir = targetDirectory.resolve(sourceDirectory.relativize(sourceSubDir));
if (!Files.isDirectory(targetSubDir)) {
Files.createDirectory(targetSubDir);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path sourcePath, BasicFileAttributes attrs) throws IOException {
Path targetPath = targetDirectory.resolve(sourceDirectory.relativize(sourcePath));
try {
// Delete file, as behavior of atomic move is undefined if target file exists:
Files.deleteIfExists(targetPath);
Files.move(sourcePath, targetPath, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
if (!atomicNotPossibleLogged) {
logger.warn("Atomic move was not possible, doing it non atomically...");
atomicNotPossibleLogged = true;
}
Files.move(sourcePath, targetPath);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
try {
Files.delete(dir);
} catch (DirectoryNotEmptyException e) {
/**
* Happens rarely for some players on windows (See issue #2160). Exact reason for this behavior is
* unknown. Maybe they have some kind of background task that processes that creates a temporary
* files in new directories to store some intermediate scan result.
*/
logger.warn("The save job could not cleanup a temporarly created directory, it will retry once in one second");
try {
Thread.sleep(1000L);
} catch (InterruptedException e1) {
// Reset flag and ignore it
Thread.currentThread().interrupt();
}
Files.delete(dir);
}
return FileVisitResult.CONTINUE;
}
});
}
}