Skip to content

Commit 11fe9e2

Browse files
authored
718 feature request make immich best guess optional (#738)
1 parent 048410b commit 11fe9e2

File tree

8 files changed

+531
-37
lines changed

8 files changed

+531
-37
lines changed

src/main/java/com/dedicatedcode/reitti/controller/settings/IntegrationsSettingsController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,11 @@ public ResponseEntity<String> getGpsLoggerProperties(@RequestParam String token,
168168
public String saveImmichIntegration(@RequestParam String serverUrl,
169169
@RequestParam String apiToken,
170170
@RequestParam(defaultValue = "false") boolean enabled,
171+
@RequestParam(defaultValue = "false") boolean useBestGuessLocation,
171172
@AuthenticationPrincipal User currentUser,
172173
RedirectAttributes model) {
173174
try {
174-
immichIntegrationService.saveIntegration(currentUser, serverUrl, apiToken, enabled);
175+
immichIntegrationService.saveIntegration(currentUser, serverUrl, apiToken, useBestGuessLocation, enabled);
175176
model.addFlashAttribute("successMessage", i18n.translate("integrations.immich.config.saved"));
176177
} catch (Exception e) {
177178
model.addFlashAttribute("errorMessage", i18n.translate("integrations.immich.config.error", e.getMessage()));

src/main/java/com/dedicatedcode/reitti/model/integration/ImmichIntegration.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public class ImmichIntegration {
99
private final String serverUrl;
1010

1111
private final String apiToken;
12-
12+
13+
private final boolean useBestGuessLocation;
14+
1315
private final boolean enabled;
1416

1517
private final Instant createdAt;
@@ -19,14 +21,15 @@ public class ImmichIntegration {
1921
private final Long version;
2022

2123

22-
public ImmichIntegration(String serverUrl, String apiToken, boolean enabled) {
23-
this(null, serverUrl, apiToken, enabled, null, null, 1L);
24+
public ImmichIntegration(String serverUrl, String apiToken, boolean useBestGuessLocation, boolean enabled) {
25+
this(null, serverUrl, apiToken, useBestGuessLocation, enabled, null, null, 1L);
2426
}
25-
26-
public ImmichIntegration(Long id, String serverUrl, String apiToken, boolean enabled, Instant createdAt, Instant updatedAt, Long version) {
27+
28+
public ImmichIntegration(Long id, String serverUrl, String apiToken, boolean useBestGuessLocation, boolean enabled, Instant createdAt, Instant updatedAt, Long version) {
2729
this.id = id;
2830
this.serverUrl = serverUrl;
2931
this.apiToken = apiToken;
32+
this.useBestGuessLocation = useBestGuessLocation;
3033
this.enabled = enabled;
3134
this.createdAt = createdAt != null ? createdAt : Instant.now();
3235
this.updatedAt = updatedAt != null ? updatedAt : Instant.now();
@@ -45,7 +48,11 @@ public String getServerUrl() {
4548
public String getApiToken() {
4649
return apiToken;
4750
}
48-
51+
52+
public boolean isUseBestGuessLocation() {
53+
return useBestGuessLocation;
54+
}
55+
4956
public boolean isEnabled() {
5057
return enabled;
5158
}
@@ -64,18 +71,22 @@ public Long getVersion() {
6471

6572
// Wither methods
6673
public ImmichIntegration withEnabled(boolean enabled) {
67-
return new ImmichIntegration(this.id, this.serverUrl, this.apiToken, enabled, this.createdAt, Instant.now(), this.version);
74+
return new ImmichIntegration(this.id, this.serverUrl, this.apiToken, this.useBestGuessLocation, enabled, this.createdAt, Instant.now(), this.version);
75+
}
76+
77+
public ImmichIntegration withUseBestGuessLocation(boolean useBestGuessLocation) {
78+
return new ImmichIntegration(this.id, this.serverUrl, this.apiToken, useBestGuessLocation, this.enabled, this.createdAt, Instant.now(), this.version);
6879
}
6980

7081
public ImmichIntegration withServerUrl(String serverUrl) {
71-
return new ImmichIntegration(this.id, serverUrl, this.apiToken, this.enabled, this.createdAt, this.updatedAt, version);
82+
return new ImmichIntegration(this.id, serverUrl, this.apiToken, this.useBestGuessLocation, this.enabled, this.createdAt, this.updatedAt, version);
7283
}
7384

7485
public ImmichIntegration withApiToken(String apiToken) {
75-
return new ImmichIntegration(this.id, this.serverUrl, apiToken, this.enabled, this.createdAt, Instant.now(), this.version);
86+
return new ImmichIntegration(this.id, this.serverUrl, apiToken, this.useBestGuessLocation, this.enabled, this.createdAt, Instant.now(), this.version);
7687
}
7788

7889
public ImmichIntegration withId(Long id) {
79-
return new ImmichIntegration(id, this.serverUrl, this.apiToken, this.enabled, this.createdAt, this.updatedAt, version);
90+
return new ImmichIntegration(id, this.serverUrl, this.apiToken, this.useBestGuessLocation, this.enabled, this.createdAt, this.updatedAt, version);
8091
}
8192
}

src/main/java/com/dedicatedcode/reitti/repository/ImmichIntegrationJdbcService.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public ImmichIntegrationJdbcService(JdbcTemplate jdbcTemplate) {
2626
rs.getLong("id"),
2727
rs.getString("server_url"),
2828
rs.getString("api_token"),
29+
rs.getBoolean("use_best_guess_location"),
2930
rs.getBoolean("enabled"),
3031
rs.getTimestamp("created_at").toInstant(),
3132
rs.getTimestamp("updated_at").toInstant(),
@@ -39,7 +40,7 @@ public Optional<ImmichIntegration> findByUser(User user) {
3940

4041
try {
4142
List<ImmichIntegration> results = jdbcTemplate.query(sql, IMMICH_INTEGRATION_ROW_MAPPER, user.getId());
42-
return results.isEmpty() ? Optional.empty() : Optional.of(results.get(0));
43+
return results.isEmpty() ? Optional.empty() : Optional.of(results.getFirst());
4344
} catch (EmptyResultDataAccessException e) {
4445
return Optional.empty();
4546
}
@@ -48,13 +49,14 @@ public Optional<ImmichIntegration> findByUser(User user) {
4849
public ImmichIntegration save(User user, ImmichIntegration immichIntegration) {
4950
if (immichIntegration.getId() == null) {
5051
// Insert new record
51-
String sql = "INSERT INTO immich_integrations (user_id, server_url, api_token, enabled, created_at, updated_at, version) " +
52-
"VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id";
52+
String sql = "INSERT INTO immich_integrations (user_id, server_url, api_token, use_best_guess_location, enabled, created_at, updated_at, version) " +
53+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id";
5354
Instant now = Instant.now();
5455
Long id = jdbcTemplate.queryForObject(sql, Long.class,
5556
user.getId(),
5657
immichIntegration.getServerUrl(),
5758
immichIntegration.getApiToken(),
59+
immichIntegration.isUseBestGuessLocation(),
5860
immichIntegration.isEnabled(),
5961
java.sql.Timestamp.from(now),
6062
java.sql.Timestamp.from(now),
@@ -63,11 +65,12 @@ public ImmichIntegration save(User user, ImmichIntegration immichIntegration) {
6365
return immichIntegration.withId(id);
6466
} else {
6567
// Update existing record
66-
String sql = "UPDATE immich_integrations SET server_url = ?, api_token = ?, enabled = ?, updated_at = ?, version = version + 1 WHERE id = ? AND version = ?";
68+
String sql = "UPDATE immich_integrations SET server_url = ?, api_token = ?, use_best_guess_location = ?, enabled = ?, updated_at = ?, version = version + 1 WHERE id = ? AND version = ?";
6769
Instant now = Instant.now();
6870
jdbcTemplate.update(sql,
6971
immichIntegration.getServerUrl(),
7072
immichIntegration.getApiToken(),
73+
immichIntegration.isUseBestGuessLocation(),
7174
immichIntegration.isEnabled(),
7275
java.sql.Timestamp.from(now),
7376
immichIntegration.getId(),
@@ -89,18 +92,4 @@ public Optional<ImmichIntegration> findById(Long id) {
8992
return Optional.empty();
9093
}
9194
}
92-
93-
public void deleteById(Long id) {
94-
String sql = "DELETE FROM immich_integrations WHERE id = ?";
95-
int rowsAffected = jdbcTemplate.update(sql, id);
96-
if (rowsAffected == 0) {
97-
throw new EmptyResultDataAccessException("No ImmichIntegration found with id: " + id, 1);
98-
}
99-
}
100-
101-
public List<ImmichIntegration> findAll() {
102-
String sql = "SELECT ii.*" +
103-
"FROM immich_integrations ii ";
104-
return jdbcTemplate.query(sql, IMMICH_INTEGRATION_ROW_MAPPER);
105-
}
10695
}

src/main/java/com/dedicatedcode/reitti/service/integration/ImmichIntegrationService.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,19 @@ public Optional<ImmichIntegration> getIntegrationForUser(User user) {
5252
}
5353

5454
@Transactional
55-
public ImmichIntegration saveIntegration(User user, String serverUrl, String apiToken, boolean enabled) {
55+
public ImmichIntegration saveIntegration(User user, String serverUrl, String apiToken, boolean useBestGuessLocation, boolean enabled) {
5656
Optional<ImmichIntegration> existingIntegration = immichIntegrationJdbcService.findByUser(user);
5757

5858
ImmichIntegration integration;
5959
if (existingIntegration.isPresent()) {
6060
integration = existingIntegration.get()
6161
.withServerUrl(serverUrl)
6262
.withApiToken(apiToken)
63-
.withEnabled(enabled);
63+
.withEnabled(enabled)
64+
.withUseBestGuessLocation(useBestGuessLocation);
6465

6566
} else {
66-
integration = new ImmichIntegration(serverUrl, apiToken, enabled);
67+
integration = new ImmichIntegration(serverUrl, apiToken, useBestGuessLocation, enabled);
6768
}
6869

6970
return immichIntegrationJdbcService.save(user, integration);
@@ -137,7 +138,7 @@ public List<PhotoResponse> searchPhotosForRange(User user, LocalDate start, Loca
137138
);
138139

139140
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
140-
return convertToPhotoResponses(user, response.getBody(), baseUrl);
141+
return convertToPhotoResponses(user, integration, response.getBody());
141142
}
142143

143144
} catch (Exception e) {
@@ -147,7 +148,7 @@ public List<PhotoResponse> searchPhotosForRange(User user, LocalDate start, Loca
147148
return new ArrayList<>();
148149
}
149150

150-
private List<PhotoResponse> convertToPhotoResponses(User user, ImmichSearchResponse searchResponse, String baseUrl) {
151+
private List<PhotoResponse> convertToPhotoResponses(User user, ImmichIntegration integration, ImmichSearchResponse searchResponse) {
151152
List<PhotoResponse> photos = new ArrayList<>();
152153

153154
if (searchResponse.getAssets() != null && searchResponse.getAssets().getItems() != null) {
@@ -165,10 +166,9 @@ private List<PhotoResponse> convertToPhotoResponses(User user, ImmichSearchRespo
165166
if (asset.getExifInfo().getDateTimeOriginal() != null) {
166167
dateTime = asset.getExifInfo().getDateTimeOriginal();
167168
}
168-
169169
}
170170

171-
if (latitude == null && longitude == null) {
171+
if (integration.isUseBestGuessLocation() && latitude == null && longitude == null) {
172172
log.debug("Asset [{}] had no exif data, will try to match it to a point we know of.", asset.getId());
173173
ZonedDateTime takenAt = ZonedDateTime.parse(dateTime, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
174174
ZonedDateTime utc = takenAt.withZoneSameInstant(ZoneId.of("UTC"));
@@ -179,6 +179,9 @@ private List<PhotoResponse> convertToPhotoResponses(User user, ImmichSearchRespo
179179
timeMatched = true;
180180
}
181181
}
182+
if (latitude == null || longitude == null) {
183+
continue;
184+
}
182185
PhotoResponse photo = new PhotoResponse(
183186
asset.getId(),
184187
asset.getOriginalFileName(),
@@ -189,7 +192,6 @@ private List<PhotoResponse> convertToPhotoResponses(User user, ImmichSearchRespo
189192
dateTime,
190193
timeMatched
191194
);
192-
193195
photos.add(photo);
194196
}
195197
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE immich_integrations ADD COLUMN use_best_guess_location BOOLEAN NOT NULL DEFAULT TRUE;
2+
ALTER TABLE immich_integrations ALTER COLUMN use_best_guess_location DROP DEFAULT;

src/main/resources/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ integrations.immich.server.url.placeholder=https://your-immich-server.com
619619
integrations.immich.api.token=API Token
620620
integrations.immich.api.token.placeholder=Enter your Immich API token
621621
integrations.immich.enabled=Enable Integration
622+
integrations.immich.useBestGuessLocation=Enable Best-Guess-Location Algorithm
623+
integrations.immich.useBestGuessLocation.description=When enabled, Reitti will match photos without location data to the closest location on the map.
622624
integrations.immich.save=Save Configuration
623625
integrations.immich.test.connection=Test Connection
624626
integrations.immich.connection.success=Connection successful

src/main/resources/templates/settings/fragments/integrations.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,17 @@ <h3 th:text="#{integrations.immich.title}">🖼️ Immich Integration</h3>
546546
th:value="${hasIntegration ? immichIntegration.apiToken : ''}">
547547
</div>
548548

549+
<div class="form-group">
550+
<label>
551+
<input type="checkbox"
552+
name="useBestGuessLocation"
553+
value="true"
554+
th:checked="${hasIntegration && immichIntegration.useBestGuessLocation}">
555+
<span th:text="#{integrations.immich.useBestGuessLocation}">Enable Use-Best-Guess</span>
556+
</label>
557+
<small th:text="#{integrations.immich.useBestGuessLocation.description}">When enabled, Reitti will match photos without location data to the closest location on the map.</small>
558+
559+
</div>
549560
<div class="form-group">
550561
<label>
551562
<input type="checkbox"

0 commit comments

Comments
 (0)