Skip to content

Commit 32998e4

Browse files
authored
Merge pull request #294 from cubitusgh2/MN_EFA_136_EFACLOUD_UPLOAD_OLD_LOGBOOK_WITH_OPEN_SESSIONS
Fixed Issues EFA#136 and EFA#137
2 parents 36b0cc6 + 357b19f commit 32998e4

File tree

8 files changed

+239
-48
lines changed

8 files changed

+239
-48
lines changed

de/nmichael/efa/Daten.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class Daten {
8080

8181
public final static String VERSIONID = "2.5.0"; // VersionsID: Format: "X.Y.Z_MM"; final-Version z.B. 1.4.0_00;
8282
// beta-Version z.B. 1.4.0_#1 //# is not good, is used in efa.data.Waters
83-
public final static String VERSIONRELEASEDATE = "10.02.2026"; // Release Date: TT.MM.JJJJ
83+
public final static String VERSIONRELEASEDATE = "14.02.2026"; // Release Date: TT.MM.JJJJ
8484
public final static String MAJORVERSION = "2";
8585
public final static String PROGRAMMID = "EFA.250"; // Versions-ID für Wettbewerbsmeldungen
8686
public final static String PROGRAMMID_DRV = "EFADRV.250"; // Versions-ID für Wettbewerbsmeldungen

de/nmichael/efa/data/LogbookRecord.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ public class LogbookRecord extends DataRecord {
133133
public static final int CREW_MAX = 24;
134134
public static final String WATERS_SEPARATORS = ",;+";
135135

136+
public static final String EMPTY="";
137+
//This is the name of the logbook the logbookrecord got created in.
138+
//
139+
private String logbookName = EMPTY;
140+
136141
// =========================================================================
137142
// Temporary Fields for Evaluation (not stored in the persistent record!)
138143
// =========================================================================
@@ -234,12 +239,23 @@ public static void initialize() {
234239

235240
public LogbookRecord(Logbook logbook, MetaData metaData) {
236241
super(logbook, metaData);
242+
this.logbookName = logbook.getName();
237243
}
238244

239245
public DataRecord createDataRecord() { // used for cloning
240-
return getPersistence().createNewRecord();
246+
//we do not need to take care for setting logbookname property
247+
//as this function automatically calls the constructor of logbookrecord.
248+
return getPersistence().createNewRecord();
241249
}
242250

251+
/**
252+
* Get the name of the logbook that has been set while constructing this logbookrecord.
253+
* @return
254+
*/
255+
public String getLogbookName() {
256+
return logbookName;
257+
}
258+
243259
public DataKey getKey() {
244260
return new DataKey<DataTypeIntString,String,String>(getEntryId(),null,null);
245261
}

de/nmichael/efa/data/efacloud/SynchControl.java

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class SynchControl {
3939
static final long synch_upload_look_back_ms = 15 * 24 * 3600000L; // period in past to check for upload
4040
static final long surely_newer_after_ms = 60000L; // one-minute time difference accepted for timestamps server <-> client
4141

42-
private static final int SYNCHERRORS_LOG_MAX_SIZE = 200000; //200 kb.
42+
private static final int SYNCHERRORS_LOG_MAX_SIZE = 10 * 1024 * 1024; // During upload sync only last 200kb are transferred, see TextResource
4343
private static final String FILENAME_PREVIOUS_SUFFIX = ".previous.log";
4444

4545
long lastSynchStartedMillis;
@@ -96,20 +96,22 @@ private void logSynchMessage(String logMessage, String tablename, DataKey dataKe
9696
File synchErrorsFile = new File(path);
9797

9898
Boolean appendLine=true;
99-
100-
//synchErrors.log rotation: if >5 Mb, delete old synchErrors.previous.log file and rename synchErrors.log to synchErrors.log.previous.log
101-
if (synchErrorsFile.length() > SYNCHERRORS_LOG_MAX_SIZE) {
102-
File oldPreviousLogfile=new File(path + FILENAME_PREVIOUS_SUFFIX);
103-
104-
// rotate existing efacloud.log to efacloud.log.previous and delete the existing file if necessary.
105-
if ((oldPreviousLogfile.exists() && oldPreviousLogfile.delete())
106-
|| (!oldPreviousLogfile.exists())) {
107-
if (synchErrorsFile.renameTo(new File(path + FILENAME_PREVIOUS_SUFFIX))) {
108-
appendLine=false;
109-
}
110-
}
99+
if (isError) {
100+
//log rotation only if we write to synchError.log
101+
//synchErrors.log rotation: if >5 Mb, delete old synchErrors.previous.log file and rename synchErrors.log to synchErrors.log.previous.log
102+
if (synchErrorsFile.length() > SYNCHERRORS_LOG_MAX_SIZE) {
103+
File oldPreviousLogfile=new File(path + FILENAME_PREVIOUS_SUFFIX);
104+
105+
// rotate existing efacloud.log to efacloud.log.previous and delete the existing file if necessary.
106+
if ((oldPreviousLogfile.exists() && oldPreviousLogfile.delete())
107+
|| (!oldPreviousLogfile.exists())) {
108+
if (synchErrorsFile.renameTo(new File(path + FILENAME_PREVIOUS_SUFFIX))) {
109+
appendLine=false;
110+
}
111+
}
112+
}
111113
}
112-
114+
// write to logfile, either synchError or efacloud.log
113115
TextResource.writeContents(path, dateString, appendLine);
114116
}
115117

@@ -154,13 +156,16 @@ void startSynchProcess(int synch_request) {
154156
}
155157

156158
/**
157-
* In case an EntryId of a logbook entry is corrected, ensure that also the boat status is updated. Only by that
158-
* update it is ensured the trip can be closed later.
159+
* In case an EntryId of a logbook entry is corrected, ensure that also the boat status is updated.
160+
* Only by that update it is ensured the trip can be closed later.
161+
*
162+
* The boat status only gets updated if the boat status points to the same logbook as the logbookrecord
163+
* which got a new entryID.
159164
*
160165
* @param boatId The UUID of the boat in the boat status
161166
* @param fixedEntryNo new EntryId for this boat status
162167
*/
163-
protected void adjustBoatStatus(UUID boatId, int fixedEntryNo) {
168+
protected void adjustBoatStatus(UUID boatId, int oldEntryNo, String oldLogbookName, int fixedEntryNo) {
164169
EfaCloudStorage boatstatus = Daten.tableBuilder.getPersistence("efa2boatstatus");
165170
DataKeyIterator it;
166171
try {
@@ -170,12 +175,17 @@ protected void adjustBoatStatus(UUID boatId, int fixedEntryNo) {
170175
BoatStatusRecord bsr = (BoatStatusRecord) boatstatus.get(boatstatuskey);
171176
if (bsr.getBoatId() != null) {
172177
if (bsr.getBoatId().compareTo(boatId) == 0) {
173-
bsr.setEntryNo(new DataTypeIntString("" + fixedEntryNo));
174-
long globalLock = boatstatus.acquireGlobalLock();
175-
boatstatus.update(bsr, globalLock);
176-
boatstatus.releaseGlobalLock(globalLock);
177-
logSynchMessage(International.getString("Korrigiere EntryNo in BoatStatus"), "efa2boatstatus",
178-
boatstatuskey, false);
178+
//Only update the boatstatusrecord if it points to the same logbook as
179+
//the record which got a new entryID.
180+
if (bsr.getLogbook().equalsIgnoreCase(oldLogbookName)) {
181+
bsr.setEntryNo(new DataTypeIntString("" + fixedEntryNo));
182+
long globalLock = boatstatus.acquireGlobalLock();
183+
boatstatus.update(bsr, globalLock);
184+
boatstatus.releaseGlobalLock(globalLock);
185+
logSynchMessage(International.getString("Korrigiere EntryNo in BoatStatus"), "efa2boatstatus",
186+
boatstatuskey, false);
187+
}
188+
179189
}
180190
}
181191
boatstatuskey = it.getNext();
@@ -450,16 +460,20 @@ void nextTableForUploadSynch(Transaction tx) {
450460
if (localLastModified > LastModifiedLimit)
451461
localRecordsToInsertAtServer.add(localRecord);
452462
} else if (localLastModified > serverRecord.getLastModified()) {
453-
String preUpdateRecordsCompareResult = ""; // removed August 2024: preUpdateRecordsCompare(localRecord, serverRecord, tx.tablename);
454-
if (!preUpdateRecordsCompareResult.isEmpty())
455-
localRecordsToUpdateAtServer.add(localRecord);
456-
else {
463+
// removed August 2024: String preUpdateRecordsCompare(localRecord, serverRecord, tx.tablename);
464+
/* String preUpdateRecordsCompareResult = "";
465+
* if (!preUpdateRecordsCompareResult.isEmpty()) {
466+
* localRecordsToUpdateAtServer.add(localRecord);
467+
* } else {
468+
*/
469+
// just get a comparison of the records so that we can log them and the user can actually have a hint what has changed.
470+
String preUpdateRecordsCompareResult = preUpdateRecordsCompare(localRecord, serverRecord, tx.tablename);
457471
logSynchMessage(International.getMessage(
458472
"Update-Konflikt bei Datensatz in der {type}-Synchronisation. Unterschiedlich sind: {fields}",
459473
"Upload", preUpdateRecordsCompareResult) +
460474
" " + International.getString("Bitte bereinige den Datensatz manuell."), tx.tablename,
461475
toCheck, false);
462-
}
476+
//}
463477
}
464478
toCheck = it.getNext();
465479
}
@@ -498,5 +512,50 @@ void nextTableForUploadSynch(Transaction tx) {
498512
null, false);
499513
}
500514
}
515+
516+
/**
517+
* Compares two records and points out which fields differ from another.
518+
*
519+
* @param dr1 DataRecord one to compare
520+
* @param dr2 DataRecord two to compare
521+
* @param tablename the table name to look up how many fields may be different.
522+
* @return empty String, if the records are of the same type and differ in only a tolerable amount of data fields
523+
*/
524+
private String preUpdateRecordsCompare(DataRecord dr1, DataRecord dr2, String tablename) {
525+
if (!efaCloudRolleBths && !isBoathouseApp)
526+
return "";
527+
if ((dr1 == null) || (dr2 == null) || (dr1.getClass() != dr2.getClass()))
528+
return "type mismatch";
529+
// if archiving is executed or an archived record is restored, do not check for mismatches
530+
// because the archived record stub will have all fields changed.
531+
532+
int diff = 0;
533+
StringBuilder fieldList = new StringBuilder();
534+
for (String field : dr1.getFields()) {
535+
if (!field.equalsIgnoreCase("ChangeCount")
536+
&& !field.equalsIgnoreCase("LastModified")
537+
&& !field.equalsIgnoreCase("LastModification")) {
538+
539+
String f1Value = dr1.getAsString(field);
540+
String f2Value = dr2.getAsString(field);
541+
542+
if (f1Value == null) {
543+
if (f2Value != null) {
544+
fieldList.append(field).append("(null").append("/").append(f2Value).append(")").append(", ");
545+
diff ++;
546+
}
547+
} else if (f2Value == null) {
548+
fieldList.append(field).append("(").append(f1Value).append("/null)").append(", ");
549+
diff++;
550+
// Use String comparison as the compareTo() implementation throws to many different exceptions.
551+
} else if (!dr1.getAsString(field).equalsIgnoreCase(dr2.getAsString(field))) {
552+
fieldList.append(field).append("(").append(f1Value).append("/").append(f2Value).append(", ");
553+
diff++;
554+
}
555+
}
556+
}
557+
558+
return fieldList.toString();
559+
}
501560

502561
}

de/nmichael/efa/data/efacloud/TextResource.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*/
2121
public class TextResource {
2222

23+
private final static int TAIL_SIZE = 200 * 1024; // 200 KB
24+
2325
/**
2426
* <p>
2527
* Fetch the entire contents of a text file, and return it in a String. This style of implementation does not throw
@@ -74,13 +76,112 @@ public static String getContents(File aFile, String charSetName) {
7476
contents.append("\n");
7577
}
7678
} catch (IOException ex) {
77-
return null;
79+
return "";
7880
}
7981
if (contents.length() == 0)
8082
return "";
8183
// remove last "\n" character.
8284
return contents.substring(0, contents.length() - 1);
8385
}
86+
87+
/**
88+
* <p>
89+
* Fetch the last TAIL_SIZE bytes of contents of a text file, and return it in a String.
90+
* This style of implementation does not throw exceptions to the caller,
91+
* but returns a null String, when hitting an IOException.
92+
*
93+
* This method is intended to be used for log file uploads to efaCloud,
94+
* so that big logfiles do not affect upload size or upload times.
95+
*
96+
* </p>
97+
*
98+
* <pre>
99+
* Charset Description
100+
* US-ASCII Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of
101+
* the Unicode character set
102+
* ISO-8859-1 ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
103+
* UTF-8 Eight-bit UCS Transformation Format
104+
* UTF-16BE Sixteen-bit UCS Transformation Format, big-endian byte order
105+
* UTF-16LE Sixteen-bit UCS Transformation Format, little-endian byte order
106+
* UTF-16 Sixteen-bit UCS Transformation Format, byte order identified by
107+
* an optional byte-order mark
108+
*
109+
* <pre>
110+
*
111+
* @param aFile File to be read
112+
* @param charSetName character set to be used. Set null for default character set
113+
* @return empty string, if not successful.
114+
*/
115+
public static String getTailContents(File file, String charSetName) {
116+
if (file == null || !file.exists() || !file.isFile() || !file.canRead()) {
117+
return "";
118+
}
119+
120+
Charset charset;
121+
122+
try {
123+
if (charSetName == null || charSetName.trim().isEmpty()) {
124+
charset = Charset.defaultCharset();
125+
} else {
126+
charset = Charset.forName(charSetName);
127+
}
128+
} catch (Exception e) {
129+
return ""; // invalid charset
130+
}
131+
132+
long fileLength = file.length();
133+
// just get the last TAIL_SIZE bytes of the file.
134+
long startPos = Math.max(0, fileLength - TAIL_SIZE);
135+
136+
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
137+
138+
// Jump to start position
139+
raf.seek(startPos);
140+
141+
// Supposed logfiles - we don't want to start in the middle of a line,
142+
// but directly after a newline.
143+
if (startPos > 0) {
144+
int b;
145+
while ((b = raf.read()) != -1) {
146+
if (b == '\n') {
147+
break;
148+
}
149+
}
150+
}
151+
152+
long extractStart = raf.getFilePointer();
153+
154+
// if file <TAIL_SIZE or no newline has been found
155+
if (extractStart < 0 || extractStart > fileLength) {
156+
extractStart = 0;
157+
raf.seek(0);
158+
}
159+
160+
// Now use the standard reader to read the log file into a single string
161+
try (InputStreamReader isr = new InputStreamReader(new FileInputStream(raf.getFD()), charset);
162+
BufferedReader br = new BufferedReader(isr)) {
163+
164+
StringBuilder sb = new StringBuilder(64 * 1024);
165+
String line;
166+
167+
while ((line = br.readLine()) != null) {
168+
sb.append(line).append('\n');
169+
}
170+
171+
if (sb.length() == 0) {
172+
return "";
173+
}
174+
175+
// remove last \n
176+
sb.setLength(sb.length() - 1);
177+
return sb.toString();
178+
}
179+
180+
} catch (IOException e) {
181+
return "";
182+
}
183+
}
184+
84185

85186
/**
86187
* <p>

de/nmichael/efa/data/efacloud/Transaction.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@
1010
*/
1111
package de.nmichael.efa.data.efacloud;
1212

13-
import de.nmichael.efa.Daten;
14-
import de.nmichael.efa.data.storage.EfaCloudStorage;
15-
1613
import java.io.File;
1714
import java.io.UnsupportedEncodingException;
15+
import java.util.ArrayList;
16+
import java.util.Arrays;
1817

1918
// import java.nio.charset.StandardCharsets; Java 8 only
2019
// import java.util.Base64; Java 8 only
2120

2221
import java.util.HashMap;
23-
import java.util.ArrayList;
2422
import java.util.Vector;
2523

24+
import de.nmichael.efa.Daten;
25+
import de.nmichael.efa.data.storage.EfaCloudStorage;
26+
2627
/**
2728
* A container class for data modifications passed to the efaClod Server.
2829
*/
@@ -130,7 +131,8 @@ static TaskManager.RequestMessage createIamRequest(Vector<Transaction> txs, Stri
130131
for (Transaction tx : txs) {
131132
tx.appendTxPostString(txContainer);
132133
txq.logApiMessage("tx" + tx.ID + ", " + tx.type + " [" + tx.tablename + "]: Transaction sent. Record length: "
133-
+ ((tx.record == null) ? "null" : "" + tx.record.length), 0);
134+
+ ((tx.record == null) ? "null" : "" + tx.record.length)
135+
+ (txq.isExtendedDebug ? " - " + Arrays.toString(tx.record) : "" ), 0);
134136
txContainer.append(MESSAGE_SEPARATOR_STRING);
135137
}
136138
String txContainerStr = txContainer.toString();

0 commit comments

Comments
 (0)