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}