Initialize ID for PF2e imported monsters to prevent SQLite exceptions

This commit is contained in:
2026-09-22 20:35:44 -07:00
committed by Tom
parent a7b0e68197
commit 35f45513aa
6 changed files with 213 additions and 87 deletions

View File

@@ -17,6 +17,7 @@ public class ImportConfig {
ImportSource.ImportType.OPEN5E_API,
null,
null,
null,
null
));
@@ -28,7 +29,20 @@ public class ImportConfig {
ImportSource.ImportType.GIT_ARCHIVE,
"https://github.com/foundryvtt/pf2e/archive/refs/heads/master.zip",
"com.majinnaibu.monstercards.importers.Pf2eImporter",
".json"
".json",
"packs/pf2e"
));
SOURCES.add(new ImportSource(
"sf2e_foundry",
"Starfinder 2e Foundry VTT",
"foundryvtt",
"https://github.com/foundryvtt/pf2e",
ImportSource.ImportType.GIT_ARCHIVE,
"https://github.com/foundryvtt/pf2e/archive/refs/heads/master.zip",
"com.majinnaibu.monstercards.importers.Pf2eImporter",
".json",
"packs/sf2e"
));
}
}

View File

@@ -1,7 +1,7 @@
package com.majinnaibu.monstercards.importers;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import androidx.annotation.NonNull;
@@ -31,19 +31,30 @@ public class GitRepoImporterService {
public static int importFromGitRepository(Context context, ImportSource source, BooleanSupplier isCancelled) throws Exception {
int totalImported = 0;
File tempZip = File.createTempFile("repo_download", ".zip", context.getCacheDir());
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (!downloadsDir.exists()) {
downloadsDir.mkdirs();
}
File tempZip = null;
File[] existingZips = downloadsDir.listFiles((dir, name) -> name.startsWith(source.id + "_download") && name.endsWith(".zip"));
if (existingZips != null && existingZips.length > 0) {
tempZip = existingZips[0];
} else {
tempZip = new File(downloadsDir, source.id + "_download_" + System.currentTimeMillis() + ".zip");
downloadZip(source.downloadUrl, tempZip, isCancelled);
}
File extractDir = new File(context.getCacheDir(), "repo_extracted_" + System.currentTimeMillis());
try {
downloadZip(source.downloadUrl, tempZip, isCancelled);
if (isCancelled.getAsBoolean()) return 0;
if (!extractDir.exists()) {
extractDir.mkdirs();
}
unzipAndFilter(tempZip, extractDir, source.fileExtension, isCancelled);
unzipAndFilter(tempZip, extractDir, source.fileExtension, source.subfolder, isCancelled);
if (isCancelled.getAsBoolean()) return 0;
@@ -58,7 +69,8 @@ public class GitRepoImporterService {
totalImported = processDirectory(extractDir, importer, repository, isCancelled);
} finally {
deleteFileOrDir(tempZip);
// We no longer delete tempZip since the user wants to keep the downloaded zip
// deleteFileOrDir(tempZip);
deleteFileOrDir(extractDir);
}
@@ -97,14 +109,15 @@ public class GitRepoImporterService {
}
}
private static void unzipAndFilter(File zipFile, File extractDir, String filterExtension, BooleanSupplier isCancelled) throws IOException {
private static void unzipAndFilter(File zipFile, File extractDir, String filterExtension, String subfolder, BooleanSupplier isCancelled) throws IOException {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (isCancelled.getAsBoolean()) break;
if (!entry.isDirectory()) {
String name = entry.getName();
if (filterExtension == null || name.endsWith(filterExtension)) {
boolean inSubfolder = subfolder == null || subfolder.isEmpty() || name.contains("/" + subfolder + "/") || name.startsWith(subfolder + "/");
if (inSubfolder && (filterExtension == null || name.endsWith(filterExtension))) {
File outFile = new File(extractDir, new File(name).getName());
if (outFile.exists()) {
outFile = new File(extractDir, System.currentTimeMillis() + "_" + new File(name).getName());

View File

@@ -16,6 +16,7 @@ import com.majinnaibu.monstercards.models.Skill;
import com.majinnaibu.monstercards.models.Trait;
import java.util.Locale;
import java.util.UUID;
public class Pf2eImporter implements EntityImporter<Monster> {
@Override
@@ -43,6 +44,7 @@ public class Pf2eImporter implements EntityImporter<Monster> {
JsonObject system = root.has("system") ? root.getAsJsonObject("system") : new JsonObject();
Monster monster = new Monster();
monster.id = UUID.randomUUID();
// Name
if (root.has("name") && !root.get("name").isJsonNull()) {

View File

@@ -16,8 +16,9 @@ public class ImportSource {
public String downloadUrl;
public String importerClassName;
public String fileExtension;
public String subfolder;
public ImportSource(String id, String projectName, String creatorName, String creatorPageLink, ImportType importType, String downloadUrl, String importerClassName, String fileExtension) {
public ImportSource(String id, String projectName, String creatorName, String creatorPageLink, ImportType importType, String downloadUrl, String importerClassName, String fileExtension, String subfolder) {
this.id = id;
this.projectName = projectName;
this.creatorName = creatorName;
@@ -26,5 +27,6 @@ public class ImportSource {
this.downloadUrl = downloadUrl;
this.importerClassName = importerClassName;
this.fileExtension = fileExtension;
this.subfolder = subfolder;
}
}

View File

@@ -1,5 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="Collection" representedClassName="Collection" syncable="YES" codeGenerationType="category">
<attribute name="description" attributeType="String" defaultValueString=""/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<attribute name="sortOrder" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="monsters" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="CollectionMonster" inverseName="collection" inverseEntity="CollectionMonster"/>
</entity>
<entity name="CollectionMonster" representedClassName="CollectionMonster" syncable="YES" codeGenerationType="category">
<attribute name="collectionId" attributeType="String" defaultValueString="00000000-0000-0000-0000-000000000000"/>
<attribute name="monsterId" attributeType="String" defaultValueString="00000000-0000-0000-0000-000000000000"/>
<attribute name="ordinal" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="collection" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Collection" inverseName="monsters" inverseEntity="Collection"/>
</entity>
<entity name="Monster" representedClassName="Monster" syncable="YES" codeGenerationType="category">
<attribute name="abilities" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="actions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
@@ -9,7 +21,7 @@
<attribute name="burrowSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="canHover" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="challengeRating" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowAdvantage" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="charismaSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="charismaScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="climbSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
@@ -59,10 +71,12 @@
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="type" attributeType="String" defaultValueString=""/>
<attribute name="understandsBut" attributeType="String" defaultValueString=""/>
<attribute name="uuid" optional="YES" attributeType="UUID"/>
<attribute name="walkSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wisdomSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="createdDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="skills" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Skill" inverseName="monster" inverseEntity="Skill"/>
</entity>
<entity name="Skill" representedClassName="Skill" syncable="YES" codeGenerationType="category">
@@ -73,7 +87,9 @@
<relationship name="monster" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Monster" inverseName="skills" inverseEntity="Monster"/>
</entity>
<elements>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="974"/>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="1154"/>
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
<element name="Collection" positionX="-220" positionY="0" width="128" height="112"/>
<element name="CollectionMonster" positionX="-63" positionY="260" width="128" height="128"/>
</elements>
</model>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20D91" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="Monster" representedClassName="Monster" syncable="YES" codeGenerationType="category">
<attribute name="abilities" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="actions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="alignment" attributeType="String" defaultValueString=""/>
<attribute name="armorType" attributeType="String" defaultValueString=""/>
<attribute name="blindsightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="burrowSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="canHover" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="challengeRating" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowAdvantage" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="charismaScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="climbSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="conditionImmunities" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
<attribute name="constitutionSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="constitutionSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="constitutionScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="customArmor" attributeType="String" defaultValueString=""/>
<attribute name="customChallengeRating" attributeType="String" defaultValueString=""/>
<attribute name="customHP" attributeType="String" defaultValueString=""/>
<attribute name="customProficiencyBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="customSpeed" attributeType="String" defaultValueString=""/>
<attribute name="damageImmunities" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
<attribute name="damageResistances" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
<attribute name="damageVulnerabilities" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
<attribute name="darkvisionDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="dexteritySavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="dexteritySavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="dexterityScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="flySpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hasCustomHP" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hasCustomSpeed" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hasShield" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hitDice" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="intelligenceSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="intelligenceSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="intelligenceScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="isBlind" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lairActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="languages" optional="YES" attributeType="Transformable" valueTransformerName="LanguageViewModelValueTransformer" customClassName="[LanguageViewModel]"/>
<attribute name="legendaryActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="name" attributeType="String" defaultValueString="Unnamed Monster"/>
<attribute name="naturalArmorBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="otherArmorDescription" attributeType="String" defaultValueString=""/>
<attribute name="reactions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="regionalActions" optional="YES" attributeType="Transformable" valueTransformerName="AbilityViewModelValueTransformer" customClassName="[AbilityViewModel]"/>
<attribute name="senses" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName" customClassName="[String]"/>
<attribute name="shieldBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="size" attributeType="String" defaultValueString=""/>
<attribute name="strengthSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="strengthSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="strengthScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="subtype" attributeType="String" defaultValueString=""/>
<attribute name="swimSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="telepathy" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tremorsenseDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="type" attributeType="String" defaultValueString=""/>
<attribute name="understandsBut" attributeType="String" defaultValueString=""/>
<attribute name="walkSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wisdomSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<relationship name="skills" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Skill" inverseName="monster" inverseEntity="Skill"/>
</entity>
<entity name="Skill" representedClassName="Skill" syncable="YES" codeGenerationType="category">
<attribute name="abilityScoreName" attributeType="String" defaultValueString="strength"/>
<attribute name="advantage" attributeType="String" defaultValueString="none"/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<attribute name="proficiency" attributeType="String" defaultValueString="none"/>
<relationship name="monster" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Monster" inverseName="skills" inverseEntity="Monster"/>
</entity>
<elements>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="974"/>
<element name="Skill" positionX="-63" positionY="135" width="128" height="14"/>
</elements>
</model>