Skip to content

Commit

Permalink
Mod meta file timestamp handling + cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
Aquerr committed Dec 29, 2024
1 parent 923238b commit 8befcb9
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import org.springframework.stereotype.Component;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.mod.model.InstalledModEntity;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.mod.InstalledFileSystemMod;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.mod.MetaCppFile;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.SteamService;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopMod;

import java.time.OffsetDateTime;
import java.util.Optional;

@Slf4j
@AllArgsConstructor
Expand All @@ -20,32 +19,21 @@ public class InstalledModEntityHelper

public InstalledModEntity toEntity(InstalledFileSystemMod fileSystemMod)
{
MetaCppFile metaCppFile = fileSystemMod.getModMetaFile().orElseThrow();
InstalledModEntity.InstalledModEntityBuilder installedModBuilder = InstalledModEntity.builder();
installedModBuilder.workshopFileId(metaCppFile.getPublishedFileId());
installedModBuilder.name(metaCppFile.getName());
installedModBuilder.workshopFileId(fileSystemMod.getWorkshopFileId());
installedModBuilder.name(fileSystemMod.getName());
installedModBuilder.directoryPath(fileSystemMod.getModDirectory().getPath().toAbsolutePath().toString());
installedModBuilder.createdDate(OffsetDateTime.now());
installedModBuilder.lastWorkshopUpdate(fileSystemMod.getLastUpdated());

tryPopulateModPreviewUrl(metaCppFile.getPublishedFileId(), metaCppFile.getName(), installedModBuilder);
tryPopulateModPreviewUrl(fileSystemMod.getWorkshopFileId(), installedModBuilder);
return installedModBuilder.build();
}

