Portiere PHP-Cronjobs auf Spring-Tasks und ergänze Legacy-Tests
Migriert die weiterhin benötigten Zeitaufgaben einschließlich SOAP-Importen, Betriebsstatus und Wiederanlaufkontrolle. Aktiviert 115 native Spring-Tasks im Docker-Image ohne Cron-Daemon oder PHP und dokumentiert die entfallenen HHA-Aufrufe. Ergänzt PHP-/UI-Testwerkzeuge und aktualisiert den Projektstand auf Version 0.2.0.
This commit is contained in:
1 parent
781a2b82dc
commit
9de2358480
57 files changed
+3251
-7
No files matched your search
@@ -0,0 +1,72 @@
|
||||
package de.stadtbote.votian;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
|
||||
import static de.stadtbote.votian.AdminSupport.bad;
|
||||
|
||||
/** Native do_bzip2.php. Only explicitly mounted legacy paths are eligible, never NG document stores. */
|
||||
@Component
|
||||
class LegacyCronFiles {
|
||||
final Environment env;
|
||||
LegacyCronFiles(Environment env){this.env=env;}
|
||||
static List<Path> paths(String value) throws IOException {
|
||||
var result=new ArrayList<Path>();
|
||||
for(String raw:value.split(",")){if(raw.isBlank())continue;Path p=Path.of(raw.strip()).toAbsolutePath().normalize();
|
||||
if(p.getNameCount()<3||!Files.isDirectory(p,NOFOLLOW_LINKS)||!p.equals(p.toRealPath()))throw bad("Cron-Dateipfad muss ein eigenes vorhandenes Verzeichnis ohne symbolische Links sein.");
|
||||
result.add(p);
|
||||
}return result;
|
||||
}
|
||||
static boolean same(BasicFileAttributes a,BasicFileAttributes b){return a.size()==b.size()&&a.lastModifiedTime().equals(b.lastModifiedTime())&&Objects.equals(a.fileKey(),b.fileKey());}
|
||||
static String digest(InputStream in) throws IOException {try(in){var md=java.security.MessageDigest.getInstance("SHA-256");byte[] b=new byte[65536];int n;while((n=in.read(b))!=-1)md.update(b,0,n);return HexFormat.of().formatHex(md.digest());}catch(java.security.NoSuchAlgorithmException e){throw new IllegalStateException(e);}}
|
||||
static void compress(Path source) throws IOException {
|
||||
var before=Files.readAttributes(source,BasicFileAttributes.class,NOFOLLOW_LINKS);if(!before.isRegularFile())return;
|
||||
Path target=source.resolveSibling(source.getFileName()+".bz2");
|
||||
if(Files.exists(target,NOFOLLOW_LINKS)){
|
||||
if(!Files.isRegularFile(target,NOFOLLOW_LINKS))throw bad("Cron-Archivziel ist keine reguläre Datei.");
|
||||
String original=digest(Files.newInputStream(source,NOFOLLOW_LINKS));
|
||||
String archived=digest(new BZip2CompressorInputStream(Files.newInputStream(target,NOFOLLOW_LINKS)));
|
||||
if(!original.equals(archived))throw bad("Vorhandenes Cron-Archiv passt nicht zur Quelldatei; kein Überschreiben.");
|
||||
}else{
|
||||
Path temp=Files.createTempFile(source.getParent(),".ng-cron-",".part");
|
||||
try{
|
||||
try(var input=Files.newInputStream(source,NOFOLLOW_LINKS);var output=new BZip2CompressorOutputStream(Files.newOutputStream(temp),9)){input.transferTo(output);}
|
||||
try(var channel=FileChannel.open(temp,StandardOpenOption.WRITE)){channel.force(true);}
|
||||
if(!same(before,Files.readAttributes(source,BasicFileAttributes.class,NOFOLLOW_LINKS)))throw bad("Quelldatei wurde während der Kompression geändert.");
|
||||
// Atomic publication without overwrite; the temporary file is on the same filesystem.
|
||||
Files.createLink(target,temp);
|
||||
}finally{Files.deleteIfExists(temp);}
|
||||
}
|
||||
if(!same(before,Files.readAttributes(source,BasicFileAttributes.class,NOFOLLOW_LINKS)))throw bad("Quelldatei wurde während der Kompression geändert.");
|
||||
Files.delete(source);
|
||||
}
|
||||
LegacyCronRunner.Result run(LocalDate today){
|
||||
try{
|
||||
var bin=paths(env.getProperty("app.legacy-cron.files.binlog-directories",""));
|
||||
var logs=paths(env.getProperty("app.legacy-cron.files.log-directories",""));
|
||||
var purge=paths(env.getProperty("app.legacy-cron.files.temporary-directories",""));
|
||||
if(bin.isEmpty()&&logs.isEmpty()&&purge.isEmpty())throw bad("Keine Altdatei-Verzeichnisse für die Cron-Wartung konfiguriert.");
|
||||
var all=new ArrayList<Path>();all.addAll(bin);all.addAll(logs);all.addAll(purge);
|
||||
for(int i=0;i<all.size();i++)for(int j=i+1;j<all.size();j++)if(all.get(i).startsWith(all.get(j))||all.get(j).startsWith(all.get(i)))throw bad("Cron-Dateipfade dürfen sich nicht überlappen.");
|
||||
int count=0;
|
||||
for(Path directory:bin){List<Path> candidates;try(var stream=Files.list(directory)){candidates=stream.filter(p->Files.isRegularFile(p,NOFOLLOW_LINKS)&&p.getFileName().toString().matches("mps2-bin\\.0[0-9]+" )).toList();}
|
||||
long newest=Long.MIN_VALUE;for(var p:candidates)newest=Math.max(newest,Files.getLastModifiedTime(p,NOFOLLOW_LINKS).toMillis());
|
||||
for(var p:candidates)if(Files.getLastModifiedTime(p,NOFOLLOW_LINKS).toMillis()<newest){compress(p);count++;}
|
||||
}
|
||||
String month="_"+today.minusMonths(1).format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
for(Path directory:logs){List<Path> candidates;try(var stream=Files.list(directory)){candidates=stream.filter(p->Files.isRegularFile(p,NOFOLLOW_LINKS)&&p.getFileName().toString().contains(month)&&p.getFileName().toString().endsWith(".log")).toList();}for(var p:candidates){compress(p);count++;}}
|
||||
// PHP deletes all regular files recursively in these three temporary trees; links stay untouched.
|
||||
for(Path directory:purge)try(var stream=Files.walk(directory)){for(var p:stream.filter(p->Files.isRegularFile(p,NOFOLLOW_LINKS)).toList()){Files.delete(p);count++;}}
|
||||
return LegacyCronRunner.Result.ok(count);
|
||||
}catch(IOException e){return new LegacyCronRunner.Result("UNCERTAIN","Dateiwartung unterbrochen; Quelldateien und Archive vor Freigabe prüfen.",0);}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user