Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/src/androidTest/assets/spreadsheet-test.ods
Binary file not shown.
81 changes: 75 additions & 6 deletions app/src/androidTest/java/at/tomtasche/reader/test/CoreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

Expand All @@ -24,13 +26,49 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class CoreTest {
private static Thread serverThread;
private File m_testFile;
private File m_passwordTestFile;
private File m_spreadsheetTestFile;

@Before
public void initializeCore() {
@BeforeClass
public static void startServer() throws InterruptedException {
Context appCtx = InstrumentationRegistry.getInstrumentation().getTargetContext();
CoreWrapper.initialize(appCtx);

// Create server cache directory
File serverCacheDir = new File(appCtx.getCacheDir(), "core/server");
if (!serverCacheDir.isDirectory()) {
serverCacheDir.mkdirs();
}
CoreWrapper.createServer(serverCacheDir.getAbsolutePath());

// Start server in background thread
serverThread = new Thread(() -> {
try {
CoreWrapper.listenServer(29665);
} catch (Exception e) {
e.printStackTrace();
}
});
serverThread.setDaemon(true);
serverThread.start();

// Give server time to start
Thread.sleep(1000);
}

@AfterClass
public static void stopServer() {
CoreWrapper.stopServer();
if (serverThread != null) {
serverThread.interrupt();
}
}

@Before
public void initializeCore() {
// Server is already initialized in @BeforeClass
}

@Before
Expand All @@ -47,6 +85,10 @@ public void extractTestFile() throws IOException {
try (InputStream inputStream = assetManager.open("password-test.odt")) {
copy(inputStream, m_passwordTestFile);
}
m_spreadsheetTestFile = new File(appCtx.getCacheDir(), "spreadsheet-test.ods");
try (InputStream inputStream = assetManager.open("spreadsheet-test.ods")) {
copy(inputStream, m_spreadsheetTestFile);
}
}

@After
Expand All @@ -57,6 +99,9 @@ public void cleanupTestFile() {
if (null != m_passwordTestFile) {
m_passwordTestFile.delete();
}
if (null != m_spreadsheetTestFile) {
m_spreadsheetTestFile.delete();
}
}

private static void copy(InputStream src, File dst) throws IOException {
Expand All @@ -81,7 +126,7 @@ public void test() {
coreOptions.editable = true;
coreOptions.cachePath = cachePath.getPath();

CoreWrapper.CoreResult coreResult = CoreWrapper.parse(coreOptions);
CoreWrapper.CoreResult coreResult = CoreWrapper.hostFile("test", coreOptions);
Assert.assertEquals(0, coreResult.errorCode);

File resultFile = new File(cacheDir, "result");
Expand All @@ -105,7 +150,7 @@ public void testPasswordProtectedDocumentWithoutPassword() {
coreOptions.editable = false;
coreOptions.cachePath = cachePath.getPath();

CoreWrapper.CoreResult coreResult = CoreWrapper.parse(coreOptions);
CoreWrapper.CoreResult coreResult = CoreWrapper.hostFile("password-test-no-pw", coreOptions);
Assert.assertEquals(-2, coreResult.errorCode);
}

Expand All @@ -122,7 +167,7 @@ public void testPasswordProtectedDocumentWithWrongPassword() {
coreOptions.editable = false;
coreOptions.cachePath = cachePath.getPath();

CoreWrapper.CoreResult coreResult = CoreWrapper.parse(coreOptions);
CoreWrapper.CoreResult coreResult = CoreWrapper.hostFile("password-test-wrong-pw", coreOptions);
Assert.assertEquals(-2, coreResult.errorCode);
}

Expand All @@ -139,7 +184,31 @@ public void testPasswordProtectedDocumentWithCorrectPassword() {
coreOptions.editable = false;
coreOptions.cachePath = cachePath.getPath();

CoreWrapper.CoreResult coreResult = CoreWrapper.parse(coreOptions);
CoreWrapper.CoreResult coreResult = CoreWrapper.hostFile("password-test-correct-pw", coreOptions);
Assert.assertEquals(0, coreResult.errorCode);
}

@Test
public void testSpreadsheetSheetNames() {
File cacheDir = InstrumentationRegistry.getInstrumentation().getTargetContext().getCacheDir();
File outputPath = new File(cacheDir, "spreadsheet_output");
File cachePath = new File(cacheDir, "spreadsheet_cache");

CoreWrapper.CoreOptions coreOptions = new CoreWrapper.CoreOptions();
coreOptions.inputPath = m_spreadsheetTestFile.getAbsolutePath();
coreOptions.outputPath = outputPath.getPath();
coreOptions.editable = false;
coreOptions.cachePath = cachePath.getPath();

CoreWrapper.CoreResult coreResult = CoreWrapper.hostFile("spreadsheet-test", coreOptions);
Assert.assertEquals("CoreWrapper should successfully parse the ODS file", 0, coreResult.errorCode);

// Verify we have exactly 3 sheets
Assert.assertEquals("ODS file should contain 3 sheets", 3, coreResult.pageNames.size());

// Verify sheet names match the actual sheet names from the ODS file
Assert.assertEquals("First sheet should be named 'hey'", "hey", coreResult.pageNames.get(0));
Assert.assertEquals("Second sheet should be named 'ho'", "ho", coreResult.pageNames.get(1));
Assert.assertEquals("Third sheet should be named 'Sheet3'", "Sheet3", coreResult.pageNames.get(2));
}
}
143 changes: 0 additions & 143 deletions app/src/main/cpp/core_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,149 +111,6 @@ Java_at_tomtasche_reader_background_CoreWrapper_mimetypeNative(JNIEnv *env, jcla
return mimetype;
}

JNIEXPORT jobject JNICALL
Java_at_tomtasche_reader_background_CoreWrapper_parseNative(JNIEnv *env, jclass clazz,
jobject options) {
std::error_code ec;
auto logger = std::make_shared<AndroidLogger>();

jclass resultClass = env->FindClass("at/tomtasche/reader/background/CoreWrapper$CoreResult");
jmethodID resultConstructor = env->GetMethodID(resultClass, "<init>", "()V");
jobject result = env->NewObject(resultClass, resultConstructor);

jfieldID errorField = env->GetFieldID(resultClass, "errorCode", "I");

jclass optionsClass = env->GetObjectClass(options);
std::string inputPathCpp = getStringField(env, optionsClass, options, "inputPath");

try {
std::optional<std::string> passwordCpp;
jfieldID passwordField = env->GetFieldID(optionsClass, "password", "Ljava/lang/String;");
auto password = (jstring) env->GetObjectField(options, passwordField);
if (password != nullptr) {
passwordCpp = convertString(env, password);
}

jfieldID editableField = env->GetFieldID(optionsClass, "editable", "Z");
jboolean editable = env->GetBooleanField(options, editableField);

std::string outputPathCpp = getStringField(env, optionsClass, options, "outputPath");
std::string cachePathCpp = getStringField(env, optionsClass, options, "cachePath");

jclass listClass = env->FindClass("java/util/List");
jmethodID addMethod = env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z");

jfieldID pageNamesField = env->GetFieldID(resultClass, "pageNames", "Ljava/util/List;");
auto pageNames = (jobject) env->GetObjectField(result, pageNamesField);

jfieldID pagePathsField = env->GetFieldID(resultClass, "pagePaths", "Ljava/util/List;");
auto pagePaths = (jobject) env->GetObjectField(result, pagePathsField);

jfieldID ooxmlField = env->GetFieldID(optionsClass, "ooxml", "Z");
jboolean ooxml = env->GetBooleanField(options, ooxmlField);

jfieldID txtField = env->GetFieldID(optionsClass, "txt", "Z");
jboolean txt = env->GetBooleanField(options, txtField);

jfieldID pdfField = env->GetFieldID(optionsClass, "pdf", "Z");
jboolean pdf = env->GetBooleanField(options, pdfField);

jfieldID pagingField = env->GetFieldID(optionsClass, "paging", "Z");
jboolean paging = env->GetBooleanField(options, pagingField);

try {
odr::FileType fileType;
try {
const auto types = odr::list_file_types(inputPathCpp, *logger);
if (types.empty()) {
env->SetIntField(result, errorField, -5);
return result;
}

fileType = types.back();
} catch (odr::UnsupportedFileType &e) {
fileType = e.file_type;
}

std::string extensionCpp = odr::file_type_to_string(fileType);
jstring extension = env->NewStringUTF(extensionCpp.c_str());
jfieldID extensionField = env->GetFieldID(resultClass, "extension",
"Ljava/lang/String;");
env->SetObjectField(result, extensionField, extension);

__android_log_print(ANDROID_LOG_VERBOSE, "smn", "Open %s", inputPathCpp.c_str());

auto file = odr::open(inputPathCpp, *logger);

if (file.password_encrypted()) {
if (!passwordCpp.has_value()) {
env->SetIntField(result, errorField, -2);
return result;
}
try {
file = file.decrypt(passwordCpp.value());
} catch (...) {
env->SetIntField(result, errorField, -2);
return result;
}
}

// .doc-files are not real documents in core
if (file.is_document_file() && fileType != odr::FileType::legacy_word_document) {
// TODO this will cause a second load
s_document = file.as_document_file().document();
}

extensionCpp = odr::file_type_to_string(file.file_type());
extension = env->NewStringUTF(extensionCpp.c_str());
env->SetObjectField(result, extensionField, extension);

odr::HtmlConfig htmlConfig;
htmlConfig.editable = editable;
htmlConfig.text_document_margin = paging;

__android_log_print(ANDROID_LOG_VERBOSE, "smn", "Translate to HTML");

std::filesystem::remove_all(cachePathCpp, ec);
std::filesystem::create_directories(cachePathCpp);
odr::HtmlService service = odr::html::translate(file, cachePathCpp, htmlConfig, logger);
odr::Html html = service.bring_offline(outputPathCpp);
std::filesystem::remove_all(cachePathCpp);

for (const odr::HtmlPage &page: html.pages()) {
jstring pageName = env->NewStringUTF(page.name.c_str());
env->CallBooleanMethod(pageNames, addMethod, pageName);

jstring pagePath = env->NewStringUTF(page.path.c_str());
env->CallBooleanMethod(pagePaths, addMethod, pagePath);
}
} catch (const odr::UnknownFileType &e) {
__android_log_print(ANDROID_LOG_ERROR, "smn", "Unknown file type: %s", e.what());
env->SetIntField(result, errorField, -5);
return result;
} catch (const odr::UnsupportedFileType &e) {
__android_log_print(ANDROID_LOG_ERROR, "smn", "Unsupported file type: %s", e.what());
env->SetIntField(result, errorField, -5);
return result;
} catch (const std::exception &e) {
__android_log_print(ANDROID_LOG_ERROR, "smn", "Unhandled C++ exception: %s", e.what());
env->SetIntField(result, errorField, -4);
return result;
} catch (...) {
__android_log_print(ANDROID_LOG_ERROR, "smn",
"Unhandled C++ exception without further information");
env->SetIntField(result, errorField, -4);
return result;
}
} catch (...) {
env->SetIntField(result, errorField, -3);
return result;
}

env->SetIntField(result, errorField, 0);
return result;
}

JNIEXPORT jobject JNICALL
Java_at_tomtasche_reader_background_CoreWrapper_backtranslateNative(JNIEnv *env, jclass clazz,
jobject options,
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/cpp/core_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ JNIEXPORT jstring JNICALL
Java_at_tomtasche_reader_background_CoreWrapper_mimetypeNative(JNIEnv *env, jclass clazz,
jstring path);

JNIEXPORT jobject JNICALL
Java_at_tomtasche_reader_background_CoreWrapper_parseNative(JNIEnv *env, jclass clazz,
jobject options);

JNIEXPORT jobject JNICALL
Java_at_tomtasche_reader_background_CoreWrapper_backtranslateNative(JNIEnv *env, jclass clazz,
jobject options,
Expand Down
Loading
Loading