Skip to content

Commit 0cf831c

Browse files
committed
Add asset extraction invalidation by version keys
Introduces invalidateKey support to asset extraction utilities, allowing unpacked assets to be invalidated based on Python or app version. Updates Android plugin and Dart code to provide app and Python version info for more robust cache invalidation when extracting Python bundles and site-packages.
1 parent 2a95061 commit 0cf831c

File tree

3 files changed

+102
-17
lines changed

3 files changed

+102
-17
lines changed

src/serious_python_android/android/src/main/java/com/flet/serious_python_android/AndroidPlugin.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBi
6060
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
6161
if (call.method.equals("getPlatformVersion")) {
6262
result.success("Android " + android.os.Build.VERSION.RELEASE);
63+
} else if (call.method.equals("getAppVersion")) {
64+
try {
65+
String packageName = context.getPackageName();
66+
android.content.pm.PackageManager pm = context.getPackageManager();
67+
android.content.pm.PackageInfo info = pm.getPackageInfo(packageName, 0);
68+
String versionName = info.versionName;
69+
long versionCode;
70+
if (android.os.Build.VERSION.SDK_INT >= 28) {
71+
versionCode = info.getLongVersionCode();
72+
} else {
73+
versionCode = info.versionCode;
74+
}
75+
result.success(versionName + "+" + versionCode);
76+
} catch (Exception e) {
77+
result.error("Error", e.getMessage(), null);
78+
}
6379
} else if (call.method.equals("getNativeLibraryDir")) {
6480
ContextWrapper contextWrapper = new ContextWrapper(context);
6581
String nativeLibraryDir = contextWrapper.getApplicationInfo().nativeLibraryDir;

src/serious_python_android/lib/serious_python_android.dart

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:io';
44
import 'package:flutter/foundation.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter/services.dart';
7+
import 'package:ffi/ffi.dart';
78
import 'package:path/path.dart' as p;
89
import 'package:serious_python_platform_interface/serious_python_platform_interface.dart';
910

@@ -47,6 +48,28 @@ class SeriousPythonAndroid extends SeriousPythonPlatform {
4748
spDebug("Unable to load libpyjni.so library: $e");
4849
}
4950

51+
const pythonSharedLib = "libpython3.12.so";
52+
53+
String? getPythonFullVersion() {
54+
try {
55+
final cpython = getCPython(pythonSharedLib);
56+
final versionPtr = cpython.Py_GetVersion();
57+
return versionPtr.cast<Utf8>().toDartString();
58+
} catch (e) {
59+
spDebug("Unable to read Python version for invalidation: $e");
60+
return null;
61+
}
62+
}
63+
64+
Future<String?> getAppVersion() async {
65+
try {
66+
return await methodChannel.invokeMethod<String>('getAppVersion');
67+
} catch (e) {
68+
spDebug("Unable to get app version for invalidation: $e");
69+
return null;
70+
}
71+
}
72+
5073
// unpack python bundle
5174
final nativeLibraryDir =
5275
await methodChannel.invokeMethod<String>('getNativeLibraryDir');
@@ -58,8 +81,11 @@ class SeriousPythonAndroid extends SeriousPythonPlatform {
5881
if (!await File(bundlePath).exists()) {
5982
throw Exception("Python bundle not found: $bundlePath");
6083
}
61-
var pythonLibPath =
62-
await extractFileZip(bundlePath, targetPath: "python_bundle");
84+
final pythonVersion = getPythonFullVersion();
85+
final pythonInvalidateKey =
86+
pythonVersion != null ? "python:$pythonVersion" : "python:$pythonSharedLib";
87+
var pythonLibPath = await extractFileZip(bundlePath,
88+
targetPath: "python_bundle", invalidateKey: pythonInvalidateKey);
6389
spDebug("pythonLibPath: $pythonLibPath");
6490

6591
var programDirPath = p.dirname(appPath);
@@ -72,8 +98,12 @@ class SeriousPythonAndroid extends SeriousPythonPlatform {
7298
];
7399

74100
if (await File(sitePackagesZipPath).exists()) {
101+
final appVersion = await getAppVersion();
102+
final sitePackagesInvalidateKey =
103+
appVersion != null ? "app:$appVersion" : null;
75104
var sitePackagesPath = await extractFileZip(sitePackagesZipPath,
76-
targetPath: "python_site_packages");
105+
targetPath: "python_site_packages",
106+
invalidateKey: sitePackagesInvalidateKey);
77107
spDebug("sitePackagesPath: $sitePackagesPath");
78108
moduleSearchPaths.add(sitePackagesPath);
79109
}
@@ -94,6 +124,6 @@ class SeriousPythonAndroid extends SeriousPythonPlatform {
94124
}
95125

96126
return runPythonProgramFFI(
97-
sync ?? false, "libpython3.12.so", appPath, script ?? "");
127+
sync ?? false, pythonSharedLib, appPath, script ?? "");
98128
}
99129
}

