feat(update): APK-Cache robuster + manueller 'Update-Cache leeren' Button
Stefan: 'app blaeht sich auf durch heruntergeladene Update-Versionen'.
updater.ts:
- cleanupOldApks durchsucht jetzt 4 Pfade (Caches, Documents, ExternalCaches,
ExternalDir) statt nur CachesDirectoryPath
- Public gemacht + returnt {removed, freedMB}
- getApkCacheSize() neu — listet count + totalMB
SettingsScreen → Speicher:
- Neue Sektion 'Update-Cache' mit Live-Groessenanzeige
- Button 'Update-Cache leeren' triggert cleanup + Toast mit Ergebnis
- Beim Mount wird die Groesse einmal geladen
Auto-Cleanup laeuft weiterhin beim App-Start + vor jedem Download —
der Button ist fuer den Notfall (haengender Download, alte Pfade,
defekte APKs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,7 @@ import wakeWordService, {
|
|||||||
import ModeSelector from '../components/ModeSelector';
|
import ModeSelector from '../components/ModeSelector';
|
||||||
import QRScanner from '../components/QRScanner';
|
import QRScanner from '../components/QRScanner';
|
||||||
import VoiceCloneModal from '../components/VoiceCloneModal';
|
import VoiceCloneModal from '../components/VoiceCloneModal';
|
||||||
|
import updateService from '../services/updater';
|
||||||
|
|
||||||
const STORAGE_PATH_KEY = 'aria_attachment_storage_path';
|
const STORAGE_PATH_KEY = 'aria_attachment_storage_path';
|
||||||
const DEFAULT_STORAGE_PATH = `${RNFS.DocumentDirectoryPath}/chat_attachments`;
|
const DEFAULT_STORAGE_PATH = `${RNFS.DocumentDirectoryPath}/chat_attachments`;
|
||||||
@@ -132,6 +133,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
// null = automatisch (adaptive Baseline), sonst manueller dB-Override
|
// null = automatisch (adaptive Baseline), sonst manueller dB-Override
|
||||||
const [vadSilenceDb, setVadSilenceDb] = useState<number | null>(null);
|
const [vadSilenceDb, setVadSilenceDb] = useState<number | null>(null);
|
||||||
const [showVadInfo, setShowVadInfo] = useState(false);
|
const [showVadInfo, setShowVadInfo] = useState(false);
|
||||||
|
const [apkCacheInfo, setApkCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
|
||||||
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
||||||
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
||||||
const [wakeStatus, setWakeStatus] = useState<string>('');
|
const [wakeStatus, setWakeStatus] = useState<string>('');
|
||||||
@@ -220,6 +222,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved);
|
if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved);
|
||||||
});
|
});
|
||||||
isWakeReadySoundEnabled().then(setWakeReadySound);
|
isWakeReadySoundEnabled().then(setWakeReadySound);
|
||||||
|
updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
|
||||||
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
||||||
if (saved) setXttsVoice(saved);
|
if (saved) setXttsVoice(saved);
|
||||||
});
|
});
|
||||||
@@ -1194,6 +1197,37 @@ const SettingsScreen: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* === Update-Cache === */}
|
||||||
|
<Text style={[styles.sectionTitle, {marginTop: 16}]}>Update-Cache</Text>
|
||||||
|
<View style={styles.card}>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Heruntergeladene APK-Dateien fuer App-Updates. Werden automatisch
|
||||||
|
beim App-Start und vor jedem neuen Download geloescht — der Button
|
||||||
|
ist fuer den Notfall (z.B. wenn ein Download haengen geblieben ist).
|
||||||
|
</Text>
|
||||||
|
<Text style={[styles.storageSizeText, {marginTop: 8}]}>
|
||||||
|
{apkCacheInfo === null ? '...' :
|
||||||
|
apkCacheInfo.count === 0 ? 'leer' :
|
||||||
|
`${apkCacheInfo.count} APK${apkCacheInfo.count === 1 ? '' : 's'} · ${apkCacheInfo.totalMB.toFixed(1)}MB`}
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.15)'}]}
|
||||||
|
onPress={async () => {
|
||||||
|
const res = await updateService.cleanupOldApks();
|
||||||
|
ToastAndroid.show(
|
||||||
|
res.removed === 0
|
||||||
|
? 'Update-Cache war schon leer'
|
||||||
|
: `${res.removed} APK${res.removed === 1 ? '' : 's'} geloescht (${res.freedMB.toFixed(1)}MB frei)`,
|
||||||
|
ToastAndroid.SHORT,
|
||||||
|
);
|
||||||
|
const info = await updateService.getApkCacheSize();
|
||||||
|
setApkCacheInfo(info);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>Update-Cache leeren</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
{/* === Logs === */}
|
{/* === Logs === */}
|
||||||
|
|||||||
@@ -50,29 +50,70 @@ class UpdateService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Raeumt alte heruntergeladene APK-Dateien aus dem Cache auf. */
|
/** Sucht ueberall wo .apk-Dateien herumliegen koennten. */
|
||||||
private async cleanupOldApks(): Promise<void> {
|
private async _apkSearchDirs(): Promise<string[]> {
|
||||||
try {
|
const dirs = [RNFS.CachesDirectoryPath, RNFS.DocumentDirectoryPath];
|
||||||
const files = await RNFS.readDir(RNFS.CachesDirectoryPath);
|
if ((RNFS as any).ExternalCachesDirectoryPath) {
|
||||||
const apks = files.filter(f => /\.apk$/i.test(f.name));
|
dirs.push((RNFS as any).ExternalCachesDirectoryPath);
|
||||||
|
}
|
||||||
|
if (RNFS.ExternalDirectoryPath) {
|
||||||
|
dirs.push(RNFS.ExternalDirectoryPath);
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Raeumt alte heruntergeladene APK-Dateien aus den App-Verzeichnissen auf.
|
||||||
|
* Public damit Settings den Button "Update-Cache leeren" benutzen kann. */
|
||||||
|
async cleanupOldApks(keepCurrentName?: string): Promise<{ removed: number; freedMB: number }> {
|
||||||
|
const dirs = await this._apkSearchDirs();
|
||||||
|
let removed = 0;
|
||||||
let freed = 0;
|
let freed = 0;
|
||||||
|
for (const dir of dirs) {
|
||||||
|
try {
|
||||||
|
if (!(await RNFS.exists(dir))) continue;
|
||||||
|
const files = await RNFS.readDir(dir);
|
||||||
|
const apks = files.filter(f => /\.apk$/i.test(f.name));
|
||||||
for (const f of apks) {
|
for (const f of apks) {
|
||||||
|
if (keepCurrentName && f.name === keepCurrentName) continue;
|
||||||
try {
|
try {
|
||||||
const size = parseInt(f.size as any, 10) || 0;
|
const size = parseInt(f.size as any, 10) || 0;
|
||||||
await RNFS.unlink(f.path);
|
await RNFS.unlink(f.path);
|
||||||
|
removed += 1;
|
||||||
freed += size;
|
freed += size;
|
||||||
console.log(`[Update] Alte APK geloescht: ${f.name} (${(size / 1024 / 1024).toFixed(1)}MB)`);
|
console.log(`[Update] APK geloescht: ${f.path} (${(size / 1024 / 1024).toFixed(1)}MB)`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.warn(`[Update] APK-Loeschen fehlgeschlagen: ${f.name} (${err?.message || err})`);
|
console.warn(`[Update] APK-Loeschen fehlgeschlagen: ${f.path} (${err?.message || err})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (apks.length > 0) {
|
|
||||||
console.log(`[Update] Cleanup fertig: ${apks.length} APKs entfernt, ${(freed / 1024 / 1024).toFixed(1)}MB freigegeben`);
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.warn(`[Update] Cleanup-Fehler: ${err?.message || err}`);
|
console.warn(`[Update] Cleanup-Fehler in ${dir}: ${err?.message || err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const freedMB = freed / 1024 / 1024;
|
||||||
|
if (removed > 0) {
|
||||||
|
console.log(`[Update] Cleanup fertig: ${removed} APK${removed === 1 ? '' : 's'} entfernt, ${freedMB.toFixed(1)}MB freigegeben`);
|
||||||
|
}
|
||||||
|
return { removed, freedMB };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Aktuelle Groesse aller APK-Dateien in den App-Verzeichnissen (in MB). */
|
||||||
|
async getApkCacheSize(): Promise<{ count: number; totalMB: number }> {
|
||||||
|
const dirs = await this._apkSearchDirs();
|
||||||
|
let count = 0;
|
||||||
|
let total = 0;
|
||||||
|
for (const dir of dirs) {
|
||||||
|
try {
|
||||||
|
if (!(await RNFS.exists(dir))) continue;
|
||||||
|
const files = await RNFS.readDir(dir);
|
||||||
|
for (const f of files) {
|
||||||
|
if (!f.isFile() || !/\.apk$/i.test(f.name)) continue;
|
||||||
|
count += 1;
|
||||||
|
total += parseInt(f.size as any, 10) || 0;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return { count, totalMB: total / 1024 / 1024 };
|
||||||
|
}
|
||||||
|
|
||||||
/** Bei App-Start Update pruefen */
|
/** Bei App-Start Update pruefen */
|
||||||
checkForUpdate(): void {
|
checkForUpdate(): void {
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ Wichtige Mechanismen:
|
|||||||
- [x] **Neue Frage waehrend Telefonat** ueberschreibt pending Auto-Resume — letzte Antwort gewinnt, alter resumeSound wird gestoppt
|
- [x] **Neue Frage waehrend Telefonat** ueberschreibt pending Auto-Resume — letzte Antwort gewinnt, alter resumeSound wird gestoppt
|
||||||
- [x] **Audio-Ausgabe waehrend aktivem Telefonat** funktioniert (haltAllPlayback nur bei state-Wechsel idle→ringing/offhook, nicht bei offhook→offhook)
|
- [x] **Audio-Ausgabe waehrend aktivem Telefonat** funktioniert (haltAllPlayback nur bei state-Wechsel idle→ringing/offhook, nicht bei offhook→offhook)
|
||||||
- [x] **PcmPlaybackFinished-Event** im Native: AudioFocus wird erst released wenn AudioTrack wirklich durch ist (vorher: end()-Cap nach 0.5s → Spotify spielte 32s parallel zu ARIA)
|
- [x] **PcmPlaybackFinished-Event** im Native: AudioFocus wird erst released wenn AudioTrack wirklich durch ist (vorher: end()-Cap nach 0.5s → Spotify spielte 32s parallel zu ARIA)
|
||||||
|
- [x] **APK-Cache-Cleanup robuster**: durchsucht jetzt CachesDirectoryPath + DocumentDirectoryPath + ExternalCachesDirectoryPath + ExternalDirectoryPath statt nur Caches. Plus manueller Button "Update-Cache leeren" in Settings → Speicher mit Live-Anzeige der aktuellen Groesse
|
||||||
- [x] Diagnostic-Chat: bubblige Formatierung, mehrzeiliges Eingabefeld (textarea, Enter sendet, Shift+Enter neue Zeile)
|
- [x] Diagnostic-Chat: bubblige Formatierung, mehrzeiliges Eingabefeld (textarea, Enter sendet, Shift+Enter neue Zeile)
|
||||||
- [x] Adaptive VAD-Schwelle: Baseline aus den ersten 500ms Mic-Pegel, Stille = baseline+6dB / Sprache = baseline+12dB
|
- [x] Adaptive VAD-Schwelle: Baseline aus den ersten 500ms Mic-Pegel, Stille = baseline+6dB / Sprache = baseline+12dB
|
||||||
- [x] Max-Aufnahmedauer konfigurierbar in Settings (1-30 min, Default 5 min) — laengere Diktate moeglich
|
- [x] Max-Aufnahmedauer konfigurierbar in Settings (1-30 min, Default 5 min) — laengere Diktate moeglich
|
||||||
|
|||||||
Reference in New Issue
Block a user