private void tryPopulateModPreviewUrl(long publishedFileId,
String modName,
InstalledModEntity.InstalledModEntityBuilder installedModBuilder)
{
try
{
WorkshopMod workshopMod = steamService.getWorkshopMod(publishedFileId);
if (workshopMod != null)
{
installedModBuilder.previewUrl(workshopMod.getPreviewUrl());
}
}
catch (Exception exception)
{
log.warn("Could not fetch mod preview url. Mod = {}", modName, exception);
}
Optional.ofNullable(steamService.getWorkshopMod(publishedFileId))
.ifPresent(workshopMod -> installedModBuilder.previewUrl(workshopMod.getPreviewUrl()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,4 @@ public WorkshopMod convertToWorkshopMod(InstalledModEntity installedModEntity)
.modWorkshopUrl(workshopUrlBuilder.buildUrlForFileId(installedModEntity.getWorkshopFileId()))
.build();
}

public InstalledFileSystemMod toFileSystemMod(InstalledModEntity installedModEntity)
{
return InstalledFileSystemMod.from(Paths.get(installedModEntity.getDirectoryPath()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.nio.file.Path;

@Slf4j
public class CppFileHelper
public final class CppFileHelper
{
public static final String META_CPP = "meta.cpp";
public static final String MOD_CPP = "mod.cpp";
Expand All @@ -35,4 +35,9 @@ public static <T extends CppFile> T readFile(Path filePath, Class<?> clazz)
}
return instance;
}

private CppFileHelper()
{
throw new IllegalAccessError("Utility class");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,23 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.util.dotnet.DotnetDateTimeUtils;

import java.nio.file.Path;
import java.util.Optional;
import java.time.OffsetDateTime;

import static java.util.Optional.ofNullable;

@Getter
@EqualsAndHashCode
@ToString
public class InstalledFileSystemMod
{
private Optional<MetaCppFile> modMetaFile = Optional.empty();
private Optional<ModCppFile> modFile = Optional.empty();

private final ModDirectory modDirectory;

private InstalledFileSystemMod(ModDirectory modDirectory)
{
this.modDirectory = modDirectory;
try
{
MetaCppFile metaCppFile = modDirectory.readModMetaFile();
this.modMetaFile = Optional.ofNullable(metaCppFile);
}
catch (Exception exception)
{
// Nothing
}
}

public static InstalledFileSystemMod from(Path modDirectory)
Expand All @@ -43,16 +34,24 @@ public static InstalledFileSystemMod from(ModDirectory modDirectory)

public long getWorkshopFileId()
{
return this.modMetaFile.map(MetaCppFile::getPublishedFileId).orElse(0L);
return ofNullable(this.modDirectory.getMetaCppFile()).map(MetaCppFile::getPublishedFileId).orElse(0L);
}

public String getName()
{
return this.modMetaFile.map(MetaCppFile::getName).orElse(modDirectory.getDirectoryName());
return this.modDirectory.getModName();
}

public boolean isValid()
{
return modMetaFile.isPresent();
return ofNullable(this.modDirectory.getMetaCppFile()).isPresent();
}

public OffsetDateTime getLastUpdated()
{
return ofNullable(this.modDirectory.getMetaCppFile())
.map(MetaCppFile::getTimestamp)
.map(DotnetDateTimeUtils::dotnetTicksToOffsetDateTime)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ public final class MetaCppFile implements CppFile
private long publishedFileId;
@CfgProperty(name = "name", type = PropertyType.QUOTED_STRING)
private String name;
@CfgProperty(name = "timestamp", type = PropertyType.LONG)
private long timestamp;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@

public class ModDirectory
{
Path path;

Lazy<MetaCppFile> metaCppFile = Lazy.of(() -> CppFileHelper.readFile(path.resolve(CppFileHelper.META_CPP), MetaCppFile.class));

Lazy<ModCppFile> modCppFile = Lazy.of(() -> CppFileHelper.readFile(path.resolve(CppFileHelper.MOD_CPP), ModCppFile.class));
private final Path path;
private final Lazy<MetaCppFile> metaCppFile;
private final Lazy<ModCppFile> modCppFile;

public static ModDirectory from(Path modDirectory)
{
Expand All @@ -22,6 +20,8 @@ public static ModDirectory from(Path modDirectory)
private ModDirectory(Path modDirectory)
{
this.path = modDirectory;
this.metaCppFile = Lazy.of(() -> CppFileHelper.readFile(path.resolve(CppFileHelper.META_CPP), MetaCppFile.class));
this.modCppFile = Lazy.of(() -> CppFileHelper.readFile(path.resolve(CppFileHelper.MOD_CPP), ModCppFile.class));
}

public boolean hasModMetaFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public Mono<Boolean> deleteMod(InstalledModEntity installedModEntity)
{
if (file.getName().equals(installedModEntity.getModDirectoryName()))
{
log.info("Deleting mod directory {}", installedModEntity.getModDirectoryName());
log.info("Deleting mod directory {}", installedModEntity.getDirectoryPath());
FileUtils.deleteFilesRecursively(file.toPath(), true);
break;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.util.dotnet;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

public final class DotnetDateTimeUtils
{
public static OffsetDateTime dotnetTicksToOffsetDateTime(final long fromBytes)
{
// Mask out kind and ticks
int kind = Math.toIntExact((fromBytes >> 62) & 0x3);
long ticks = fromBytes & 0x3FFF_FFFF_FFFF_FFFFL;
LocalDateTime cSharpEpoch = LocalDate.of(1, Month.JANUARY, 1).atStartOfDay();
// 100 nanosecond units or 10^-7 seconds
final int unitsPerSecond = 10_000_000;
long seconds = ticks / unitsPerSecond;
long nanos = (ticks % unitsPerSecond) * 100;
LocalDateTime localDateTime = cSharpEpoch.plusSeconds(seconds).plusNanos(nanos);

if (kind > 2 || kind < 0) {
throw new IllegalArgumentException("Invalid ticks kind: " + kind);
}

return OffsetDateTime.of(localDateTime, ZoneOffset.UTC);
}

private DotnetDateTimeUtils()
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopModInstallSteamTask;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopQueryParams;

import javax.annotation.Nullable;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
Expand Down Expand Up @@ -83,6 +84,7 @@ public UUID scheduleArmaUpdate()
return this.steamCmdHandler.queueSteamTask(new GameUpdateSteamTask());
}

@Nullable
@Override
public WorkshopMod getWorkshopMod(long modId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@
import io.github.aquerr.steamwebapiclient.response.PublishedFileDetailsResponse;
import io.github.aquerr.steamwebapiclient.response.WorkShopQueryResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopMod;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.ArmaWorkshopQueryResponse;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopQueryParams;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
@AllArgsConstructor
public class SteamWebApiService
Expand Down Expand Up @@ -51,6 +54,7 @@ public ArmaWorkshopQueryResponse queryWorkshopMods(WorkshopQueryParams params)
.build();
}

@Nullable
public WorkshopMod getWorkshopMod(long modId)
{
try
Expand All @@ -65,7 +69,7 @@ public WorkshopMod getWorkshopMod(long modId)
}
catch (Exception exception)
{
exception.printStackTrace();
log.warn("Could not fetch mod info from workshop. Reason: {}", exception.getMessage());
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package pl.bartlomiejstepien.armaserverwebgui.domain.server.mod;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.mod.model.InstalledModEntity;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.mod.InstalledFileSystemMod;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.mod.MetaCppFile;
import pl.bartlomiejstepien.armaserverwebgui.domain.server.storage.mod.ModDirectory;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.SteamService;
import pl.bartlomiejstepien.armaserverwebgui.domain.steam.model.WorkshopMod;

import java.nio.file.Paths;
import java.time.OffsetDateTime;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
class InstalledModEntityHelperTest
{
@Mock
private ModDirectory modDirectory;

@Mock
private SteamService steamService;

@InjectMocks
private InstalledModEntityHelper installedModEntityHelper;

@Test
void shouldConvertInstalledFileSystemModToEntity()
{
//given
MetaCppFile metaCppFile = mock(MetaCppFile.class);

given(modDirectory.getModName()).willReturn("ACE");
given(modDirectory.getMetaCppFile()).willReturn(metaCppFile);
given(modDirectory.getPath()).willReturn(Paths.get("./ace-mod"));
given(metaCppFile.getPublishedFileId()).willReturn(123456789L);
given(metaCppFile.getTimestamp()).willReturn(5250320079498121474L);

given(steamService.getWorkshopMod(123456789L)).willReturn(WorkshopMod.builder()
.previewUrl("previewurl")
.build());

InstalledFileSystemMod installedFileSystemMod = prepareFileSystemMod();

// when
InstalledModEntity entity = installedModEntityHelper.toEntity(installedFileSystemMod);

// then
assertThat(entity.getId()).isNull();
assertThat(entity.getName()).isEqualTo("ACE");
assertThat(entity.getWorkshopFileId()).isEqualTo(123456789L);
assertThat(entity.getDirectoryPath()).isEqualTo(Paths.get("./ace-mod").toAbsolutePath().toString());
assertThat(entity.getCreatedDate()).isBeforeOrEqualTo(OffsetDateTime.now());
assertThat(entity.getLastWorkshopUpdate()).isEqualTo(OffsetDateTime.parse("2024-10-01T19:01:47.073357Z"));
assertThat(entity.getPreviewUrl()).isEqualTo("previewurl");
}

@Test
void shouldNotSetModPreviewWhenCouldNotFetchModInfoFromWorkshop()
{
//given
MetaCppFile metaCppFile = mock(MetaCppFile.class);

given(modDirectory.getModName()).willReturn("A3 Thermal Improvement");
given(modDirectory.getMetaCppFile()).willReturn(metaCppFile);
given(modDirectory.getPath()).willReturn(Paths.get("./@A3-thermal-improvement"));
given(metaCppFile.getPublishedFileId()).willReturn(2041057379L);
given(metaCppFile.getTimestamp()).willReturn(5250320079498121474L);

given(steamService.getWorkshopMod(2041057379L)).willReturn(null);

InstalledFileSystemMod installedFileSystemMod = prepareFileSystemMod();

// when
InstalledModEntity entity = installedModEntityHelper.toEntity(installedFileSystemMod);

// then
assertThat(entity.getId()).isNull();
assertThat(entity.getName()).isEqualTo("A3 Thermal Improvement");
assertThat(entity.getWorkshopFileId()).isEqualTo(2041057379L);
assertThat(entity.getDirectoryPath()).isEqualTo(Paths.get("./@A3-thermal-improvement").toAbsolutePath().toString());
assertThat(entity.getCreatedDate()).isBeforeOrEqualTo(OffsetDateTime.now());
assertThat(entity.getLastWorkshopUpdate()).isEqualTo(OffsetDateTime.parse("2024-10-01T19:01:47.073357Z"));
assertThat(entity.getPreviewUrl()).isNull();
}

private InstalledFileSystemMod prepareFileSystemMod()
{
return InstalledFileSystemMod.from(modDirectory);
}
}

0 comments on commit 8befcb9

Please sign in to comment.