Adding URLResponse Disk Cache to mojo.
This CL introduces a url response disk cache that allows the shell and content
handlers to access cached response data as file without the need to
stream the full content nor create a new file. It also allows
content handlers to access uncompressed cached archive.
The cache service is loaded internally by the shell because it is used
during application loads.
The android handler is using this service to cache the uncompressed archive of
android applications and the compiled dex files
R=blundell@chromium.org, davemoore@chromium.org
Review URL: https://codereview.chromium.org/1088533003
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index fd8ff5c..5827d86 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -123,6 +123,8 @@
# TODO(vtl): Split this target into parent/child/common libs.
source_set("lib") {
sources = [
+ "background_application_loader.cc",
+ "background_application_loader.h",
"child_process_host.cc",
"child_process_host.h",
"command_line_util.cc",
@@ -141,6 +143,8 @@
"tracer.h",
"url_resolver.cc",
"url_resolver.h",
+ "url_response_disk_cache_loader.cc",
+ "url_response_disk_cache_loader.h",
]
deps = [
@@ -156,6 +160,7 @@
"//mojo/public/interfaces/application",
"//mojo/services/network/public/interfaces",
"//shell/application_manager",
+ "//services/url_response_disk_cache",
"//services/tracing:bindings",
"//url",
]
@@ -170,8 +175,6 @@
"android/android_handler.h",
"android/android_handler_loader.cc",
"android/android_handler_loader.h",
- "android/background_application_loader.cc",
- "android/background_application_loader.h",
"android/intent_receiver_manager_factory.cc",
"android/intent_receiver_manager_factory.h",
"android/intent_receiver_manager_impl.cc",
@@ -376,6 +379,7 @@
test("mojo_shell_tests") {
sources = [
+ "background_application_loader_unittest.cc",
"child_process_host_unittest.cc",
"command_line_util_unittest.cc",
"context_unittest.cc",
@@ -412,8 +416,6 @@
]
if (is_android) {
- sources += [ "android/background_application_loader_unittest.cc" ]
-
deps += [ ":jni_headers" ]
apk_deps = [
diff --git a/shell/android/android_handler.cc b/shell/android/android_handler.cc
index 036be29..9d3181b 100644
--- a/shell/android/android_handler.cc
+++ b/shell/android/android_handler.cc
@@ -8,6 +8,8 @@
#include "base/android/jni_string.h"
#include "base/files/file_path.h"
#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
#include "base/scoped_native_library.h"
#include "base/trace_event/trace_event.h"
#include "jni/AndroidHandler_jni.h"
@@ -43,8 +45,7 @@
// Load the library, so that we can set the application context there if
// needed.
// TODO(vtl): We'd use a ScopedNativeLibrary, but it doesn't have .get()!
- base::NativeLibrary app_library =
- LoadNativeApplication(app_path, NativeApplicationCleanup::DELETE);
+ base::NativeLibrary app_library = LoadNativeApplication(app_path);
if (!app_library)
return;
@@ -90,20 +91,38 @@
uintptr_t tracing_id = reinterpret_cast<uintptr_t>(this);
TRACE_EVENT_ASYNC_BEGIN1("android_handler", "AndroidHandler::RunApplication",
tracing_id, "url", std::string(response->url));
- ScopedJavaLocalRef<jstring> j_archive_path =
- Java_AndroidHandler_getNewTempArchivePath(env, GetApplicationContext());
- base::FilePath archive_path(
- ConvertJavaStringToUTF8(env, j_archive_path.obj()));
+ base::FilePath extracted_dir;
+ base::FilePath cache_dir;
+ {
+ base::MessageLoop loop;
+ handler_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&AndroidHandler::ExtractApplication, base::Unretained(this),
+ base::Unretained(&extracted_dir),
+ base::Unretained(&cache_dir), base::Passed(response.Pass()),
+ base::Bind(base::IgnoreResult(
+ &base::SingleThreadTaskRunner::PostTask),
+ loop.task_runner(), FROM_HERE,
+ base::MessageLoop::QuitWhenIdleClosure())));
+ base::RunLoop().Run();
+ }
- mojo::common::BlockingCopyToFile(response->body.Pass(), archive_path);
+ ScopedJavaLocalRef<jstring> j_extracted_dir =
+ ConvertUTF8ToJavaString(env, extracted_dir.value());
+ ScopedJavaLocalRef<jstring> j_cache_dir =
+ ConvertUTF8ToJavaString(env, cache_dir.value());
RunAndroidApplicationFn run_android_application_fn = &RunAndroidApplication;
Java_AndroidHandler_bootstrap(
- env, GetApplicationContext(), tracing_id, j_archive_path.obj(),
+ env, GetApplicationContext(), tracing_id, j_extracted_dir.obj(),
+ j_cache_dir.obj(),
application_request.PassMessagePipe().release().value(),
reinterpret_cast<jlong>(run_android_application_fn));
}
void AndroidHandler::Initialize(mojo::ApplicationImpl* app) {
+ handler_task_runner_ = base::MessageLoop::current()->task_runner();
+ app->ConnectToService("mojo:url_response_disk_cache",
+ &url_response_disk_cache_);
}
bool AndroidHandler::ConfigureIncomingConnection(
@@ -113,6 +132,28 @@
return true;
}
+void AndroidHandler::ExtractApplication(base::FilePath* extracted_dir,
+ base::FilePath* cache_dir,
+ mojo::URLResponsePtr response,
+ const base::Closure& callback) {
+ url_response_disk_cache_->GetExtractedContent(
+ response.Pass(),
+ [extracted_dir, cache_dir, callback](mojo::Array<uint8_t> extracted_path,
+ mojo::Array<uint8_t> cache_path) {
+ if (extracted_path.is_null()) {
+ *extracted_dir = base::FilePath();
+ *cache_dir = base::FilePath();
+ } else {
+ *extracted_dir = base::FilePath(
+ std::string(reinterpret_cast<char*>(&extracted_path.front()),
+ extracted_path.size()));
+ *cache_dir = base::FilePath(std::string(
+ reinterpret_cast<char*>(&cache_path.front()), cache_path.size()));
+ }
+ callback.Run();
+ });
+}
+
bool RegisterAndroidHandlerJni(JNIEnv* env) {
return RegisterNativesImpl(env);
}
diff --git a/shell/android/android_handler.h b/shell/android/android_handler.h
index f2779ff..19adf36 100644
--- a/shell/android/android_handler.h
+++ b/shell/android/android_handler.h
@@ -7,10 +7,12 @@
#include <jni.h>
+#include "base/single_thread_task_runner.h"
#include "mojo/application/content_handler_factory.h"
#include "mojo/public/cpp/application/application_delegate.h"
#include "mojo/public/cpp/application/interface_factory_impl.h"
#include "mojo/services/content_handler/public/interfaces/content_handler.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
#include "shell/android/intent_receiver_manager_factory.h"
namespace base {
@@ -36,8 +38,15 @@
mojo::InterfaceRequest<mojo::Application> application_request,
mojo::URLResponsePtr response) override;
+ void ExtractApplication(base::FilePath* extracted_dir,
+ base::FilePath* cache_dir,
+ mojo::URLResponsePtr response,
+ const base::Closure& callback);
+
mojo::ContentHandlerFactory content_handler_factory_;
IntentReceiverManagerFactory intent_receiver_manager_factory_;
+ mojo::URLResponseDiskCachePtr url_response_disk_cache_;
+ scoped_refptr<base::SingleThreadTaskRunner> handler_task_runner_;
MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler);
};
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java b/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
index 138ed5a..9719f19 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java
@@ -14,7 +14,6 @@
import org.chromium.base.TraceEvent;
import java.io.File;
-import java.io.IOException;
import java.lang.reflect.Constructor;
/**
@@ -36,33 +35,19 @@
// File extensions used to identify application libraries in the provided archive.
private static final String JAVA_LIBRARY_SUFFIX = ".dex.jar";
private static final String NATIVE_LIBRARY_SUFFIX = ".so";
- // Filename sections used for naming temporary files holding application files.
- private static final String ARCHIVE_PREFIX = "archive";
- private static final String ARCHIVE_SUFFIX = ".zip";
- // Directories used to hold temporary files. These are cleared when clearTemporaryFiles() is
- // called.
- private static final String DEX_OUTPUT_DIRECTORY = "dex_output";
- private static final String APP_DIRECTORY = "applications";
- private static final String ASSET_DIRECTORY = "assets";
-
- /**
- * Deletes directories holding the temporary files. This should be called early on shell startup
- * to clean up after the previous run.
- */
- static void clearTemporaryFiles(Context context) {
- FileHelper.deleteRecursively(getDexOutputDir(context));
- FileHelper.deleteRecursively(getAppDir(context));
- FileHelper.deleteRecursively(getAssetDir(context));
- }
-
- /**
- * Returns the path at which the native part should save the application archive.
- */
- @CalledByNative
- private static String getNewTempArchivePath(Context context) throws IOException {
- return File.createTempFile(ARCHIVE_PREFIX, ARCHIVE_SUFFIX,
- getAppDir(context)).getAbsolutePath();
+ // Recursively finds the first file in |dir| whose name ends with |suffix|. Returns |null| if
+ // none is found.
+ static File find(File dir, String suffix) {
+ for (File child : dir.listFiles()) {
+ if (child.isDirectory()) {
+ File result = find(child, suffix);
+ if (result != null) return result;
+ } else {
+ if (child.getName().endsWith(suffix)) return child;
+ }
+ }
+ return null;
}
/**
@@ -70,56 +55,59 @@
*
* @param context the application context
* @param tracingId opaque id, used for tracing.
- * @param archivePath the path of the archive containing the application to be run
+ * @param extractedPath the path of the directory containing the application to be run
+ * @param cachePath the path of a cache directory that can be used to extract assets
* @param handle handle to the shell to be passed to the native application. On the Java side
* this is opaque payload.
* @param runApplicationPtr pointer to the function that will set the native thunks and call
* into the application MojoMain. On the Java side this is opaque payload.
*/
@CalledByNative
- private static boolean bootstrap(Context context, long tracingId, String archivePath,
- int handle, long runApplicationPtr) {
- File bootstrap_java_library;
- File bootstrap_native_library;
- try {
- TraceEvent.begin("ExtractBootstrapJavaLibrary");
- bootstrap_java_library = FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY,
- getAssetDir(context), true);
- TraceEvent.end("ExtractBootstrapJavaLibrary");
- TraceEvent.begin("ExtractBootstrapNativeLibrary");
- bootstrap_native_library = FileHelper.extractFromAssets(context,
- BOOTSTRAP_NATIVE_LIBRARY, getAssetDir(context), true);
- TraceEvent.end("ExtractBootstrapNativeLibrary");
- } catch (Exception e) {
- Log.e(TAG, "Extraction of bootstrap files from assets failed.", e);
- return false;
- }
+ private static boolean bootstrap(Context context, long tracingId, String extractedPath,
+ String cachePath, int handle, long runApplicationPtr) {
+ File extractedDir = new File(extractedPath);
+ File cacheDir = new File(cachePath);
+ File compiledDexDir = new File(cacheDir, "dex");
+ File assetDir = new File(cacheDir, "asset");
+ assetDir.mkdirs();
+ File preparedSentinel = new File(cacheDir, "prepared");
- File application_java_library;
- File application_native_library;
- try {
- File archive = new File(archivePath);
- TraceEvent.begin("ExtractApplicationJavaLibrary");
- application_java_library = FileHelper.extractFromArchive(archive, JAVA_LIBRARY_SUFFIX,
- getAppDir(context));
- TraceEvent.end("ExtractApplicationJavaLibrary");
- TraceEvent.begin("ExtractApplicationNativeLibrary");
- application_native_library = FileHelper.extractFromArchive(archive,
- NATIVE_LIBRARY_SUFFIX, getAppDir(context));
- TraceEvent.end("ExtractApplicationNativeLibrary");
- } catch (Exception e) {
- Log.e(TAG, "Extraction of application files from the archive failed.", e);
- return false;
+ // If the sentinel doesn't exist, extract the assets from the apk.
+ if (!preparedSentinel.exists()) {
+ compiledDexDir.mkdirs();
+ try {
+ TraceEvent.begin("ExtractBootstrapJavaLibrary");
+ FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY, assetDir, false);
+ TraceEvent.end("ExtractBootstrapJavaLibrary");
+ TraceEvent.begin("ExtractBootstrapNativeLibrary");
+ FileHelper.extractFromAssets(context, BOOTSTRAP_NATIVE_LIBRARY, assetDir, false);
+ TraceEvent.end("ExtractBootstrapNativeLibrary");
+ TraceEvent.begin("MoveBootstrapNativeLibrary");
+ // Rename the bootstrap library to prevent dlopen to think it is alread opened.
+ new File(assetDir, BOOTSTRAP_NATIVE_LIBRARY)
+ .renameTo(File.createTempFile("bootstrap", ".so", assetDir));
+ TraceEvent.end("MoveBootstrapNativeLibrary");
+ new java.io.FileOutputStream(preparedSentinel).close();
+ } catch (Exception e) {
+ Log.e(TAG, "Extraction of bootstrap files from assets failed.", e);
+ return false;
+ }
}
+ // Find the 4 files needed to execute the android application.
+ File bootstrap_java_library = new File(assetDir, BOOTSTRAP_JAVA_LIBRARY);
+ File bootstrap_native_library = find(assetDir, NATIVE_LIBRARY_SUFFIX);
+ File application_java_library = find(extractedDir, JAVA_LIBRARY_SUFFIX);
+ File application_native_library = find(extractedDir, NATIVE_LIBRARY_SUFFIX);
+ // Compile the java files.
String dexPath = bootstrap_java_library.getAbsolutePath() + File.pathSeparator
+ application_java_library.getAbsolutePath();
TraceEvent.begin("CreateDexClassLoader");
DexClassLoader bootstrapLoader = new DexClassLoader(dexPath,
- getDexOutputDir(context).getAbsolutePath(), null,
- ClassLoader.getSystemClassLoader());
+ compiledDexDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
TraceEvent.end("CreateDexClassLoader");
+ // Create the instance of the Bootstrap class from the compiled java file and run it.
try {
Class<?> loadedClass = bootstrapLoader.loadClass(BOOTSTRAP_CLASS);
Class<? extends Runnable> bootstrapClass = loadedClass.asSubclass(Runnable.class);
@@ -136,15 +124,4 @@
return true;
}
- private static File getDexOutputDir(Context context) {
- return context.getDir(DEX_OUTPUT_DIRECTORY, Context.MODE_PRIVATE);
- }
-
- private static File getAppDir(Context context) {
- return context.getDir(APP_DIRECTORY, Context.MODE_PRIVATE);
- }
-
- private static File getAssetDir(Context context) {
- return context.getDir(ASSET_DIRECTORY, Context.MODE_PRIVATE);
- }
}
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java b/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
index 18e4050..0dbdeab 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java
@@ -23,24 +23,16 @@
@Override
public void onCreate() {
super.onCreate();
- clearTemporaryFiles();
initializeJavaUtils();
initializeNative();
}
/**
- * Deletes the temporary files and directories created in the previous run of the application.
- * This is important regardless of cleanups on exit, as the previous run could have crashed.
- */
- private void clearTemporaryFiles() {
- AndroidHandler.clearTemporaryFiles(this);
- }
-
- /**
* Initializes Java-side utils.
*/
private void initializeJavaUtils() {
- PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX, getApplicationContext());
+ PathUtils.setPrivateDataDirectorySuffix(
+ PRIVATE_DATA_DIRECTORY_SUFFIX, getApplicationContext());
}
/**
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
index ab834af..e815967 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
@@ -29,6 +29,10 @@
private static final String NETWORK_LIBRARY_APP = "network_service.mojo";
// Directory where the child executable will be extracted.
private static final String CHILD_DIRECTORY = "child";
+ // Directory to set TMPDIR to.
+ private static final String TMP_DIRECTORY = "tmp";
+ // Directory to set HOME to.
+ private static final String HOME_DIRECTORY = "home";
// Name of the child executable.
private static final String MOJO_SHELL_CHILD_EXECUTABLE = "mojo_shell_child";
// Path to the default origin of mojo: apps.
@@ -69,7 +73,8 @@
nativeInit(applicationContext, mojoShellChild.getAbsolutePath(),
parametersList.toArray(new String[parametersList.size()]),
getLocalAppsDir(applicationContext).getAbsolutePath(),
- getTmpDir(applicationContext).getAbsolutePath());
+ getTmpDir(applicationContext).getAbsolutePath(),
+ getHomeDir(applicationContext).getAbsolutePath());
sInitialized = true;
} catch (Exception e) {
Log.e(TAG, "ShellMain initialization failed.", e);
@@ -109,7 +114,11 @@
}
private static File getTmpDir(Context context) {
- return new File(context.getCacheDir(), "tmp");
+ return new File(context.getCacheDir(), TMP_DIRECTORY);
+ }
+
+ private static File getHomeDir(Context context) {
+ return context.getDir(HOME_DIRECTORY, Context.MODE_PRIVATE);
}
@CalledByNative
@@ -121,7 +130,7 @@
* Initializes the native system. This API should be called only once per process.
**/
private static native void nativeInit(Context context, String mojoShellChildPath,
- String[] parameters, String bundledAppsDirectory, String tmpDir);
+ String[] parameters, String bundledAppsDirectory, String tmpDir, String homeDir);
private static native boolean nativeStart();
diff --git a/shell/android/main.cc b/shell/android/main.cc
index ac72bcb..299fc5a 100644
--- a/shell/android/main.cc
+++ b/shell/android/main.cc
@@ -24,10 +24,10 @@
#include "mojo/common/message_pump_mojo.h"
#include "mojo/services/window_manager/public/interfaces/window_manager.mojom.h"
#include "shell/android/android_handler_loader.h"
-#include "shell/android/background_application_loader.h"
#include "shell/android/native_viewport_application_loader.h"
#include "shell/android/ui_application_loader_android.h"
#include "shell/application_manager/application_loader.h"
+#include "shell/background_application_loader.h"
#include "shell/command_line_util.h"
#include "shell/context.h"
#include "shell/init.h"
@@ -181,17 +181,23 @@
jstring mojo_shell_child_path,
jobjectArray jparameters,
jstring j_local_apps_directory,
- jstring j_tmp_dir) {
+ jstring j_tmp_dir,
+ jstring j_home_dir) {
g_internal_data.Get().main_activity.Reset(env, activity);
// Initially, the shell runner is not ready.
g_internal_data.Get().shell_runner_ready.reset(
new base::WaitableEvent(true, false));
std::string tmp_dir = base::android::ConvertJavaStringToUTF8(env, j_tmp_dir);
- // Setting the TMPDIR environment variable so that applications can use it.
+ // Setting the TMPDIR and HOME environment variables so that applications can
+ // use it.
// TODO(qsr) We will need our subprocesses to inherit this.
int return_value = setenv("TMPDIR", tmp_dir.c_str(), 1);
DCHECK_EQ(return_value, 0);
+ return_value = setenv(
+ "HOME", base::android::ConvertJavaStringToUTF8(env, j_home_dir).c_str(),
+ 1);
+ DCHECK_EQ(return_value, 0);
base::android::ScopedJavaLocalRef<jobject> scoped_activity(env, activity);
base::android::InitApplicationContext(env, scoped_activity);
diff --git a/shell/application_manager/BUILD.gn b/shell/application_manager/BUILD.gn
index 72a90c8..e5ebb5d 100644
--- a/shell/application_manager/BUILD.gn
+++ b/shell/application_manager/BUILD.gn
@@ -33,6 +33,7 @@
"//mojo/common",
"//mojo/public/interfaces/application:application",
"//mojo/services/network/public/interfaces",
+ "//mojo/services/url_response_disk_cache/public/interfaces",
"//url",
]
deps = [
diff --git a/shell/application_manager/application_manager.cc b/shell/application_manager/application_manager.cc
index e1a6125..abaa11a 100644
--- a/shell/application_manager/application_manager.cc
+++ b/shell/application_manager/application_manager.cc
@@ -178,23 +178,19 @@
parameters);
if (resolved_url.SchemeIsFile()) {
- new LocalFetcher(
- resolved_url, GetBaseURLAndQuery(resolved_url, nullptr),
- base::Bind(callback, NativeApplicationCleanup::DONT_DELETE));
+ new LocalFetcher(resolved_url, GetBaseURLAndQuery(resolved_url, nullptr),
+ callback);
return;
}
- if (!network_service_)
+ if (!network_service_) {
ConnectToService(GURL("mojo:network_service"), &network_service_);
-
- const NativeApplicationCleanup cleanup =
- base::CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kDontDeleteOnDownload)
- ? NativeApplicationCleanup::DONT_DELETE
- : NativeApplicationCleanup::DELETE;
+ ConnectToService(GURL("mojo:url_response_disk_cache"),
+ &url_response_disk_cache_);
+ }
new NetworkFetcher(disable_cache_, resolved_url, network_service_.get(),
- base::Bind(callback, cleanup));
+ url_response_disk_cache_.get(), callback);
}
bool ApplicationManager::ConnectToRunningApplication(
@@ -274,7 +270,6 @@
ServiceProviderPtr exposed_services,
const base::Closure& on_application_end,
const std::vector<std::string>& parameters,
- NativeApplicationCleanup cleanup,
scoped_ptr<Fetcher> fetcher) {
if (!fetcher) {
// Network error. Drop |application_request| to tell requestor.
@@ -342,13 +337,12 @@
blocking_pool_,
base::Bind(&ApplicationManager::RunNativeApplication,
weak_ptr_factory_.GetWeakPtr(), base::Passed(request.Pass()),
- options, cleanup, base::Passed(fetcher.Pass())));
+ options, base::Passed(fetcher.Pass())));
}
void ApplicationManager::RunNativeApplication(
InterfaceRequest<Application> application_request,
const NativeRunnerFactory::Options& options,
- NativeApplicationCleanup cleanup,
scoped_ptr<Fetcher> fetcher,
const base::FilePath& path,
bool path_exists) {
@@ -367,7 +361,7 @@
path.AsUTF8Unsafe());
NativeRunner* runner = native_runner_factory_->Create(options).release();
native_runners_.push_back(runner);
- runner->Start(path, cleanup, application_request.Pass(),
+ runner->Start(path, application_request.Pass(),
base::Bind(&ApplicationManager::CleanupRunner,
weak_ptr_factory_.GetWeakPtr(), runner));
}
diff --git a/shell/application_manager/application_manager.h b/shell/application_manager/application_manager.h
index ded733f..a638786 100644
--- a/shell/application_manager/application_manager.h
+++ b/shell/application_manager/application_manager.h
@@ -15,6 +15,7 @@
#include "mojo/public/interfaces/application/application.mojom.h"
#include "mojo/public/interfaces/application/service_provider.mojom.h"
#include "mojo/services/network/public/interfaces/network_service.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
#include "shell/application_manager/application_loader.h"
#include "shell/application_manager/identity.h"
#include "shell/application_manager/native_runner.h"
@@ -188,13 +189,11 @@
mojo::ServiceProviderPtr exposed_services,
const base::Closure& on_application_end,
const std::vector<std::string>& parameters,
- NativeApplicationCleanup cleanup,
scoped_ptr<Fetcher> fetcher);
void RunNativeApplication(
mojo::InterfaceRequest<mojo::Application> application_request,
const NativeRunnerFactory::Options& options,
- NativeApplicationCleanup cleanup,
scoped_ptr<Fetcher> fetcher,
const base::FilePath& file_path,
bool path_exists);
@@ -232,6 +231,7 @@
base::SequencedWorkerPool* blocking_pool_;
mojo::NetworkServicePtr network_service_;
+ mojo::URLResponseDiskCachePtr url_response_disk_cache_;
MimeTypeToURLMap mime_type_to_url_;
ScopedVector<NativeRunner> native_runners_;
bool disable_cache_;
diff --git a/shell/application_manager/local_fetcher.cc b/shell/application_manager/local_fetcher.cc
index ad08913..58d2a2d 100644
--- a/shell/application_manager/local_fetcher.cc
+++ b/shell/application_manager/local_fetcher.cc
@@ -5,6 +5,7 @@
#include "shell/application_manager/local_fetcher.h"
#include "base/bind.h"
+#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/message_loop/message_loop.h"
@@ -13,6 +14,7 @@
#include "base/trace_event/trace_event.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/common/data_pipe_utils.h"
+#include <sys/stat.h>
#include "url/url_util.h"
namespace shell {
@@ -56,11 +58,14 @@
response->url = mojo::String::From(url_);
mojo::DataPipe data_pipe;
response->body = data_pipe.consumer_handle.Pass();
- int64 file_size;
- if (base::GetFileSize(path_, &file_size)) {
- response->headers = mojo::Array<mojo::String>(1);
+ base::stat_wrapper_t stat_result;
+ if (stat64(path_.value().c_str(), &stat_result) == 0) {
+ response->headers = mojo::Array<mojo::String>(2);
response->headers[0] =
- base::StringPrintf("Content-Length: %" PRId64, file_size);
+ base::StringPrintf("Content-Length: %" PRId64, stat_result.st_size);
+ response->headers[1] = base::StringPrintf(
+ "ETag: \"%" PRId64 "-%" PRId64 "-%" PRId64 "\"", stat_result.st_dev,
+ stat_result.st_ino, static_cast<uint64_t>(stat_result.st_mtime));
}
mojo::common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip,
task_runner, base::Bind(&IgnoreResult));
diff --git a/shell/application_manager/native_runner.h b/shell/application_manager/native_runner.h
index e18f214..e50bb08 100644
--- a/shell/application_manager/native_runner.h
+++ b/shell/application_manager/native_runner.h
@@ -24,16 +24,15 @@
virtual ~NativeRunner() {}
// Loads the app in the file at |app_path| and runs it on some other
- // thread/process. If |cleanup| is |DELETE|, this takes ownership of the file.
+ // thread/process.
// |app_completed_callback| is posted (to the thread on which |Start()| was
// called) after |MojoMain()| completes, or on any error (including if it
// fails to start).
- // TODO(vtl): |app_path| and |cleanup| should probably be moved to the
- // factory's Create(). Rationale: The factory may need information from the
- // file to decide what kind of NativeRunner to make.
+ // TODO(vtl): |app_path| should probably be moved to the factory's Create().
+ // Rationale: The factory may need information from the file to decide what
+ // kind of NativeRunner to make.
virtual void Start(
const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) = 0;
};
diff --git a/shell/application_manager/network_fetcher.cc b/shell/application_manager/network_fetcher.cc
index 50b9e11..ca5fdd8 100644
--- a/shell/application_manager/network_fetcher.cc
+++ b/shell/application_manager/network_fetcher.cc
@@ -26,13 +26,16 @@
namespace shell {
-NetworkFetcher::NetworkFetcher(bool disable_cache,
- const GURL& url,
- mojo::NetworkService* network_service,
- const FetchCallback& loader_callback)
+NetworkFetcher::NetworkFetcher(
+ bool disable_cache,
+ const GURL& url,
+ mojo::NetworkService* network_service,
+ mojo::URLResponseDiskCache* url_response_disk_cache,
+ const FetchCallback& loader_callback)
: Fetcher(loader_callback),
disable_cache_(false),
url_(url),
+ url_response_disk_cache_(url_response_disk_cache),
weak_ptr_factory_(this) {
StartNetworkRequest(url, network_service);
}
@@ -148,14 +151,17 @@
return base::Move(old_path, *new_path);
}
-void NetworkFetcher::CopyCompleted(
+void NetworkFetcher::OnFileRetrievedFromCache(
base::Callback<void(const base::FilePath&, bool)> callback,
- bool success) {
+ mojo::Array<uint8_t> path_as_array,
+ mojo::Array<uint8_t> cache_dir) {
+ bool success = !path_as_array.is_null();
if (success) {
+ path_ = base::FilePath(std::string(
+ reinterpret_cast<char*>(&path_as_array.front()), path_as_array.size()));
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kPredictableAppFilenames)) {
// The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen.
- success = false;
base::FilePath new_path;
if (RenameToAppId(url_, path_, &new_path)) {
if (base::PathExists(new_path)) {
@@ -176,17 +182,12 @@
void NetworkFetcher::AsPath(
base::TaskRunner* task_runner,
base::Callback<void(const base::FilePath&, bool)> callback) {
- if (!path_.empty() || !response_) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
- return;
- }
+ // This should only called once, when we have a response.
+ DCHECK(response_.get());
- base::CreateTemporaryFile(&path_);
- mojo::common::CopyToFile(
- response_->body.Pass(), path_, task_runner,
- base::Bind(&NetworkFetcher::CopyCompleted, weak_ptr_factory_.GetWeakPtr(),
- callback));
+ url_response_disk_cache_->GetFile(
+ response_.Pass(), base::Bind(&NetworkFetcher::OnFileRetrievedFromCache,
+ weak_ptr_factory_.GetWeakPtr(), callback));
}
std::string NetworkFetcher::MimeType() {
diff --git a/shell/application_manager/network_fetcher.h b/shell/application_manager/network_fetcher.h
index f8632ad..c512b70 100644
--- a/shell/application_manager/network_fetcher.h
+++ b/shell/application_manager/network_fetcher.h
@@ -10,6 +10,7 @@
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "mojo/services/url_response_disk_cache/public/interfaces/url_response_disk_cache.mojom.h"
#include "url/gurl.h"
namespace mojo {
@@ -24,6 +25,7 @@
NetworkFetcher(bool disable_cache,
const GURL& url,
mojo::NetworkService* network_service,
+ mojo::URLResponseDiskCache* url_response_disk_cache,
const FetchCallback& loader_callback);
~NetworkFetcher() override;
@@ -50,8 +52,10 @@
const base::FilePath& old_path,
base::FilePath* new_path);
- void CopyCompleted(base::Callback<void(const base::FilePath&, bool)> callback,
- bool success);
+ void OnFileRetrievedFromCache(
+ base::Callback<void(const base::FilePath&, bool)> callback,
+ mojo::Array<uint8_t> path_as_array,
+ mojo::Array<uint8_t> cache_dir);
void AsPath(
base::TaskRunner* task_runner,
@@ -70,6 +74,7 @@
bool disable_cache_;
const GURL url_;
+ mojo::URLResponseDiskCache* url_response_disk_cache_;
mojo::URLLoaderPtr url_loader_;
mojo::URLResponsePtr response_;
base::FilePath path_;
diff --git a/shell/android/background_application_loader.cc b/shell/background_application_loader.cc
similarity index 97%
rename from shell/android/background_application_loader.cc
rename to shell/background_application_loader.cc
index 849d312..476f60a 100644
--- a/shell/android/background_application_loader.cc
+++ b/shell/background_application_loader.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "shell/android/background_application_loader.h"
+#include "shell/background_application_loader.h"
#include "base/bind.h"
#include "base/run_loop.h"
diff --git a/shell/android/background_application_loader.h b/shell/background_application_loader.h
similarity index 92%
rename from shell/android/background_application_loader.h
rename to shell/background_application_loader.h
index 5362963..6d9cb6d 100644
--- a/shell/android/background_application_loader.h
+++ b/shell/background_application_loader.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
-#define SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+#ifndef SHELL_BACKGROUND_APPLICATION_LOADER_H_
+#define SHELL_BACKGROUND_APPLICATION_LOADER_H_
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
@@ -60,4 +60,4 @@
} // namespace shell
-#endif // SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_
+#endif // SHELL_BACKGROUND_APPLICATION_LOADER_H_
diff --git a/shell/android/background_application_loader_unittest.cc b/shell/background_application_loader_unittest.cc
similarity index 96%
rename from shell/android/background_application_loader_unittest.cc
rename to shell/background_application_loader_unittest.cc
index 17291b4..ef47060 100644
--- a/shell/android/background_application_loader_unittest.cc
+++ b/shell/background_application_loader_unittest.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "shell/android/background_application_loader.h"
+#include "shell/background_application_loader.h"
#include "mojo/public/interfaces/application/application.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/shell/child_controller.mojom b/shell/child_controller.mojom
index 7313410..a0bf785 100644
--- a/shell/child_controller.mojom
+++ b/shell/child_controller.mojom
@@ -7,9 +7,8 @@
import "mojo/public/interfaces/application/application.mojom";
interface ChildController {
- // Starts the app at the given path (deleting it if |clean_app_path| is true).
+ // Starts the app at the given path.
StartApp(string app_path,
- bool clean_app_path,
mojo.Application& application_request) => (int32 result);
// Exits the child process now (with no cleanup), with the given exit code.
diff --git a/shell/child_main.cc b/shell/child_main.cc
index 6fc14b3..1e3834e 100644
--- a/shell/child_main.cc
+++ b/shell/child_main.cc
@@ -216,7 +216,6 @@
// |ChildController| methods:
void StartApp(const mojo::String& app_path,
- bool clean_app_path,
mojo::InterfaceRequest<mojo::Application> application_request,
const StartAppCallback& on_app_complete) override {
DVLOG(2) << "ChildControllerImpl::StartApp(" << app_path << ", ...)";
@@ -225,9 +224,6 @@
on_app_complete_ = on_app_complete;
unblocker_.Unblock(base::Bind(&ChildControllerImpl::StartAppOnMainThread,
base::FilePath::FromUTF8Unsafe(app_path),
- clean_app_path
- ? NativeApplicationCleanup::DELETE
- : NativeApplicationCleanup::DONT_DELETE,
base::Passed(&application_request)));
}
@@ -255,7 +251,6 @@
static void StartAppOnMainThread(
const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request) {
// TODO(vtl): This is copied from in_process_native_runner.cc.
DVLOG(2) << "Loading/running Mojo app from " << app_path.value()
@@ -263,7 +258,7 @@
// We intentionally don't unload the native library as its lifetime is the
// same as that of the process.
- base::NativeLibrary app_library = LoadNativeApplication(app_path, cleanup);
+ base::NativeLibrary app_library = LoadNativeApplication(app_path);
RunNativeApplication(app_library, application_request.Pass());
}
diff --git a/shell/child_process_host.cc b/shell/child_process_host.cc
index d0ab5dd..8660162 100644
--- a/shell/child_process_host.cc
+++ b/shell/child_process_host.cc
@@ -61,14 +61,13 @@
void ChildProcessHost::StartApp(
const mojo::String& app_path,
- bool clean_app_path,
mojo::InterfaceRequest<mojo::Application> application_request,
const ChildController::StartAppCallback& on_app_complete) {
DCHECK(controller_);
on_app_complete_ = on_app_complete;
controller_->StartApp(
- app_path, clean_app_path, application_request.Pass(),
+ app_path, application_request.Pass(),
base::Bind(&ChildProcessHost::AppCompleted, base::Unretained(this)));
}
diff --git a/shell/child_process_host.h b/shell/child_process_host.h
index 86a6afa..c0cef79 100644
--- a/shell/child_process_host.h
+++ b/shell/child_process_host.h
@@ -52,7 +52,6 @@
// |on_app_complete| will *always* get called, even on connection error (or
// even if the child process failed to start at all).
void StartApp(const mojo::String& app_path,
- bool clean_app_path,
mojo::InterfaceRequest<mojo::Application> application_request,
const ChildController::StartAppCallback& on_app_complete);
void ExitNow(int32_t exit_code);
diff --git a/shell/child_process_host_unittest.cc b/shell/child_process_host_unittest.cc
index 31ace06..bb1817b 100644
--- a/shell/child_process_host_unittest.cc
+++ b/shell/child_process_host_unittest.cc
@@ -83,7 +83,7 @@
application_request.Bind(mp.handle0.Pass());
// This won't actually be called, but the callback should be run.
MojoResult result = MOJO_RESULT_INTERNAL;
- child_process_host.StartApp("/does_not_exist/cbvgyuio", false,
+ child_process_host.StartApp("/does_not_exist/cbvgyuio",
application_request.Pass(), [&result](int32_t r) {
result = r;
base::MessageLoop::current()->QuitWhenIdle();
diff --git a/shell/context.cc b/shell/context.cc
index 8aa8a7a..331efa1 100644
--- a/shell/context.cc
+++ b/shell/context.cc
@@ -31,12 +31,14 @@
#include "services/tracing/tracing.mojom.h"
#include "shell/application_manager/application_loader.h"
#include "shell/application_manager/application_manager.h"
+#include "shell/background_application_loader.h"
#include "shell/command_line_util.h"
#include "shell/filename_util.h"
#include "shell/in_process_native_runner.h"
#include "shell/out_of_process_native_runner.h"
#include "shell/switches.h"
#include "shell/tracer.h"
+#include "shell/url_response_disk_cache_loader.h"
#include "url/gurl.h"
using mojo::ServiceProvider;
@@ -258,6 +260,12 @@
mojo_shell_child_path_ = shell_child_path;
+ application_manager()->SetLoaderForURL(
+ make_scoped_ptr(new BackgroundApplicationLoader(
+ make_scoped_ptr(new URLResponseDiskCacheLoader()),
+ "url_response_disk_cache", base::MessageLoop::TYPE_DEFAULT)),
+ GURL("mojo:url_response_disk_cache"));
+
EnsureEmbedderIsInitialized();
task_runners_.reset(
new TaskRunners(base::MessageLoop::current()->message_loop_proxy()));
diff --git a/shell/in_process_native_runner.cc b/shell/in_process_native_runner.cc
index f12708d..f5ae3ee 100644
--- a/shell/in_process_native_runner.cc
+++ b/shell/in_process_native_runner.cc
@@ -14,7 +14,7 @@
namespace shell {
InProcessNativeRunner::InProcessNativeRunner(Context* context)
- : cleanup_(NativeApplicationCleanup::DONT_DELETE), app_library_(nullptr) {
+ : app_library_(nullptr) {
}
InProcessNativeRunner::~InProcessNativeRunner() {
@@ -30,11 +30,9 @@
void InProcessNativeRunner::Start(
const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) {
app_path_ = app_path;
- cleanup_ = cleanup;
DCHECK(!application_request_.is_pending());
application_request_ = application_request.Pass();
@@ -56,7 +54,7 @@
<< " thread id=" << base::PlatformThread::CurrentId();
// TODO(vtl): ScopedNativeLibrary doesn't have a .get() method!
- base::NativeLibrary app_library = LoadNativeApplication(app_path_, cleanup_);
+ base::NativeLibrary app_library = LoadNativeApplication(app_path_);
app_library_.Reset(app_library);
RunNativeApplication(app_library, application_request_.Pass());
app_completed_callback_runner_.Run();
diff --git a/shell/in_process_native_runner.h b/shell/in_process_native_runner.h
index 0d5e870..41065c1 100644
--- a/shell/in_process_native_runner.h
+++ b/shell/in_process_native_runner.h
@@ -28,7 +28,6 @@
// |NativeRunner| method:
void Start(const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) override;
@@ -37,7 +36,6 @@
void Run() override;
base::FilePath app_path_;
- NativeApplicationCleanup cleanup_;
mojo::InterfaceRequest<mojo::Application> application_request_;
base::Callback<bool(void)> app_completed_callback_runner_;
diff --git a/shell/native_application_support.cc b/shell/native_application_support.cc
index 378a014..f82b4ef 100644
--- a/shell/native_application_support.cc
+++ b/shell/native_application_support.cc
@@ -42,14 +42,11 @@
} // namespace
-base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path,
- NativeApplicationCleanup cleanup) {
+base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path) {
DVLOG(2) << "Loading Mojo app in process from library: " << app_path.value();
base::NativeLibraryLoadError error;
base::NativeLibrary app_library = base::LoadNativeLibrary(app_path, &error);
- if (cleanup == NativeApplicationCleanup::DELETE)
- DeleteFile(app_path, false);
LOG_IF(ERROR, !app_library)
<< "Failed to load app library (error: " << error.ToString() << ")";
return app_library;
diff --git a/shell/native_application_support.h b/shell/native_application_support.h
index 2c3b0db..5e24d4b 100644
--- a/shell/native_application_support.h
+++ b/shell/native_application_support.h
@@ -18,19 +18,15 @@
namespace shell {
-enum class NativeApplicationCleanup { DELETE, DONT_DELETE };
-
// Loads the native Mojo application from the DSO specified by |app_path|.
// Returns the |base::NativeLibrary| for the application on success (or null on
-// failure). If |cleanup| is |DELETE|, it will delete |app_path| (regardless of
-// sucess or failure).
+// failure).
//
// Note: The caller may choose to eventually unload the returned DSO. If so,
// this should be done only after the thread on which |LoadNativeApplication()|
// and |RunNativeApplication()| were called has terminated, so that any
// thread-local destructors have been executed.
-base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path,
- NativeApplicationCleanup cleanup);
+base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path);
// Runs the native Mojo application from the DSO that was loaded using
// |LoadNativeApplication()|; this tolerates |app_library| being null. This
diff --git a/shell/native_runner_unittest.cc b/shell/native_runner_unittest.cc
index 2b732c0..fe792c9 100644
--- a/shell/native_runner_unittest.cc
+++ b/shell/native_runner_unittest.cc
@@ -32,7 +32,6 @@
base::MessageLoop::current()->Quit();
}
void Start(const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) override {
state_->runner_was_started = true;
diff --git a/shell/out_of_process_native_runner.cc b/shell/out_of_process_native_runner.cc
index da3abdb..39cf1f7 100644
--- a/shell/out_of_process_native_runner.cc
+++ b/shell/out_of_process_native_runner.cc
@@ -29,7 +29,6 @@
void OutOfProcessNativeRunner::Start(
const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) {
app_path_ = app_path;
@@ -42,8 +41,7 @@
// TODO(vtl): |app_path.AsUTF8Unsafe()| is unsafe.
child_process_host_->StartApp(
- app_path.AsUTF8Unsafe(), cleanup == NativeApplicationCleanup::DELETE,
- application_request.Pass(),
+ app_path.AsUTF8Unsafe(), application_request.Pass(),
base::Bind(&OutOfProcessNativeRunner::AppCompleted,
base::Unretained(this)));
}
diff --git a/shell/out_of_process_native_runner.h b/shell/out_of_process_native_runner.h
index ff86668..23e0372 100644
--- a/shell/out_of_process_native_runner.h
+++ b/shell/out_of_process_native_runner.h
@@ -26,7 +26,6 @@
// |NativeRunner| method:
void Start(const base::FilePath& app_path,
- NativeApplicationCleanup cleanup,
mojo::InterfaceRequest<mojo::Application> application_request,
const base::Closure& app_completed_callback) override;
diff --git a/shell/url_response_disk_cache_loader.cc b/shell/url_response_disk_cache_loader.cc
new file mode 100644
index 0000000..3371cb5
--- /dev/null
+++ b/shell/url_response_disk_cache_loader.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shell/url_response_disk_cache_loader.h"
+
+namespace shell {
+
+URLResponseDiskCacheLoader::URLResponseDiskCacheLoader() {
+}
+
+URLResponseDiskCacheLoader::~URLResponseDiskCacheLoader() {
+}
+
+void URLResponseDiskCacheLoader::Load(
+ const GURL& url,
+ mojo::InterfaceRequest<mojo::Application> application_request) {
+ DCHECK(application_request.is_pending());
+ application_.reset(new mojo::ApplicationImpl(&url_response_disk_cache_,
+ application_request.Pass()));
+}
+
+} // namespace shell
diff --git a/shell/url_response_disk_cache_loader.h b/shell/url_response_disk_cache_loader.h
new file mode 100644
index 0000000..4f75433
--- /dev/null
+++ b/shell/url_response_disk_cache_loader.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_
+#define SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "services/url_response_disk_cache/url_response_disk_cache_app.h"
+#include "shell/application_manager/application_loader.h"
+
+namespace shell {
+
+class URLResponseDiskCacheLoader : public ApplicationLoader {
+ public:
+ URLResponseDiskCacheLoader();
+ ~URLResponseDiskCacheLoader() override;
+
+ private:
+ // ApplicationLoader overrides:
+ void Load(
+ const GURL& url,
+ mojo::InterfaceRequest<mojo::Application> application_request) override;
+
+ mojo::URLResponseDiskCacheApp url_response_disk_cache_;
+ scoped_ptr<mojo::ApplicationImpl> application_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLResponseDiskCacheLoader);
+};
+
+} // namespace shell
+
+#endif // SHELL_URL_RESPONSE_DISK_CACHE_LOADER_H_