diff --git a/Aerofoil/GpFileSystem_Win32.cpp b/Aerofoil/GpFileSystem_Win32.cpp index 2533ec6..2b4915e 100644 --- a/Aerofoil/GpFileSystem_Win32.cpp +++ b/Aerofoil/GpFileSystem_Win32.cpp @@ -327,6 +327,10 @@ void GpFileSystem_Win32::SetMainThreadRelay(IGpThreadRelay *relay) (void)relay; } +void GpFileSystem_Win32::SetDelayCallback(GpFileSystem_Win32::DelayCallback_t delayCallback) +{ +} + bool GpFileSystem_Win32::ValidateFilePath(const char *str, size_t length) const { for (size_t i = 0; i < length; i++) diff --git a/Aerofoil/GpFileSystem_Win32.h b/Aerofoil/GpFileSystem_Win32.h index 903a434..7a8cf25 100644 --- a/Aerofoil/GpFileSystem_Win32.h +++ b/Aerofoil/GpFileSystem_Win32.h @@ -24,6 +24,7 @@ public: bool IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const override; void SetMainThreadRelay(IGpThreadRelay *relay) override; + void SetDelayCallback(DelayCallback_t delayCallback) override; const wchar_t *GetBasePath() const; diff --git a/AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp b/AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp index ee60a89..b52c1ab 100644 --- a/AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp +++ b/AerofoilAndroid/app/jni/main/GpFileSystem_Android.cpp @@ -2,6 +2,8 @@ #include "GpFileSystem_Android.h" #include "GpIOStream.h" #include "HostDirectoryCursor.h" +#include "HostSystemServices.h" +#include "HostMutex.h" #include "IGpThreadRelay.h" #include "VirtualDirectory.h" @@ -17,7 +19,40 @@ #include #include "UTF8.h" +JNIEXPORT void JNICALL nativePostSourceExportRequest(JNIEnv *env, jclass cls, jboolean cancelled, jint fd, jobject pfd); +static JNINativeMethod GpFileSystemAPI_tab[] = +{ + { "nativePostSourceExportRequest", "(ZILjava/lang/Object;)V", reinterpret_cast(nativePostSourceExportRequest) }, +}; + +class GpFileStream_PFD final : public GpIOStream +{ +public: + GpFileStream_PFD(GpFileSystem_Android *fs, int fd, jobject pfd, bool readOnly, bool writeOnly); + ~GpFileStream_PFD(); + + size_t Read(void *bytesOut, size_t size) override; + size_t Write(const void *bytes, size_t size) override; + bool IsSeekable() const override; + bool IsReadOnly() const override; + bool IsWriteOnly() const override; + bool SeekStart(GpUFilePos_t loc) override; + bool SeekCurrent(GpFilePos_t loc) override; + bool SeekEnd(GpUFilePos_t loc) override; + bool Truncate(GpUFilePos_t loc) override; + GpUFilePos_t Size() const override; + GpUFilePos_t Tell() const override; + void Close() override; + void Flush() override; + +private: + GpFileSystem_Android *m_fs; + int m_fd; + jobject m_pfd; + bool m_readOnly; + bool m_writeOnly; +}; class GpFileStream_SDLRWops final : public GpIOStream { @@ -45,6 +80,121 @@ private: bool m_isWriteOnly; }; +class GpFileStream_Android_File final : public GpIOStream +{ +public: + GpFileStream_Android_File(FILE *f, int fd, bool readOnly, bool writeOnly); + ~GpFileStream_Android_File(); + + size_t Read(void *bytesOut, size_t size) override; + size_t Write(const void *bytes, size_t size) override; + bool IsSeekable() const override; + bool IsReadOnly() const override; + bool IsWriteOnly() const override; + bool SeekStart(GpUFilePos_t loc) override; + bool SeekCurrent(GpFilePos_t loc) override; + bool SeekEnd(GpUFilePos_t loc) override; + bool Truncate(GpUFilePos_t loc) override; + GpUFilePos_t Size() const override; + GpUFilePos_t Tell() const override; + void Close() override; + void Flush() override; + +private: + FILE *m_f; + int m_fd; + bool m_seekable; + bool m_isReadOnly; + bool m_isWriteOnly; +}; + +GpFileStream_PFD::GpFileStream_PFD(GpFileSystem_Android *fs, int fd, jobject pfd, bool readOnly, bool writeOnly) + : m_fs(fs) + , m_fd(fd) + , m_readOnly(readOnly) + , m_writeOnly(writeOnly) + , m_pfd(pfd) +{ +} + +GpFileStream_PFD::~GpFileStream_PFD() +{ + m_fs->ClosePFD(m_pfd); +} + +size_t GpFileStream_PFD::Read(void *bytesOut, size_t size) +{ + if (m_writeOnly) + return 0; + return read(m_fd, bytesOut, size); +} + +size_t GpFileStream_PFD::Write(const void *bytes, size_t size) +{ + if (m_readOnly) + return 0; + return write(m_fd, bytes, size); +} + +bool GpFileStream_PFD::IsSeekable() const +{ + return true; +} + +bool GpFileStream_PFD::IsReadOnly() const +{ + return m_readOnly; +} + +bool GpFileStream_PFD::IsWriteOnly() const +{ + return m_writeOnly; +} + +bool GpFileStream_PFD::SeekStart(GpUFilePos_t loc) +{ + return lseek64(m_fd, loc, SEEK_SET) >= 0; +} + +bool GpFileStream_PFD::SeekCurrent(GpFilePos_t loc) +{ + return lseek64(m_fd, loc, SEEK_CUR) >= 0; +} + +bool GpFileStream_PFD::SeekEnd(GpUFilePos_t loc) +{ + return lseek64(m_fd, loc, SEEK_END) >= 0; +} + +bool GpFileStream_PFD::Truncate(GpUFilePos_t loc) +{ + return ftruncate64(m_fd, static_cast(loc)) >= 0; +} + +GpUFilePos_t GpFileStream_PFD::Size() const +{ + struct stat64 s; + if (fstat64(m_fd, &s) < 0) + return 0; + + return static_cast(s.st_size); +} + +GpUFilePos_t GpFileStream_PFD::Tell() const +{ + return lseek64(m_fd, 0, SEEK_CUR); +} + +void GpFileStream_PFD::Close() +{ + this->~GpFileStream_PFD(); + free(this); +} + +void GpFileStream_PFD::Flush() +{ +} + GpFileStream_SDLRWops::GpFileStream_SDLRWops(SDL_RWops *f, bool readOnly, bool writeOnly) : m_rw(f) @@ -124,34 +274,6 @@ void GpFileStream_SDLRWops::Flush() { } -class GpFileStream_Android_File final : public GpIOStream -{ -public: - GpFileStream_Android_File(FILE *f, int fd, bool readOnly, bool writeOnly); - ~GpFileStream_Android_File(); - - size_t Read(void *bytesOut, size_t size) override; - size_t Write(const void *bytes, size_t size) override; - bool IsSeekable() const override; - bool IsReadOnly() const override; - bool IsWriteOnly() const override; - bool SeekStart(GpUFilePos_t loc) override; - bool SeekCurrent(GpFilePos_t loc) override; - bool SeekEnd(GpUFilePos_t loc) override; - bool Truncate(GpUFilePos_t loc) override; - GpUFilePos_t Size() const override; - GpUFilePos_t Tell() const override; - void Close() override; - void Flush() override; - -private: - FILE *m_f; - int m_fd; - bool m_seekable; - bool m_isReadOnly; - bool m_isWriteOnly; -}; - GpFileStream_Android_File::GpFileStream_Android_File(FILE *f, int fd, bool readOnly, bool writeOnly) : m_f(f) , m_fd(fd) @@ -255,29 +377,37 @@ void GpFileStream_Android_File::Flush() fflush(m_f); } -bool GpFileSystem_Android::ResolvePathInDownloadsDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset) +bool GpFileSystem_Android::OpenSourceExportFD(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *const *paths, size_t numPaths, int &fd, jobject &pfd) { + if (!m_sourceExportMutex) + m_sourceExportMutex = PortabilityLayer::HostSystemServices::GetInstance()->CreateMutex(); + + m_sourceExportWaiting = true; + m_sourceExportCancelled = false; + JNIEnv *jni = static_cast(SDL_AndroidGetJNIEnv()); jstring fname = jni->NewStringUTF(paths[0]); - jobject resultName = jni->CallObjectMethod(m_activity, this->m_selectSourceExportPathMID, fname); - int n = 0; - jstring resultPath = static_cast(resultName); + jni->CallVoidMethod(m_activity, this->m_selectSourceExportPathMID, fname); jni->DeleteLocalRef(fname); - const char *pathStrChars = jni->GetStringUTFChars(resultPath, nullptr); - resolution = std::string(pathStrChars, static_cast(jni->GetStringUTFLength(resultPath))); + for (;;) + { + m_sourceExportMutex->Lock(); + const bool isWaiting = m_sourceExportWaiting; + m_sourceExportMutex->Unlock(); - jni->ReleaseStringUTFChars(resultPath, pathStrChars); - jni->DeleteLocalRef(resultPath); + if (!isWaiting) + break; - resolution = SDL_AndroidGetExternalStoragePath(); - resolution += "/SourceCode.zip"; + m_delayCallback(5); + } - isAsset = false; + fd = m_sourceExportFD; + pfd = m_sourceExportPFD; - return true; + return !m_sourceExportCancelled; } bool GpFileSystem_Android::ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset) @@ -311,8 +441,6 @@ bool GpFileSystem_Android::ResolvePath(PortabilityLayer::VirtualDirectory_t virt case PortabilityLayer::VirtualDirectories::kPrefs: prefsAppend = "Prefs"; break; - case PortabilityLayer::VirtualDirectories::kSourceExport: - return ResolvePathInDownloadsDirectory(virtualDirectory, paths, numPaths, resolution, isAsset); default: return false; }; @@ -337,6 +465,12 @@ bool GpFileSystem_Android::ResolvePath(PortabilityLayer::VirtualDirectory_t virt GpFileSystem_Android::GpFileSystem_Android() : m_activity(nullptr) , m_relay(nullptr) + , m_delayCallback(nullptr) + , m_sourceExportMutex(nullptr) + , m_sourceExportFD(0) + , m_sourceExportWaiting(false) + , m_sourceExportCancelled(false) + , m_sourceExportPFD(nullptr) { } @@ -348,11 +482,16 @@ void GpFileSystem_Android::InitJNI() { JNIEnv *jni = static_cast(SDL_AndroidGetJNIEnv()); + jclass fileSystemAPIClass = jni->FindClass("org/thecodedeposit/aerofoil/GpFileSystemAPI"); + int registerStatus = jni->RegisterNatives(fileSystemAPIClass, GpFileSystemAPI_tab, sizeof(GpFileSystemAPI_tab) / sizeof(GpFileSystemAPI_tab[0])); + jni->DeleteLocalRef(fileSystemAPIClass); + jobject activityLR = static_cast(SDL_AndroidGetActivity()); jclass activityClassLR = static_cast(jni->GetObjectClass(activityLR)); m_scanAssetDirectoryMID = jni->GetMethodID(activityClassLR, "scanAssetDirectory", "(Ljava/lang/String;)[Ljava/lang/String;"); - m_selectSourceExportPathMID = jni->GetMethodID(activityClassLR, "selectSourceExportPath", "(Ljava/lang/String;)Ljava/lang/String;"); + m_selectSourceExportPathMID = jni->GetMethodID(activityClassLR, "selectSourceExportPath", "(Ljava/lang/String;)V"); + m_closeSourceExportPFDMID = jni->GetMethodID(activityClassLR, "closeSourceExportPFD", "(Ljava/lang/Object;)V"); m_activity = jni->NewGlobalRef(activityLR); @@ -459,6 +598,24 @@ GpIOStream *GpFileSystem_Android::OpenFileNested(PortabilityLayer::VirtualDirect return nullptr; }; + if (virtualDirectory == PortabilityLayer::VirtualDirectories::kSourceExport) + { + void *objStorage = malloc(sizeof(GpFileStream_PFD)); + if (!objStorage) + return nullptr; + + int fd = 0; + jobject pfd = nullptr; + const bool resolved = OpenSourceExportFD(virtualDirectory, subPaths, numSubPaths, fd, pfd); + if (!resolved) + { + free(objStorage); + return nullptr; + } + + return new (objStorage) GpFileStream_PFD(this, fd, pfd, false, true); + } + std::string resolvedPath; bool isAsset; if (!ResolvePath(virtualDirectory, subPaths, numSubPaths, resolvedPath, isAsset)) @@ -619,6 +776,31 @@ void GpFileSystem_Android::SetMainThreadRelay(IGpThreadRelay *relay) m_relay = relay; } +void GpFileSystem_Android::SetDelayCallback(DelayCallback_t delayCallback) +{ + m_delayCallback = delayCallback; +} + +void GpFileSystem_Android::PostSourceExportRequest(bool cancelled, int fd, jobject pfd) +{ + JNIEnv *jni = static_cast(SDL_AndroidGetJNIEnv()); + jobject globalRef = jni->NewGlobalRef(pfd); + + m_sourceExportMutex->Lock(); + m_sourceExportWaiting = false; + m_sourceExportCancelled = cancelled; + m_sourceExportFD = fd; + m_sourceExportPFD = globalRef; + m_sourceExportMutex->Unlock(); +} + +void GpFileSystem_Android::ClosePFD(jobject pfd) +{ + JNIEnv *jni = static_cast(SDL_AndroidGetJNIEnv()); + jni->CallVoidMethod(m_activity, m_closeSourceExportPFDMID, pfd); + jni->DeleteGlobalRef(pfd); +} + GpFileSystem_Android *GpFileSystem_Android::GetInstance() { return &ms_instance; @@ -749,4 +931,9 @@ PortabilityLayer::HostDirectoryCursor *GpFileSystem_Android::ScanStorageDirector return new GpDirectoryCursor_POSIX(d); } +JNIEXPORT void JNICALL nativePostSourceExportRequest(JNIEnv *env, jclass cls, jboolean cancelled, jint fd, jobject pfd) +{ + GpFileSystem_Android::GetInstance()->PostSourceExportRequest(cancelled != JNI_FALSE, fd, pfd); +} + GpFileSystem_Android GpFileSystem_Android::ms_instance; diff --git a/AerofoilAndroid/app/jni/main/GpFileSystem_Android.h b/AerofoilAndroid/app/jni/main/GpFileSystem_Android.h index 0381fdd..095feac 100644 --- a/AerofoilAndroid/app/jni/main/GpFileSystem_Android.h +++ b/AerofoilAndroid/app/jni/main/GpFileSystem_Android.h @@ -7,6 +7,11 @@ #include #include +namespace PortabilityLayer +{ + class HostMutex; +} + class GpFileSystem_Android final : public PortabilityLayer::HostFileSystem { public: @@ -28,6 +33,10 @@ public: bool IsVirtualDirectoryLooseResources(PortabilityLayer::VirtualDirectory_t virtualDir) const override; void SetMainThreadRelay(IGpThreadRelay *relay) override; + void SetDelayCallback(DelayCallback_t delayCallback) override; + + void PostSourceExportRequest(bool cancelled, int fd, jobject pfd); + void ClosePFD(jobject pfd); static GpFileSystem_Android *GetInstance(); @@ -48,14 +57,22 @@ private: PortabilityLayer::HostDirectoryCursor *ScanAssetDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths); PortabilityLayer::HostDirectoryCursor *ScanStorageDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths); - bool ResolvePathInDownloadsDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset); + bool OpenSourceExportFD(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, int &fd, jobject &pfd); bool ResolvePath(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* paths, size_t numPaths, std::string &resolution, bool &isAsset); IGpThreadRelay *m_relay; + DelayCallback_t m_delayCallback; jobject m_activity; jmethodID m_scanAssetDirectoryMID; jmethodID m_selectSourceExportPathMID; + jmethodID m_closeSourceExportPFDMID; + + PortabilityLayer::HostMutex *m_sourceExportMutex; + int m_sourceExportFD; + bool m_sourceExportWaiting; + bool m_sourceExportCancelled; + jobject m_sourceExportPFD; static GpFileSystem_Android ms_instance; }; diff --git a/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpActivity.java b/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpActivity.java index 271367a..38800a7 100644 --- a/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpActivity.java +++ b/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpActivity.java @@ -4,17 +4,23 @@ import org.libsdl.app.SDLActivity; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; import android.os.Bundle; import android.os.Environment; +import android.os.ParcelFileDescriptor; import android.provider.MediaStore; - +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; public class GpActivity extends SDLActivity { + private static final int SOURCE_EXPORT_REQUEST_ID = 20; + private AssetManager assetManager; @Override @@ -40,10 +46,36 @@ public class GpActivity extends SDLActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - if (requestCode == 1111 && resultCode == RESULT_OK && intent != null) + if (requestCode == SOURCE_EXPORT_REQUEST_ID) { - Uri uri = intent.getData(); - int n = 0; + if (resultCode == RESULT_OK) + { + Uri uri = intent.getData(); + Context context = getContext(); + ContentResolver contentResolver = context.getContentResolver(); + try + { + ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, "w"); + GpFileSystemAPI.nativePostSourceExportRequest(false, fd.getFd(), fd); + } + catch (FileNotFoundException e) + { + GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null); + return; + } + catch (IOException e) + { + GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null); + return; + } + catch (Exception e) + { + GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null); + return; + } + } + else + GpFileSystemAPI.nativePostSourceExportRequest(true, 0, null); } else { @@ -51,13 +83,23 @@ public class GpActivity extends SDLActivity } } - public String selectSourceExportPath(String fname) + public void selectSourceExportPath(String fname) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) .setType("application/zip") .addCategory(Intent.CATEGORY_OPENABLE) - .putExtra(Intent.EXTRA_TITLE, "SourceCode.zip"); - startActivityForResult(intent, 1111); - return ""; + .putExtra(Intent.EXTRA_TITLE, fname); + startActivityForResult(intent, SOURCE_EXPORT_REQUEST_ID); + } + + public void closeSourceExportPFD(Object obj) + { + try + { + ((ParcelFileDescriptor) obj).close(); + } + catch (IOException e) + { + } } } diff --git a/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpFileSystemAPI.java b/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpFileSystemAPI.java new file mode 100644 index 0000000..97a21fa --- /dev/null +++ b/AerofoilAndroid/app/src/main/java/org/thecodedeposit/aerofoil/GpFileSystemAPI.java @@ -0,0 +1,6 @@ +package org.thecodedeposit.aerofoil; + +public class GpFileSystemAPI +{ + public static native void nativePostSourceExportRequest(boolean cancelled, int fd, Object pfd); +} diff --git a/PortabilityLayer/HostFileSystem.h b/PortabilityLayer/HostFileSystem.h index 5a6ff4c..2b8839a 100644 --- a/PortabilityLayer/HostFileSystem.h +++ b/PortabilityLayer/HostFileSystem.h @@ -15,6 +15,8 @@ namespace PortabilityLayer class HostFileSystem { public: + typedef void (*DelayCallback_t)(uint32_t ticks); + virtual bool FileExists(VirtualDirectory_t virtualDirectory, const char *path) = 0; virtual bool FileLocked(VirtualDirectory_t virtualDirectory, const char *path, bool *exists) = 0; virtual GpIOStream *OpenFileNested(PortabilityLayer::VirtualDirectory_t virtualDirectory, char const* const* subPaths, size_t numSubPaths, bool writeAccess, GpFileCreationDisposition_t createDisposition) = 0; @@ -33,6 +35,7 @@ namespace PortabilityLayer GpIOStream *OpenFile(VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition); virtual void SetMainThreadRelay(IGpThreadRelay *relay) = 0; + virtual void SetDelayCallback(DelayCallback_t delayCallback) = 0; private: static HostFileSystem *ms_instance; diff --git a/PortabilityLayer/PLCore.cpp b/PortabilityLayer/PLCore.cpp index 66d9c2a..9d92fcc 100644 --- a/PortabilityLayer/PLCore.cpp +++ b/PortabilityLayer/PLCore.cpp @@ -676,6 +676,7 @@ void PL_Init() PortabilityLayer::MenuManager::GetInstance()->Init(); PortabilityLayer::HostFileSystem::GetInstance()->SetMainThreadRelay(PLMainThreadRelay::GetInstance()); + PortabilityLayer::HostFileSystem::GetInstance()->SetDelayCallback(PLSysCalls::Sleep); } WindowPtr PL_GetPutInFrontWindowPtr()