2
.mvn/wrapper/maven-wrapper.config
vendored
2
.mvn/wrapper/maven-wrapper.config
vendored
@@ -1 +1 @@
|
||||
jvmArguments=--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED
|
||||
jvmArguments=-Djava.util.logging.manager=org.jboss.logmanager.LogManager --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED
|
||||
@@ -14,8 +14,8 @@ services:
|
||||
- AUCTION_IMAGES_PATH=/mnt/okcomputer/output/images
|
||||
|
||||
# Notification configuration
|
||||
- AUCTION_NOTIFICATION_CONFIG=desktop
|
||||
|
||||
# - AUCTION_NOTIFICATION_CONFIG=desktop
|
||||
- AUCTION_NOTIFICATION_CONFIG=smtp:michael.bakker1986@gmail.com:agrepolhlnvhipkv:michael.bakker1986@gmail.com
|
||||
# Quarkus configuration
|
||||
- QUARKUS_HTTP_PORT=8081
|
||||
- QUARKUS_HTTP_HOST=0.0.0.0
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -440,6 +440,8 @@
|
||||
<!-- Enable ByteBuddy experimental mode for Java 25 support -->
|
||||
<!-- Mockito requires this for Java 24+ -->
|
||||
<argLine>
|
||||
--enable-native-access=ALL-UNNAMED
|
||||
--add-opens java.base/sun.misc=ALL-UNNAMED
|
||||
-Dnet.bytebuddy.experimental=true
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED
|
||||
--add-opens java.base/java.util=ALL-UNNAMED
|
||||
|
||||
3034
scripts/smb-copy.log
3034
scripts/smb-copy.log
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,13 @@
|
||||
package auctiora;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import nu.pattern.OpenCV;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
@@ -23,7 +26,7 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
|
||||
/**
|
||||
* Service for performing object detection on images using OpenCV's DNN
|
||||
* module. The DNN module can load pre‑trained models from several
|
||||
* frameworks (Darknet, TensorFlow, ONNX, etc.)【784097309529506†L209-L233】. Here
|
||||
* frameworks (Darknet, TensorFlow, ONNX, etc.). Here
|
||||
* we load a YOLO model (Darknet) by specifying the configuration and
|
||||
* weights files. For each image we run a forward pass and return a
|
||||
* list of detected class labels.
|
||||
@@ -33,14 +36,45 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
|
||||
*/
|
||||
@Slf4j
|
||||
public class ObjectDetectionService {
|
||||
|
||||
private final Net net;
|
||||
private final List<String> classNames;
|
||||
private final boolean enabled;
|
||||
|
||||
private Net net;
|
||||
private List<String> classNames;
|
||||
private boolean enabled;
|
||||
private int warnCount = 0;
|
||||
private static final int MAX_WARNINGS = 5;
|
||||
|
||||
private static boolean openCvLoaded = false;
|
||||
|
||||
private final String cfgPath;
|
||||
private final String weightsPath;
|
||||
private final String classNamesPath;
|
||||
|
||||
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
|
||||
this.cfgPath = cfgPath;
|
||||
this.weightsPath = weightsPath;
|
||||
this.classNamesPath = classNamesPath;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
// Load OpenCV native libraries first
|
||||
if (!openCvLoaded) {
|
||||
try {
|
||||
OpenCV.loadLocally();
|
||||
openCvLoaded = true;
|
||||
log.info("✓ OpenCV {} loaded successfully", Core.VERSION);
|
||||
} catch (Exception e) {
|
||||
log.warn("⚠️ Object detection disabled: OpenCV native libraries not loaded");
|
||||
enabled = false;
|
||||
net = null;
|
||||
classNames = new ArrayList<>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
initializeModel();
|
||||
}
|
||||
|
||||
private void initializeModel() {
|
||||
// Check if model files exist
|
||||
var cfgFile = Paths.get(cfgPath);
|
||||
var weightsFile = Paths.get(weightsPath);
|
||||
@@ -86,11 +120,15 @@ public class ObjectDetectionService {
|
||||
classNames = Files.readAllLines(classNamesFile);
|
||||
enabled = true;
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
System.err.println("⚠️ Object detection disabled: OpenCV native libraries not loaded");
|
||||
throw new IOException("Failed to initialize object detection: OpenCV native libraries not loaded", e);
|
||||
log.error("⚠️ Object detection disabled: OpenCV native libraries not loaded", e);
|
||||
enabled = false;
|
||||
net = null;
|
||||
classNames = new ArrayList<>();
|
||||
} catch (Exception e) {
|
||||
System.err.println("⚠️ Object detection disabled: " + e.getMessage());
|
||||
throw new IOException("Failed to initialize object detection", e);
|
||||
log.error("⚠️ Object detection disabled: " + e.getMessage(), e);
|
||||
enabled = false;
|
||||
net = null;
|
||||
classNames = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -65,10 +65,10 @@ public class StatusResource {
|
||||
|
||||
private String getOpenCvVersion() {
|
||||
try {
|
||||
OpenCV.loadLocally();
|
||||
// OpenCV is already loaded by AuctionMonitorProducer
|
||||
return Core.VERSION;
|
||||
} catch (Exception e) {
|
||||
return "4.9.0 (default)";
|
||||
return "Not loaded";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public class TroostwijkMonitor {
|
||||
allLots.size(), imageCount);
|
||||
if (!allLots.isEmpty()) {
|
||||
var sum = allLots.stream().mapToDouble(Lot::currentBid).sum();
|
||||
log.info("Total current bids: €{:.2f}", sum);
|
||||
log.info("Total current bids: €{}", String.format("%.2f", sum));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not retrieve database stats", e);
|
||||
|
||||
@@ -46,7 +46,10 @@ quarkus.http.root-path=/
|
||||
# Auction Monitor Configuration
|
||||
auction.database.path=/mnt/okcomputer/output/cache.db
|
||||
auction.images.path=/mnt/okcomputer/output/images
|
||||
auction.notification.config=desktop
|
||||
# auction.notification.config=desktop
|
||||
# Format: smtp:username:password:recipient_email
|
||||
auction.notification.config=smtp:michael.bakker1986@gmail.com:agrepolhlnvhipkv:michael.bakker1986@gmail.com
|
||||
|
||||
auction.yolo.config=/mnt/okcomputer/output/models/yolov4.cfg
|
||||
auction.yolo.weights=/mnt/okcomputer/output/models/yolov4.weights
|
||||
auction.yolo.classes=/mnt/okcomputer/output/models/coco.names
|
||||
@@ -69,3 +72,4 @@ auction.http.timeout-seconds=30
|
||||
|
||||
# Health Check Configuration
|
||||
quarkus.smallrye-health.root-path=/health
|
||||
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package auctiora;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Test auction parsing logic using saved HTML from test.html
|
||||
* Tests the markup data extraction for each auction found
|
||||
*/
|
||||
@Slf4j
|
||||
public class AuctionParsingTest {
|
||||
|
||||
private static String testHtml;
|
||||
|
||||
@BeforeAll
|
||||
public static void loadTestHtml() throws IOException {
|
||||
// Load the test HTML file
|
||||
testHtml = Files.readString(Paths.get("src/test/resources/test_auctions.html"));
|
||||
log.info("Loaded test HTML ({} characters)", testHtml.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocationPatternMatching() {
|
||||
log.info("\n=== Location Pattern Tests ===");
|
||||
|
||||
// Test different location formats
|
||||
var testCases = new String[]{
|
||||
"<p>Amsterdam, NL</p>",
|
||||
"<p class=\"flex truncate\"><span class=\"w-full truncate\">Sofia,<!-- --> </span>BG</p>",
|
||||
"<p>Berlin, DE</p>",
|
||||
"<span>Brussels,</span>BE"
|
||||
};
|
||||
|
||||
for (var testHtml : testCases) {
|
||||
var doc = Jsoup.parse(testHtml);
|
||||
var elem = doc.select("p, span").first();
|
||||
|
||||
if (elem != null) {
|
||||
var text = elem.text();
|
||||
log.info("\nTest: {}", testHtml);
|
||||
log.info("Text: {}", text);
|
||||
|
||||
// Test regex pattern
|
||||
if (text.matches(".*[A-Z]{2}$")) {
|
||||
var countryCode = text.substring(text.length() - 2);
|
||||
var cityPart = text.substring(0, text.length() - 2).trim().replaceAll("[,\\s]+$", "");
|
||||
log.info("→ Extracted: {}, {}", cityPart, countryCode);
|
||||
} else {
|
||||
log.info("→ No match");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullTextPatternMatching() {
|
||||
log.info("\n=== Full Text Pattern Tests ===");
|
||||
|
||||
// Test the complete auction text format
|
||||
var testCases = new String[]{
|
||||
"woensdag om 18:00 1 Vrachtwagens voor bedrijfsvoertuigen Loßburg, DE",
|
||||
"maandag om 14:30 5 Industriële machines Amsterdam, NL",
|
||||
"vrijdag om 10:00 12 Landbouwmachines Antwerpen, BE"
|
||||
};
|
||||
|
||||
for (var testText : testCases) {
|
||||
log.info("\nParsing: \"{}\"", testText);
|
||||
|
||||
// Simulated extraction
|
||||
var remaining = testText;
|
||||
|
||||
// Extract time
|
||||
var timePattern = java.util.regex.Pattern.compile("(\\w+)\\s+om\\s+(\\d{1,2}:\\d{2})");
|
||||
var timeMatcher = timePattern.matcher(remaining);
|
||||
if (timeMatcher.find()) {
|
||||
log.info(" Time: {} om {}", timeMatcher.group(1), timeMatcher.group(2));
|
||||
remaining = remaining.substring(timeMatcher.end()).trim();
|
||||
}
|
||||
|
||||
// Extract location
|
||||
var locPattern = java.util.regex.Pattern.compile(
|
||||
"([A-ZÀ-ÿa-z][A-ZÀ-ÿa-z\\s\\-'öäüßàèéêëïôùûç]+?),\\s*([A-Z]{2})\\s*$"
|
||||
);
|
||||
var locMatcher = locPattern.matcher(remaining);
|
||||
if (locMatcher.find()) {
|
||||
log.info(" Location: {}, {}", locMatcher.group(1), locMatcher.group(2));
|
||||
remaining = remaining.substring(0, locMatcher.start()).trim();
|
||||
}
|
||||
|
||||
// Extract lot count
|
||||
var lotPattern = java.util.regex.Pattern.compile("^(\\d+)\\s+");
|
||||
var lotMatcher = lotPattern.matcher(remaining);
|
||||
if (lotMatcher.find()) {
|
||||
log.info(" Lot count: {}", lotMatcher.group(1));
|
||||
remaining = remaining.substring(lotMatcher.end()).trim();
|
||||
}
|
||||
|
||||
// What remains is title
|
||||
log.info(" Title: {}", remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,13 +80,13 @@ class ClosingTimeCalculationTest {
|
||||
@DisplayName("Should identify lots closing within 30 minutes (dashboard threshold)")
|
||||
void testDashboardClosingThreshold() {
|
||||
var closing20Min = createLot(LocalDateTime.now().plusMinutes(20));
|
||||
var closing30Min = createLot(LocalDateTime.now().plusMinutes(30));
|
||||
var closing31Min = createLot(LocalDateTime.now().plusMinutes(31)); // Use 31 to avoid boundary timing issue
|
||||
var closing40Min = createLot(LocalDateTime.now().plusMinutes(40));
|
||||
|
||||
assertTrue(closing20Min.minutesUntilClose() < 30,
|
||||
"Lot closing in 20 min should be < 30 minutes");
|
||||
assertTrue(closing30Min.minutesUntilClose() >= 30,
|
||||
"Lot closing in 30 min should be >= 30 minutes");
|
||||
assertTrue(closing31Min.minutesUntilClose() >= 30,
|
||||
"Lot closing in 31 min should be >= 30 minutes");
|
||||
assertTrue(closing40Min.minutesUntilClose() > 30,
|
||||
"Lot closing in 40 min should be > 30 minutes");
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ class ObjectDetectionServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw IOException when model files exist but OpenCV fails to load")
|
||||
@DisplayName("Should gracefully handle when model files exist but OpenCV fails to load")
|
||||
void testInitializeWithValidModels() throws IOException {
|
||||
var cfgPath = Paths.get(TEST_CFG);
|
||||
var weightsPath = Paths.get(TEST_WEIGHTS);
|
||||
@@ -93,10 +93,11 @@ class ObjectDetectionServiceTest {
|
||||
Files.writeString(classesPath, "person\ncar\ntruck\n");
|
||||
|
||||
// When files exist but OpenCV native library isn't loaded,
|
||||
// constructor should throw IOException wrapping the UnsatisfiedLinkError
|
||||
assertThrows(IOException.class, () -> {
|
||||
new ObjectDetectionService(TEST_CFG, TEST_WEIGHTS, TEST_CLASSES);
|
||||
});
|
||||
// service should construct successfully but be disabled (handled in @PostConstruct)
|
||||
var service = new ObjectDetectionService(TEST_CFG, TEST_WEIGHTS, TEST_CLASSES);
|
||||
// Service is created, but init() handles failures gracefully
|
||||
// detectObjects should return empty list when disabled
|
||||
assertNotNull(service);
|
||||
} finally {
|
||||
Files.deleteIfExists(cfgPath);
|
||||
Files.deleteIfExists(weightsPath);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,26 +0,0 @@
|
||||
Configure your devices to use the Pi-hole as their DNS server │
|
||||
│ using: │
|
||||
│ │
|
||||
│ IPv4: 192.168.1.159 │
|
||||
│ IPv6: fdc5:59a6:9ac1:f11f:2c86:25d3:6282:37ef │
|
||||
│ If you have not done so already, the above IP should be set to │
|
||||
│ static. │
|
||||
│ View the web interface at http://pi.hole:80/admin or │
|
||||
│ http://192.168.1.159:80/admin │
|
||||
│ │
|
||||
│ Your Admin Webpage login password is gYj7Enh- │
|
||||
│ │
|
||||
│ │
|
||||
│ To allow your user to use all CLI functions without │
|
||||
│ authentication, │
|
||||
│ refer to https://docs.pi-hole.net/main/post-install/ │
|
||||
├─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
127.0.0.1
|
||||
192.168.1.159
|
||||
::1
|
||||
fdc5:59a6:9ac1:f11f:2c86:25d3:6282:37ef
|
||||
fdc5:59a6:9ac1:f11f:bd8c:6e87:65f0:243c
|
||||
fe80::a05b:bbc6:d47f:3002%enp9s0
|
||||
2IXD-XJN9-C337-1K4Y-BBEO-HV1F-3BVI
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user