src/serious_python_platform_interface/lib/src/utils.dart

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ import 'package:path/path.dart' as p;
88
import 'package:path_provider/path_provider.dart';
99

1010
Future<String> extractAssetOrFile(String path,
11-
{bool isAsset = true, String? targetPath, bool checkHash = false}) async {
11+
{bool isAsset = true,
12+
String? targetPath,
13+
bool checkHash = false,
14+
String? invalidateKey}) async {
1215
WidgetsFlutterBinding.ensureInitialized();
1316
final supportDir = await getApplicationSupportDirectory();
1417
final destDir =
1518
Directory(p.join(supportDir.path, "flet", targetPath ?? p.dirname(path)));
1619

20+
var invalidateFile = File(p.join(destDir.path, ".invalidate"));
21+
String existingInvalidateKey = "";
22+
1723
String assetHash = "";
1824
// read asset hash from asset
1925
try {
@@ -30,18 +36,36 @@ Future<String> extractAssetOrFile(String path,
3036
// always re-create in debug mode
3137
await destDir.delete(recursive: true);
3238
} else {
33-
if (checkHash) {
34-
if (await hashFile.exists()) {
35-
destHash = (await hashFile.readAsString()).trim();
39+
var shouldDelete = false;
40+
41+
if (invalidateKey != null) {
42+
if (await invalidateFile.exists()) {
43+
existingInvalidateKey =
44+
(await invalidateFile.readAsString()).trim();
45+
}
46+
if (existingInvalidateKey != invalidateKey) {
47+
shouldDelete = true;
48+
}
49+
}
50+
51+
if (!shouldDelete) {
52+
if (checkHash) {
53+
if (await hashFile.exists()) {
54+
destHash = (await hashFile.readAsString()).trim();
55+
}
56+
}
57+
58+
if (assetHash != destHash ||
59+
(checkHash && assetHash == "" && destHash == "")) {
60+
shouldDelete = true;
61+
} else {
62+
debugPrint("Application archive already unpacked to ${destDir.path}");
63+
return destDir.path;
3664
}
3765
}
3866

39-
if (assetHash != destHash ||
40-
(checkHash && assetHash == "" && destHash == "")) {
67+
if (shouldDelete) {
4168
await destDir.delete(recursive: true);
42-
} else {
43-
debugPrint("Application archive already unpacked to ${destDir.path}");
44-
return destDir.path;
4569
}
4670
}
4771
}
@@ -77,19 +101,34 @@ Future<String> extractAssetOrFile(String path,
77101
await hashFile.writeAsString(assetHash);
78102
}
79103

104+
if (invalidateKey != null) {
105+
debugPrint(
106+
"Writing invalidate file: ${invalidateFile.path}, key: $invalidateKey");
107+
await invalidateFile.writeAsString(invalidateKey);
108+
}
109+
80110
return destDir.path;
81111
}
82112

83113
Future<String> extractAssetZip(String assetPath,
84-
{String? targetPath, bool checkHash = false}) async {
114+
{String? targetPath,
115+
bool checkHash = false,
116+
String? invalidateKey}) async {
85117
return extractAssetOrFile(assetPath,
86-
targetPath: targetPath, checkHash: checkHash);
118+
targetPath: targetPath,
119+
checkHash: checkHash,
120+
invalidateKey: invalidateKey);
87121
}
88122

89123
Future<String> extractFileZip(String filePath,
90-
{String? targetPath, bool checkHash = false}) async {
124+
{String? targetPath,
125+
bool checkHash = false,
126+
String? invalidateKey}) async {
91127
return extractAssetOrFile(filePath,
92-
isAsset: false, targetPath: targetPath, checkHash: checkHash);
128+
isAsset: false,
129+
targetPath: targetPath,
130+
checkHash: checkHash,
131+
invalidateKey: invalidateKey);
93132
}
94133

95134
Future<String> extractAsset(String assetPath) async {

0 commit comments

Comments
 (0)