Compare commits
5 Commits
29595d5a7f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a1faa395 | ||
|
|
d041298c5c | ||
|
|
b416e528b0 | ||
|
|
207f55d263 | ||
|
|
4b6a5fccf6 |
@@ -40,10 +40,39 @@ compose.desktop {
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "PhotoPhetch"
|
||||
packageVersion = "1.0.0"
|
||||
packageVersion = "1.0.1"
|
||||
description = "Import photos from phones into PhotoPhile"
|
||||
vendor = "bolenpad"
|
||||
copyright = "© 2026 Kyle Bolen"
|
||||
|
||||
modules(
|
||||
// Networking and naming — needed by logback JNDI
|
||||
"java.naming",
|
||||
// JMX — needed by logback JMX appender
|
||||
"java.management",
|
||||
// Instrumentation — needed by logback/class loading
|
||||
"java.instrument",
|
||||
// Reflection support for Unsafe — needed by coroutines, serialization
|
||||
"jdk.unsupported",
|
||||
// XML processing — needed by logback XML config parser
|
||||
"java.xml",
|
||||
// Logging — java.util.logging bridge
|
||||
"java.logging",
|
||||
// Security
|
||||
"java.security.jgss",
|
||||
"java.security.sasl",
|
||||
// Desktop/AWT (for Swing integration)
|
||||
"java.desktop",
|
||||
// Compiler API — sometimes needed by Kotlin reflection
|
||||
"java.compiler",
|
||||
// SQL — needed by some Kotlin stdlib internals
|
||||
"java.sql",
|
||||
// Network — needed by HTTP calls (Gitea API, pymobiledevice3)
|
||||
"java.net.http",
|
||||
// Preferences — used by some desktop apps
|
||||
"java.prefs",
|
||||
)
|
||||
|
||||
macOS {
|
||||
bundleID = "com.bolenpad.photophetch"
|
||||
// Uncomment and add icon.icns to src/main/resources/ for a custom icon:
|
||||
|
||||
11
release.sh
11
release.sh
@@ -39,9 +39,16 @@ fi
|
||||
NOTES=$(cat "$NOTES_FILE")
|
||||
echo "→ Release notes: $NOTES_FILE"
|
||||
|
||||
# ── Gitea token ───────────────────────────────────────────────────────────────
|
||||
# ── Gitea token — from macOS Keychain, env var, or prompt ────────────────────
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
read -rsp "GITEA_TOKEN not set. Enter token: " GITEA_TOKEN
|
||||
# Try macOS Keychain first
|
||||
GITEA_TOKEN=$(security find-generic-password -a kbolen -s gitea-photophetch -w 2>/dev/null || echo "")
|
||||
fi
|
||||
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||
echo "No token found in Keychain or environment."
|
||||
echo "Store it once with:"
|
||||
echo " security add-generic-password -a kbolen -s gitea-photophetch -w YOUR_TOKEN"
|
||||
read -rsp "Or enter token now: " GITEA_TOKEN
|
||||
echo
|
||||
fi
|
||||
|
||||
|
||||
9
releases/v1.0.1.md
Normal file
9
releases/v1.0.1.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## PhotoPhetch v1.0.1 — Packaging Fixes
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Fixed crash on launch** when installed as a .app bundle: bundled JVM was missing required modules (java.naming, java.xml, java.desktop, and others needed by logback and Kotlin coroutines)
|
||||
- **Fixed 'exiftool not installed' false warning** when launched as .app: the bundled app does not inherit the shell PATH, so Homebrew tools in /usr/local/bin and /opt/homebrew/bin were not found. ExternalTools now searches common Homebrew paths directly
|
||||
- **Fixed adb not found** when launched as .app: same PATH issue, now resolved lazily using Homebrew path search
|
||||
- **Fixed EXIF verification false warnings** on Android: Samsung and other Android manufacturers were incorrectly flagged as 'Origin unknown'. Any non-blank EXIF Make is now considered a verified camera original; only absent Make triggers the warning
|
||||
- **Preserve folder structure now sticky**: the toggle in the Import Configuration screen now saves your preference and remembers it between imports
|
||||
@@ -24,6 +24,12 @@ class AndroidConnector(
|
||||
|
||||
private val log = LoggerFactory.getLogger(AndroidConnector::class.java)
|
||||
|
||||
// Resolve adb path lazily — ExternalTools searches Homebrew paths
|
||||
// so the packaged .app finds adb even without shell PATH
|
||||
private val resolvedAdb: String by lazy {
|
||||
com.bolenpad.photophetch.util.ExternalTools.resolve(adbBin)
|
||||
}
|
||||
|
||||
// Directories on Android that contain camera-roll originals.
|
||||
// Used as fallback if dynamic DCIM discovery finds nothing.
|
||||
private val fallbackCameraDirs = listOf(
|
||||
@@ -213,7 +219,7 @@ class AndroidConnector(
|
||||
|
||||
private fun runAdb(serial: String?, vararg args: String): String {
|
||||
val cmd = buildList {
|
||||
add(adbBin)
|
||||
add(resolvedAdb)
|
||||
if (serial != null) {
|
||||
add("-s")
|
||||
add(serial)
|
||||
|
||||
@@ -247,7 +247,7 @@ class Copier(
|
||||
*/
|
||||
private fun readExifMake(file: Path): String? {
|
||||
return try {
|
||||
val process = ProcessBuilder("exiftool", "-Make", "-s3", file.absolutePathString())
|
||||
val process = ProcessBuilder(com.bolenpad.photophetch.util.ExternalTools.resolve("exiftool"), "-Make", "-s3", file.absolutePathString())
|
||||
.redirectErrorStream(false) // keep stderr separate
|
||||
.start()
|
||||
val stdout = process.inputStream.bufferedReader().readText()
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bolenpad.photophetch.model.DeviceInfo
|
||||
import com.bolenpad.photophetch.util.ExternalTools
|
||||
import com.bolenpad.photophetch.model.DeviceType
|
||||
|
||||
@Composable
|
||||
@@ -49,7 +50,8 @@ fun ConnectScreen(state: AppState) {
|
||||
// exiftool warning — checked once per session
|
||||
val exiftoolMissing = remember {
|
||||
try {
|
||||
val p = ProcessBuilder("exiftool", "-ver").redirectErrorStream(true).start()
|
||||
val p = ProcessBuilder(ExternalTools.resolve("exiftool"), "-ver")
|
||||
.redirectErrorStream(true).start()
|
||||
p.inputStream.readBytes()
|
||||
p.waitFor() != 0
|
||||
} catch (_: Exception) { true }
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bolenpad.photophetch.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Resolves paths for external command-line tools (exiftool, adb, python3, etc.)
|
||||
*
|
||||
* macOS .app bundles launch with a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin)
|
||||
* that doesn't include Homebrew. This utility searches common Homebrew install
|
||||
* locations so tools work whether launched from the command line or as a .app.
|
||||
*/
|
||||
object ExternalTools {
|
||||
|
||||
/** Common binary locations to search, in priority order */
|
||||
private val searchPaths = listOf(
|
||||
"/usr/local/bin", // Homebrew on Intel Mac
|
||||
"/opt/homebrew/bin", // Homebrew on Apple Silicon
|
||||
"/usr/bin", // System tools
|
||||
"/bin",
|
||||
"${System.getProperty("user.home")}/.local/bin", // pipx and user installs
|
||||
"/usr/local/opt/exiftool/bin", // Homebrew formula-specific
|
||||
)
|
||||
|
||||
/**
|
||||
* Find the full path to a tool binary.
|
||||
* Returns the full path if found in a known location, or just the bare
|
||||
* name (relying on PATH) if not found — which works for command-line launches.
|
||||
*/
|
||||
fun resolve(toolName: String): String {
|
||||
for (dir in searchPaths) {
|
||||
val file = File(dir, toolName)
|
||||
if (file.exists() && file.canExecute()) {
|
||||
return file.absolutePath
|
||||
}
|
||||
}
|
||||
return toolName // fall back to PATH lookup
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is available (either in known paths or on PATH).
|
||||
*/
|
||||
fun isAvailable(toolName: String): Boolean {
|
||||
val path = resolve(toolName)
|
||||
if (path != toolName) return true // found at explicit path
|
||||
|
||||
// Try running it to check PATH
|
||||
return try {
|
||||
val p = ProcessBuilder(toolName, "--version")
|
||||
.redirectErrorStream(true).start()
|
||||
p.inputStream.readBytes()
|
||||
p.waitFor() == 0 || true // any exit = it exists
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user