001package co.codewizards.cloudstore.core.repo.sync;
002
003import static java.util.Objects.*;
004
005import java.io.Closeable;
006import java.io.DataInputStream;
007import java.io.DataOutputStream;
008import java.io.FileInputStream;
009import java.io.FileOutputStream;
010import java.io.IOException;
011
012import org.slf4j.Logger;
013import org.slf4j.LoggerFactory;
014
015import co.codewizards.cloudstore.core.oio.File;
016import co.codewizards.cloudstore.core.util.LongUtil;
017
018public class DoneMarker implements Closeable {
019
020        private static final String LOCAL_REVISION_FILE_NAME = "localRevision.bin";
021
022        private static final Logger logger = LoggerFactory.getLogger(DoneMarker.class);
023
024        private final File doneDir;
025
026        public DoneMarker(File doneDir) {
027                this.doneDir = requireNonNull(doneDir, "doneDir");
028
029                if (doneDir.isFile()) {
030                        logger.error("doneDir '{}' is a normal file, but should be a directory. Deleting it now.", doneDir.getAbsolutePath());
031                        doneDir.deleteRecursively();
032                }
033
034                if (! doneDir.isDirectory()) {
035                        doneDir.mkdir();
036
037                        if (! doneDir.isDirectory())
038                                throw new RuntimeException(
039                                                new IOException(
040                                                                String.format("Directory '%s' could not be created! Check permissions and available space/inodes.", doneDir.getAbsolutePath())));
041                }
042        }
043
044        public File getDoneDir() {
045                return doneDir;
046        }
047
048        @Override
049        public void close() {
050        }
051
052        public void markDone(final long entityId, final long localRevision) {
053                final String[] entityIdHexSegments = LongUtil.toBytesHex(entityId, true);
054
055                File entityIdDir = doneDir;
056                for (String segment : entityIdHexSegments) {
057                        entityIdDir = entityIdDir.createFile(segment);
058
059                        if (! entityIdDir.isDirectory()) {
060                                entityIdDir.mkdir();
061
062                                if (! entityIdDir.isDirectory())
063                                        throw new RuntimeException(
064                                                        new IOException(
065                                                                        String.format("Directory '%s' could not be created! Check permissions and available space/inodes.", entityIdDir.getAbsolutePath())));
066                        }
067                }
068
069                final File localRevisionFile = entityIdDir.createFile(LOCAL_REVISION_FILE_NAME);
070
071                try (final DataOutputStream out = new DataOutputStream(new FileOutputStream(localRevisionFile.getIoFile()))) {
072                        out.writeLong(localRevision);
073                } catch (IOException x) {
074                        throw new RuntimeException(String.format("Failed writing file '%s'!", localRevisionFile.getAbsolutePath()), x);
075                }
076        }
077
078        public boolean isDone(final long entityId, final long localRevision) {
079                final String[] entityIdHexSegments = LongUtil.toBytesHex(entityId, true);
080
081                File entityIdDir = doneDir;
082                for (String segment : entityIdHexSegments)
083                        entityIdDir = entityIdDir.createFile(segment);
084
085                if (! entityIdDir.isDirectory())
086                        return false;
087
088                final File localRevisionFile = entityIdDir.createFile(LOCAL_REVISION_FILE_NAME);
089                if (! localRevisionFile.isFile())
090                        return false;
091
092                if (localRevisionFile.length() < 8) {
093                        logger.warn("isDone: File '{}' exists, but contains less than 8 bytes!",
094                                        localRevisionFile.getAbsolutePath());
095
096                        return false;
097                }
098
099                try (final DataInputStream in = new DataInputStream(new FileInputStream(localRevisionFile.getIoFile()))) {
100                        final long oldLocalRevision = in.readLong();
101                        if (oldLocalRevision == localRevision)
102                                return true;
103
104                        logger.warn("isDone: Entity with id={} is already marked as done for localRevision={}, but not for localRevision={}, thus returning false.",
105                                        entityId, oldLocalRevision, localRevision);
106                } catch (IOException x) {
107                        throw new RuntimeException(String.format("Failed reading file '%s'!", localRevisionFile.getAbsolutePath()), x);
108                }
109                return false;
110        }
111
112}