mirror of
https://github.com/ruffle-rs/ruffle-android.git
synced 2024-11-23 05:39:38 +00:00
Add super trivial smoke test
This commit is contained in:
parent
77a545319d
commit
e9ebf59a54
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -82,6 +82,9 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew deviceTests
|
||||
|
||||
- name: Decode keystore
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
env:
|
||||
|
@ -1,3 +1,5 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import com.github.willir.rust.CargoNdkBuildTask
|
||||
|
||||
plugins {
|
||||
@ -91,6 +93,18 @@ android {
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
testOptions {
|
||||
managedDevices {
|
||||
localDevices {
|
||||
create("pixel3api34") {
|
||||
device = "Pixel 3"
|
||||
apiLevel = 34
|
||||
systemImageSource = "aosp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -109,6 +123,9 @@ dependencies {
|
||||
implementation(libs.androidx.games.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.appcompat)
|
||||
androidTestImplementation(libs.androidx.uiautomator)
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
androidTestImplementation(libs.androidx.test.rules)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
@ -131,3 +148,9 @@ cargoNdk {
|
||||
apiLevel = 26
|
||||
buildType = "release"
|
||||
}
|
||||
|
||||
tasks {
|
||||
register("deviceTests") {
|
||||
dependsOn("pixel3api34DebugAndroidTest")
|
||||
}
|
||||
}
|
||||
|
12
app/src/androidTest/AndroidManifest.xml
Normal file
12
app/src/androidTest/AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="28" />
|
||||
|
||||
<instrumentation android:targetPackage="rs.ruffle"
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"/>
|
||||
|
||||
<application tools:replace="label" android:label="SmokeTest" />
|
||||
</manifest>
|
@ -1,22 +0,0 @@
|
||||
package rs.ruffle
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("rs.ruffle", appContext.packageName)
|
||||
}
|
||||
}
|
83
app/src/androidTest/java/rs/ruffle/SmokeTest.kt
Normal file
83
app/src/androidTest/java/rs/ruffle/SmokeTest.kt
Normal file
@ -0,0 +1,83 @@
|
||||
package rs.ruffle
|
||||
|
||||
import android.R
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.matcher.ViewMatchers.assertThat
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.Until
|
||||
import java.io.File
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.notNullValue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
private const val BASIC_SAMPLE_PACKAGE = "rs.ruffle"
|
||||
private const val LAUNCH_TIMEOUT = 5000L
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SmokeTest {
|
||||
private lateinit var device: UiDevice
|
||||
private lateinit var traceOutput: File
|
||||
private lateinit var swfFile: File
|
||||
|
||||
@Before
|
||||
fun startMainActivityFromHomeScreen() {
|
||||
// Initialize UiDevice instance
|
||||
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
|
||||
// Start from the home screen
|
||||
device.pressHome()
|
||||
|
||||
// Wait for launcher
|
||||
val launcherPackage: String = device.launcherPackageName
|
||||
assertThat(launcherPackage, notNullValue())
|
||||
device.wait(
|
||||
Until.hasObject(By.pkg(launcherPackage).depth(0)),
|
||||
LAUNCH_TIMEOUT
|
||||
)
|
||||
|
||||
// Launch the app
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
traceOutput = File.createTempFile("trace", ".txt", context.cacheDir)
|
||||
swfFile = File.createTempFile("movie", ".swf", context.cacheDir)
|
||||
val resources = InstrumentationRegistry.getInstrumentation().context.resources
|
||||
val inStream = resources.openRawResource(
|
||||
rs.ruffle.test.R.raw.helloflash
|
||||
)
|
||||
val bytes = inStream.readBytes()
|
||||
swfFile.writeBytes(bytes)
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(
|
||||
BASIC_SAMPLE_PACKAGE
|
||||
)?.apply {
|
||||
component = ComponentName("rs.ruffle", "rs.ruffle.PlayerActivity")
|
||||
data = Uri.fromFile(swfFile)
|
||||
putExtra("traceOutput", traceOutput.absolutePath)
|
||||
// Clear out any previous instances
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
// Wait for the app to appear
|
||||
device.wait(
|
||||
Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
|
||||
LAUNCH_TIMEOUT
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emulatorRunsASwf() {
|
||||
device.waitForWindowUpdate(null, 1000)
|
||||
assertThat(device, notNullValue())
|
||||
|
||||
val trace = traceOutput.readLines()
|
||||
assertThat(trace, equalTo(listOf("Hello from Flash!")))
|
||||
}
|
||||
}
|
BIN
app/src/androidTest/res/raw/helloflash.swf
Normal file
BIN
app/src/androidTest/res/raw/helloflash.swf
Normal file
Binary file not shown.
@ -55,6 +55,13 @@ class PlayerActivity : GameActivity() {
|
||||
return intent.dataString
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
// Used by Rust
|
||||
private val traceOutput: String?
|
||||
get() {
|
||||
return intent.getStringExtra("traceOutput")
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
// Used by Rust
|
||||
private fun navigateToUrl(url: String?) {
|
||||
|
@ -15,6 +15,9 @@ gamesActivity = "2.0.2" # Needs to be in sync with android-activity crate
|
||||
constraintlayout = "2.1.4"
|
||||
appcompat = "1.6.1"
|
||||
ktlint = "12.1.0"
|
||||
uiautomator = "2.3.0"
|
||||
androidXTestRunner = "1.5.2"
|
||||
androidXTestRules = "1.5.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@ -37,6 +40,9 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio
|
||||
androidx-games-activity = { group = "androidx.games", name = "games-activity", version.ref = "gamesActivity" }
|
||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
|
||||
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTestRunner" }
|
||||
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidXTestRules" }
|
||||
|
||||
[plugins]
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
|
22
src/java.rs
22
src/java.rs
@ -4,6 +4,7 @@ use jni::objects::{
|
||||
use jni::signature::{Primitive, ReturnType};
|
||||
use jni::JNIEnv;
|
||||
use ruffle_core::ContextMenuItem;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Handles to various items on the Java `PlayerActivity` class.
|
||||
@ -16,6 +17,7 @@ pub struct JavaInterface {
|
||||
show_context_menu: JMethodID,
|
||||
get_swf_bytes: JMethodID,
|
||||
get_swf_uri: JMethodID,
|
||||
get_trace_output: JMethodID,
|
||||
get_loc_on_screen: JMethodID,
|
||||
}
|
||||
|
||||
@ -109,6 +111,23 @@ impl JavaInterface {
|
||||
url
|
||||
}
|
||||
|
||||
pub fn get_trace_output(env: &mut JNIEnv, this: &JObject) -> Option<PathBuf> {
|
||||
let result = unsafe {
|
||||
env.call_method_unchecked(this, Self::get().get_trace_output, ReturnType::Object, &[])
|
||||
};
|
||||
let object = result
|
||||
.expect("getTraceOutput() must never throw")
|
||||
.l()
|
||||
.unwrap();
|
||||
if object.is_null() {
|
||||
return None;
|
||||
}
|
||||
let string_object = JString::from(object);
|
||||
let java_string = unsafe { env.get_string_unchecked(&string_object) };
|
||||
let url = java_string.unwrap().to_string_lossy().to_string();
|
||||
Some(url.into())
|
||||
}
|
||||
|
||||
pub fn get_loc_on_screen(env: &mut JNIEnv, this: &JObject) -> (i32, i32) {
|
||||
let result = unsafe {
|
||||
env.call_method_unchecked(this, Self::get().get_loc_on_screen, ReturnType::Array, &[])
|
||||
@ -150,6 +169,9 @@ impl JavaInterface {
|
||||
get_swf_uri: env
|
||||
.get_method_id(class, "getSwfUri", "()Ljava/lang/String;")
|
||||
.expect("getSwfUri must exist"),
|
||||
get_trace_output: env
|
||||
.get_method_id(class, "getTraceOutput", "()Ljava/lang/String;")
|
||||
.expect("getTraceOutput must exist"),
|
||||
get_loc_on_screen: env
|
||||
.get_method_id(class, "getLocOnScreen", "()[I")
|
||||
.expect("getLocOnScreen must exist"),
|
||||
|
11
src/lib.rs
11
src/lib.rs
@ -5,6 +5,7 @@ mod java;
|
||||
mod keycodes;
|
||||
mod navigator;
|
||||
mod task;
|
||||
mod trace;
|
||||
|
||||
use custom_event::RuffleEvent;
|
||||
|
||||
@ -41,6 +42,7 @@ use ruffle_core::{
|
||||
};
|
||||
|
||||
use crate::keycodes::android_keycode_to_ruffle;
|
||||
use crate::trace::FileLogBackend;
|
||||
use java::JavaInterface;
|
||||
use ruffle_render_wgpu::{backend::WgpuRenderBackend, target::SwapChainTarget};
|
||||
|
||||
@ -78,14 +80,14 @@ fn run(app: AndroidApp) {
|
||||
};
|
||||
|
||||
log::info!("Starting event loop...");
|
||||
let trace_output;
|
||||
|
||||
unsafe {
|
||||
let vm = JavaVM::from_raw(app.vm_as_ptr() as *mut sys::JavaVM).expect("JVM must exist");
|
||||
let activity = JObject::from_raw(app.activity_as_ptr() as jobject);
|
||||
let _ = vm
|
||||
.get_env()
|
||||
.unwrap()
|
||||
.set_rust_field(activity, "eventLoopHandle", sender.clone());
|
||||
let mut jni_env = vm.get_env().unwrap();
|
||||
trace_output = JavaInterface::get_trace_output(&mut jni_env, &activity);
|
||||
let _ = jni_env.set_rust_field(activity, "eventLoopHandle", sender.clone());
|
||||
}
|
||||
|
||||
while !quit {
|
||||
@ -222,6 +224,7 @@ fn run(app: AndroidApp) {
|
||||
.with_audio(AAudioAudioBackend::new().unwrap())
|
||||
.with_storage(MemoryStorageBackend::default())
|
||||
.with_navigator(navigator)
|
||||
.with_log(FileLogBackend::new(trace_output.as_deref()))
|
||||
.with_video(
|
||||
ruffle_video_software::backend::SoftwareVideoBackend::new(),
|
||||
)
|
||||
|
238
src/navigator.rs
238
src/navigator.rs
@ -2,12 +2,13 @@
|
||||
|
||||
use crate::custom_event::RuffleEvent;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ruffle_core::backend::navigator::{
|
||||
async_return, create_fetch_error, ErrorResponse, NavigationMethod, NavigatorBackend,
|
||||
OpenURLMode, OwnedFuture, Request, SuccessResponse,
|
||||
async_return, create_fetch_error, create_specific_fetch_error, ErrorResponse, NavigationMethod,
|
||||
NavigatorBackend, OpenURLMode, OwnedFuture, Request, SuccessResponse,
|
||||
};
|
||||
use ruffle_core::indexmap::IndexMap;
|
||||
use ruffle_core::loader::Error;
|
||||
@ -145,38 +146,52 @@ impl NavigatorBackend for ExternalNavigatorBackend {
|
||||
|
||||
fn fetch(&self, request: Request) -> OwnedFuture<Box<dyn SuccessResponse>, ErrorResponse> {
|
||||
// TODO: honor sandbox type (local-with-filesystem, local-with-network, remote, ...)
|
||||
let processed_url = match self.resolve_url(request.url()) {
|
||||
let mut processed_url = match self.resolve_url(request.url()) {
|
||||
Ok(url) => url,
|
||||
Err(e) => {
|
||||
return async_return(create_fetch_error(request.url(), e));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Handle file: and especially content: schemes
|
||||
// TODO: Handle content: schemes
|
||||
|
||||
struct NetworkResponse {
|
||||
enum AndroidResponseBody {
|
||||
File(File),
|
||||
Network(Arc<Mutex<Box<dyn Read + Send + Sync + 'static>>>),
|
||||
}
|
||||
|
||||
struct AndroidResponse {
|
||||
redirected: bool,
|
||||
status: u16,
|
||||
url: String,
|
||||
reader: Arc<Mutex<Box<dyn Read + Send + Sync + 'static>>>,
|
||||
response_body: AndroidResponseBody,
|
||||
length: Option<u64>,
|
||||
}
|
||||
|
||||
impl SuccessResponse for NetworkResponse {
|
||||
impl SuccessResponse for AndroidResponse {
|
||||
fn url(&self) -> Cow<str> {
|
||||
Cow::Borrowed(&self.url)
|
||||
}
|
||||
|
||||
fn body(self: Box<Self>) -> OwnedFuture<Vec<u8>, Error> {
|
||||
Box::pin(async move {
|
||||
let mut bytes: Vec<u8> =
|
||||
Vec::with_capacity(self.length.unwrap_or_default() as usize);
|
||||
self.reader
|
||||
.lock()
|
||||
.expect("working lock during fetch body read")
|
||||
.read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
})
|
||||
let length = self.length.unwrap_or_default() as usize;
|
||||
match self.response_body {
|
||||
AndroidResponseBody::File(mut file) => Box::pin(async move {
|
||||
let mut body = vec![];
|
||||
file.read_to_end(&mut body)
|
||||
.map_err(|e| Error::FetchError(e.to_string()))?;
|
||||
|
||||
Ok(body)
|
||||
}),
|
||||
AndroidResponseBody::Network(response) => Box::pin(async move {
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(length);
|
||||
response
|
||||
.lock()
|
||||
.expect("working lock during fetch body read")
|
||||
.read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self) -> u16 {
|
||||
@ -188,27 +203,46 @@ impl NavigatorBackend for ExternalNavigatorBackend {
|
||||
}
|
||||
|
||||
fn next_chunk(&mut self) -> OwnedFuture<Option<Vec<u8>>, Error> {
|
||||
let reader = self.reader.clone();
|
||||
Box::pin(async move {
|
||||
let mut buf = vec![0; 4096];
|
||||
let lock = reader.try_lock();
|
||||
if matches!(lock, Err(std::sync::TryLockError::WouldBlock)) {
|
||||
return Err(Error::FetchError(
|
||||
match &mut self.response_body {
|
||||
AndroidResponseBody::File(file) => {
|
||||
let mut buf = vec![0; 4096];
|
||||
let res = file.read(&mut buf);
|
||||
|
||||
Box::pin(async move {
|
||||
match res {
|
||||
Ok(count) if count > 0 => {
|
||||
buf.resize(count, 0);
|
||||
Ok(Some(buf))
|
||||
}
|
||||
Ok(_) => Ok(None),
|
||||
Err(e) => Err(Error::FetchError(e.to_string())),
|
||||
}
|
||||
})
|
||||
}
|
||||
AndroidResponseBody::Network(response) => {
|
||||
let reader = response.clone();
|
||||
Box::pin(async move {
|
||||
let mut buf = vec![0; 4096];
|
||||
let lock = reader.try_lock();
|
||||
if matches!(lock, Err(std::sync::TryLockError::WouldBlock)) {
|
||||
return Err(Error::FetchError(
|
||||
"Concurrent read operations on the same stream are not supported."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let result = lock.expect("network lock").read(&mut buf);
|
||||
}
|
||||
let result = lock.expect("network lock").read(&mut buf);
|
||||
|
||||
match result {
|
||||
Ok(count) if count > 0 => {
|
||||
buf.resize(count, 0);
|
||||
Ok(Some(buf))
|
||||
}
|
||||
Ok(_) => Ok(None),
|
||||
Err(e) => Err(Error::FetchError(e.to_string())),
|
||||
match result {
|
||||
Ok(count) if count > 0 => {
|
||||
buf.resize(count, 0);
|
||||
Ok(Some(buf))
|
||||
}
|
||||
Ok(_) => Ok(None),
|
||||
Err(e) => Err(Error::FetchError(e.to_string())),
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_length(&self) -> Result<Option<u64>, Error> {
|
||||
@ -216,60 +250,108 @@ impl NavigatorBackend for ExternalNavigatorBackend {
|
||||
}
|
||||
}
|
||||
|
||||
Box::pin(async move {
|
||||
let mut ureq_request = ureq::request_url(
|
||||
match request.method() {
|
||||
NavigationMethod::Get => "GET",
|
||||
NavigationMethod::Post => "POST",
|
||||
},
|
||||
&processed_url,
|
||||
);
|
||||
match processed_url.scheme() {
|
||||
"file" => Box::pin(async move {
|
||||
// We send the original url (including query parameters)
|
||||
// back to ruffle_core in the `Response`
|
||||
let response_url = processed_url.clone();
|
||||
// Flash supports query parameters with local urls.
|
||||
// SwfMovie takes care of exposing those to ActionScript -
|
||||
// when we actually load a filesystem url, strip them out.
|
||||
processed_url.set_query(None);
|
||||
|
||||
let (body_data, mime) = request.body().clone().unwrap_or_default();
|
||||
for (name, val) in request.headers().iter() {
|
||||
ureq_request = ureq_request.set(name, val);
|
||||
}
|
||||
ureq_request = ureq_request.set("Content-Type", &mime);
|
||||
let response = ureq_request.send_bytes(&body_data).map_err(|e| {
|
||||
log::warn!("Error fetching url: {e}");
|
||||
let inner = match e.kind() {
|
||||
ureq::ErrorKind::Dns => Error::InvalidDomain(processed_url.to_string()),
|
||||
_ => Error::FetchError(e.to_string()),
|
||||
let path = match processed_url.to_file_path() {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
return create_specific_fetch_error(
|
||||
"Unable to create path out of URL",
|
||||
response_url.as_str(),
|
||||
"",
|
||||
);
|
||||
}
|
||||
};
|
||||
ErrorResponse {
|
||||
url: processed_url.to_string(),
|
||||
error: inner,
|
||||
|
||||
let contents = std::fs::File::open(path);
|
||||
|
||||
let file = match contents {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
return create_specific_fetch_error(
|
||||
"Can't open file",
|
||||
response_url.as_str(),
|
||||
e,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let length = file.metadata().map(|m| m.len()).ok();
|
||||
let response: Box<dyn SuccessResponse> = Box::new(AndroidResponse {
|
||||
url: response_url.to_string(),
|
||||
response_body: AndroidResponseBody::File(file),
|
||||
status: 0,
|
||||
redirected: false,
|
||||
length,
|
||||
});
|
||||
|
||||
Ok(response)
|
||||
}),
|
||||
_ => Box::pin(async move {
|
||||
let mut ureq_request = ureq::request_url(
|
||||
match request.method() {
|
||||
NavigationMethod::Get => "GET",
|
||||
NavigationMethod::Post => "POST",
|
||||
},
|
||||
&processed_url,
|
||||
);
|
||||
|
||||
let (body_data, mime) = request.body().clone().unwrap_or_default();
|
||||
for (name, val) in request.headers().iter() {
|
||||
ureq_request = ureq_request.set(name, val);
|
||||
}
|
||||
})?;
|
||||
ureq_request = ureq_request.set("Content-Type", &mime);
|
||||
let response = ureq_request.send_bytes(&body_data).map_err(|e| {
|
||||
log::warn!("Error fetching url: {e}");
|
||||
let inner = match e.kind() {
|
||||
ureq::ErrorKind::Dns => Error::InvalidDomain(processed_url.to_string()),
|
||||
_ => Error::FetchError(e.to_string()),
|
||||
};
|
||||
ErrorResponse {
|
||||
url: processed_url.to_string(),
|
||||
error: inner,
|
||||
}
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
let redirected = response.get_url() != processed_url.as_str();
|
||||
let response_length = response
|
||||
.header("Content-Length")
|
||||
.and_then(|s| s.parse::<u64>().ok());
|
||||
let status = response.status();
|
||||
let redirected = response.get_url() != processed_url.as_str();
|
||||
let response_length = response
|
||||
.header("Content-Length")
|
||||
.and_then(|s| s.parse::<u64>().ok());
|
||||
|
||||
if !(200..300).contains(&status) {
|
||||
let error = Error::HttpNotOk(
|
||||
format!("HTTP status is not ok, got {}", response.status()),
|
||||
if !(200..300).contains(&status) {
|
||||
let error = Error::HttpNotOk(
|
||||
format!("HTTP status is not ok, got {}", response.status()),
|
||||
status,
|
||||
redirected,
|
||||
response_length.unwrap_or_default(),
|
||||
);
|
||||
return Err(ErrorResponse {
|
||||
url: response.get_url().to_string(),
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
let response: Box<dyn SuccessResponse> = Box::new(AndroidResponse {
|
||||
url: response.get_url().to_string(),
|
||||
response_body: AndroidResponseBody::Network(Arc::new(Mutex::new(
|
||||
response.into_reader(),
|
||||
))),
|
||||
status,
|
||||
redirected,
|
||||
response_length.unwrap_or_default(),
|
||||
);
|
||||
return Err(ErrorResponse {
|
||||
url: response.get_url().to_string(),
|
||||
error,
|
||||
length: response_length,
|
||||
});
|
||||
}
|
||||
|
||||
let response: Box<dyn SuccessResponse> = Box::new(NetworkResponse {
|
||||
url: response.get_url().to_string(),
|
||||
reader: Arc::new(Mutex::new(response.into_reader())),
|
||||
status,
|
||||
redirected,
|
||||
length: response_length,
|
||||
});
|
||||
Ok(response)
|
||||
})
|
||||
Ok(response)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_future(&mut self, future: OwnedFuture<(), Error>) {
|
||||
|
30
src/trace.rs
Normal file
30
src/trace.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use ruffle_core::backend::log::LogBackend;
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use std::io::{LineWriter, Write};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct FileLogBackend {
|
||||
writer: Option<RefCell<LineWriter<File>>>,
|
||||
}
|
||||
|
||||
impl FileLogBackend {
|
||||
pub fn new(path: Option<&Path>) -> Self {
|
||||
Self {
|
||||
writer: path
|
||||
.map(|path| File::create(path).unwrap())
|
||||
.map(LineWriter::new)
|
||||
.map(RefCell::new),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LogBackend for FileLogBackend {
|
||||
fn avm_trace(&self, message: &str) {
|
||||
log::info!("avm_trace: {message}");
|
||||
if let Some(writer) = &self.writer {
|
||||
writer.borrow_mut().write_all(message.as_bytes()).unwrap();
|
||||
writer.borrow_mut().write_all("\n".as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user