Merge pull request #1414 from mpcomplete/use.changes.2

Implement working UpdateTask in Dart
This commit is contained in:
Matt Perry
2015-09-30 18:15:12 -04:00
15 changed files with 333 additions and 35 deletions
+6 -1
View File
@@ -9,7 +9,7 @@
namespace blink {
SkyHeadless::SkyHeadless() {
SkyHeadless::SkyHeadless(Client* client) : client_(client) {
}
SkyHeadless::~SkyHeadless() {
@@ -20,6 +20,11 @@ void SkyHeadless::Init(const String& name) {
dart_controller_ = adoptPtr(new DartController);
dart_controller_->CreateIsolateFor(adoptPtr(new DOMDartState(name)));
Dart_Isolate isolate = dart_controller_->dart_state()->isolate();
DartIsolateScope scope(isolate);
DartApiScope api_scope;
client_->DidCreateIsolate(isolate);
}
void SkyHeadless::RunFromSnapshotBuffer(const uint8_t* buffer, size_t size) {
+12 -1
View File
@@ -9,13 +9,23 @@
#include "sky/engine/wtf/OwnPtr.h"
#include "sky/engine/wtf/text/WTFString.h"
typedef struct _Dart_Isolate* Dart_Isolate;
namespace blink {
class DartController;
// This class provides a way to run Dart script without a View.
class SkyHeadless {
public:
SkyHeadless();
class Client {
public:
virtual void DidCreateIsolate(Dart_Isolate isolate) = 0;
protected:
virtual ~Client() {}
};
SkyHeadless(Client* client);
~SkyHeadless();
void Init(const String& name);
@@ -23,6 +33,7 @@ class SkyHeadless {
private:
OwnPtr<DartController> dart_controller_;
Client* client_;
DISALLOW_COPY_AND_ASSIGN(SkyHeadless);
};
@@ -37,6 +37,15 @@ UserFeedbackProxy _initUserFeedbackProxy() {
final UserFeedbackProxy _userFeedbackProxy = _initUserFeedbackProxy();
final UserFeedback userFeedback = _userFeedbackProxy.ptr;
PathServiceProxy _initPathServiceProxy() {
PathServiceProxy proxy = new PathServiceProxy.unbound();
shell.requestService(null, proxy);
return proxy;
}
final PathServiceProxy _pathServiceProxy = _initPathServiceProxy();
final PathService pathService = _pathServiceProxy.ptr;
Color _cachedPrimaryColor;
String _cachedLabel;
@@ -55,5 +64,6 @@ void updateTaskDescription(String label, Color color) {
_activityProxy.ptr.setTaskDescription(description);
}
Future<String> getFilesDir() async => (await _activityProxy.ptr.getFilesDir()).path;
Future<String> getCacheDir() async => (await _activityProxy.ptr.getCacheDir()).path;
Future<String> getAppDataDir() async => (await _pathServiceProxy.ptr.getAppDataDir()).path;
Future<String> getFilesDir() async => (await _pathServiceProxy.ptr.getFilesDir()).path;
Future<String> getCacheDir() async => (await _pathServiceProxy.ptr.getCacheDir()).path;
+2
View File
@@ -25,6 +25,8 @@ action("updater") {
]
deps = [
"//sky/services/activity:interfaces",
"//sky/services/updater:interfaces",
"//sky/tools/sky_snapshot($host_toolchain)",
]
}
+94 -3
View File
@@ -2,13 +2,104 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:mojo/core.dart';
import 'package:sky/services.dart';
import 'package:sky_services/updater/update_service.mojom.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart' as yaml;
import 'version.dart';
import 'pipe_to_file.dart';
const String kManifestFile = 'sky.yaml';
const String kBundleFile = 'app.skyx';
UpdateServiceProxy _initUpdateService() {
UpdateServiceProxy updateService = new UpdateServiceProxy.unbound();
shell.requestService(null, updateService);
return updateService;
}
final UpdateServiceProxy _updateService = _initUpdateService();
String cachedDataDir = null;
Future<String> getDataDir() async {
if (cachedDataDir == null)
cachedDataDir = await getAppDataDir();
return cachedDataDir;
}
class UpdateTask {
UpdateTask() {}
String toString() => "UpdateTask()";
run() async {
try {
await _runImpl();
} catch(e) {
print('Update failed: $e');
} finally {
_updateService.ptr.notifyUpdateCheckComplete();
}
}
_runImpl() async {
_dataDir = await getDataDir();
await _readLocalManifest();
yaml.YamlMap remoteManifest = await _fetchManifest();
if (!_shouldUpdate(remoteManifest)) {
print('Update skipped. No new version.');
return;
}
MojoResult result = await _fetchBundle();
if (!result.isOk) {
print('Update failed while fetching new skyx bundle.');
return;
}
await _replaceBundle();
print('Update success.');
}
yaml.YamlMap _currentManifest;
String _dataDir;
String _tempPath;
_readLocalManifest() async {
String manifestPath = path.join(_dataDir, kManifestFile);
String manifestData = await new File(manifestPath).readAsString();
_currentManifest = yaml.loadYaml(manifestData, sourceUrl: manifestPath);
}
Future<yaml.YamlMap> _fetchManifest() async {
String manifestUrl = _currentManifest['update_url'] + '/' + kManifestFile;
String manifestData = await fetchString(manifestUrl);
return yaml.loadYaml(manifestData, sourceUrl: manifestUrl);
}
bool _shouldUpdate(yaml.YamlMap remoteManifest) {
Version currentVersion = new Version(_currentManifest['version']);
Version remoteVersion = new Version(remoteManifest['version']);
return (currentVersion < remoteVersion);
}
Future<MojoResult> _fetchBundle() async {
// TODO(mpcomplete): Use the cache dir. We need an equivalent of mkstemp().
_tempPath = path.join(_dataDir, 'tmp.skyx');
String bundleUrl = _currentManifest['update_url'] + '/' + kBundleFile;
UrlResponse response = await fetchUrl(bundleUrl);
return PipeToFile.copyToFile(response.body, _tempPath);
}
_replaceBundle() async {
String bundlePath = path.join(_dataDir, kBundleFile);
await new File(_tempPath).rename(bundlePath);
}
}
void main() {
var x = new UpdateTask();
print("Success: $x");
var task = new UpdateTask();
task.run();
}
@@ -0,0 +1,64 @@
// Copyright 2015 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.
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:mojo/core.dart';
// Helper class to drain the contents of a mojo data pipe to a file.
class PipeToFile {
MojoDataPipeConsumer _consumer;
MojoEventStream _eventStream;
IOSink _outputStream;
PipeToFile(this._consumer, String outputPath) {
_eventStream = new MojoEventStream(_consumer.handle);
_outputStream = new File(outputPath).openWrite();
}
Future<MojoResult> _doRead() async {
ByteData thisRead = _consumer.beginRead();
if (thisRead == null) {
throw 'Data pipe beginRead failed: ${_consumer.status}';
}
// TODO(mpcomplete): Should I worry about the _eventStream listen callback
// being invoked again before this completes?
await _outputStream.add(thisRead.buffer.asUint8List());
return _consumer.endRead(thisRead.lengthInBytes);
}
Future<MojoResult> drain() async {
var completer = new Completer();
// TODO(mpcomplete): Is it legit to pass an async callback to listen?
_eventStream.listen((List<int> event) async {
var mojoSignals = new MojoHandleSignals(event[1]);
if (mojoSignals.isReadable) {
var result = await _doRead();
if (!result.isOk) {
_eventStream.close();
_eventStream = null;
_outputStream.close();
completer.complete(result);
} else {
_eventStream.enableReadEvents();
}
} else if (mojoSignals.isPeerClosed) {
_eventStream.close();
_eventStream = null;
_outputStream.close();
completer.complete(MojoResult.OK);
} else {
throw 'Unexpected handle event: $mojoSignals';
}
});
return completer.future;
}
static Future<MojoResult> copyToFile(MojoDataPipeConsumer consumer, String outputPath) {
var drainer = new PipeToFile(consumer, outputPath);
return drainer.drain();
}
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2015 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.
import 'dart:math';
// This class represents a dot-separated version string. Used for comparing
// versions.
// Usage: assert(new Version('1.1.0') < new Version('1.2.1'));
class Version {
Version(String versionStr) :
_parts = versionStr.split('.').map((val) => int.parse(val)).toList();
List<int> _parts;
bool operator<(Version other) => _compare(other) < 0;
bool operator==(dynamic other) => other is Version && _compare(other) == 0;
bool operator>(Version other) => _compare(other) > 0;
int _compare(Version other) {
int length = min(_parts.length, other._parts.length);
for (int i = 0; i < length; ++i) {
if (_parts[i] < other._parts[i])
return -1;
if (_parts[i] > other._parts[i])
return 1;
}
return _parts.length - other._parts.length; // results in 1.0 < 1.0.0
}
int get hashCode => _parts.fold(373, (acc, part) => 37*acc + part);
}
+9 -2
View File
@@ -1,9 +1,16 @@
name: sky_updater
name: updater
version: 0.0.1
author: Flutter Authors <flutter-dev@googlegroups.com>
description: The autoupdater for flutter
homepage: http://flutter.io
dependencies:
mojo: ^0.0.21
mojo: '>=0.1.0 <0.2.0'
sky: any
sky_services: any
path: any
yaml: any
dependency_overrides:
sky:
path: ../sky
environment:
sdk: '>=1.12.0 <2.0.0'
+1
View File
@@ -29,6 +29,7 @@ if (is_android) {
android_library("activity_lib") {
java_files = [
"src/org/domokit/activity/ActivityImpl.java",
"src/org/domokit/activity/PathServiceImpl.java",
"src/org/domokit/activity/UserFeedbackImpl.java",
]
+4
View File
@@ -73,6 +73,10 @@ interface PathService {
GetCacheDir() => (string path);
};
interface UpdateService {
NotifyUpdateCheckComplete();
};
enum HapticFeedbackType {
LONG_PRESS,
VIRTUAL_KEY,
@@ -0,0 +1,43 @@
// Copyright 2015 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.
package org.domokit.activity;
import android.content.Context;
import org.chromium.base.PathUtils;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojom.activity.PathService;
/**
* Android implementation of PathService.
*/
public class PathServiceImpl implements PathService {
private static final String TAG = "PathServiceImpl";
private static android.content.Context context;
public PathServiceImpl(android.content.Context context) {
this.context = context;
}
@Override
public void close() {}
@Override
public void onConnectionError(MojoException e) {}
@Override
public void getAppDataDir(GetAppDataDirResponse callback) {
callback.call(PathUtils.getDataDirectory(context));
}
@Override
public void getFilesDir(GetFilesDirResponse callback) {
callback.call(context.getFilesDir().getPath());
}
@Override
public void getCacheDir(GetCacheDirResponse callback) {
callback.call(context.getCacheDir().getPath());
}
}
@@ -17,12 +17,14 @@ import org.chromium.mojo.sensors.SensorServiceImpl;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.MessagePipeHandle;
import org.chromium.mojom.activity.Activity;
import org.chromium.mojom.activity.PathService;
import org.chromium.mojom.keyboard.KeyboardService;
import org.chromium.mojom.media.MediaService;
import org.chromium.mojom.mojo.NetworkService;
import org.chromium.mojom.sensors.SensorService;
import org.chromium.mojom.vsync.VSyncProvider;
import org.domokit.activity.ActivityImpl;
import org.domokit.activity.PathServiceImpl;
import org.domokit.media.MediaServiceImpl;
import org.domokit.oknet.NetworkServiceImpl;
import org.domokit.vsync.VSyncProviderImpl;
@@ -74,6 +76,22 @@ public class SkyApplication extends BaseChromiumApplication {
}
});
registry.register(org.chromium.mojom.updater.UpdateService.MANAGER.getName(),
new ServiceFactory() {
@Override
public void connectToService(Context context, Core core, MessagePipeHandle pipe) {
org.chromium.mojom.updater.UpdateService.MANAGER.bind(
new UpdateService.MojoService(), pipe);
}
});
registry.register(PathService.MANAGER.getName(), new ServiceFactory() {
@Override
public void connectToService(Context context, Core core, MessagePipeHandle pipe) {
PathService.MANAGER.bind(new PathServiceImpl(getApplicationContext()), pipe);
}
});
registry.register(KeyboardService.MANAGER.getName(), new ServiceFactory() {
@Override
public void connectToService(Context context, Core core, MessagePipeHandle pipe) {
@@ -15,6 +15,7 @@ import java.io.File;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.PathUtils;
import org.chromium.mojo.system.MojoException;
/**
* A class that schedules and runs periodic autoupdate checks.
@@ -24,11 +25,28 @@ public class UpdateService extends Service {
private static final String TAG = "UpdateService";
private static final int REQUEST_CODE = 0; // Not sure why this is needed.
private static final boolean ENABLED = false;
private static final boolean TESTING = false;
private long mNativePtr = 0;
private static UpdateService sCurrentUpdateService = null;
static class MojoService implements org.chromium.mojom.updater.UpdateService {
@Override
public void close() {}
@Override
public void onConnectionError(MojoException e) {}
@Override
public void notifyUpdateCheckComplete() {
if (sCurrentUpdateService != null)
sCurrentUpdateService.stopSelf();
}
}
public static void init(Context context) {
if (ENABLED)
if (ENABLED || TESTING)
maybeScheduleUpdateCheck(context);
}
@@ -36,7 +54,7 @@ public class UpdateService extends Service {
Intent alarm = new Intent(context, UpdateService.class);
PendingIntent existingIntent = PendingIntent.getService(
context, REQUEST_CODE, alarm, PendingIntent.FLAG_NO_CREATE);
if (existingIntent != null) {
if (existingIntent != null && !TESTING) {
Log.i(TAG, "Update alarm exists: " + PathUtils.getDataDirectory(context));
return;
}
@@ -52,6 +70,7 @@ public class UpdateService extends Service {
@Override
public void onCreate() {
sCurrentUpdateService = this;
super.onCreate();
SkyMain.ensureInitialized(getApplicationContext(), null);
}
@@ -61,6 +80,7 @@ public class UpdateService extends Service {
if (mNativePtr != 0)
nativeDestroy(mNativePtr);
mNativePtr = 0;
sCurrentUpdateService = null;
}
@Override
@@ -74,12 +94,6 @@ public class UpdateService extends Service {
return null;
}
@SuppressWarnings("unused")
@CalledByNative
public void onUpdateFinished() {
stopSelf();
}
private native long nativeCheckForUpdates();
private native void nativeDestroy(long nativeUpdateTaskAndroid);
}
+9 -12
View File
@@ -10,6 +10,7 @@
#include "sky/engine/bindings/updater_snapshot.h"
#include "sky/engine/public/sky/sky_headless.h"
#include "sky/shell/shell.h"
#include "sky/shell/ui/internals.h"
namespace sky {
namespace shell {
@@ -25,7 +26,7 @@ bool RegisterUpdateService(JNIEnv* env) {
}
UpdateTaskAndroid::UpdateTaskAndroid(JNIEnv* env, jobject update_service)
: headless_(new blink::SkyHeadless) {
: headless_(new blink::SkyHeadless(this)) {
update_service_.Reset(env, update_service);
}
@@ -38,24 +39,20 @@ void UpdateTaskAndroid::Start() {
base::Unretained(this)));
}
void UpdateTaskAndroid::RunDartOnUIThread() {
// TODO(mpcomplete): pass variables into main.
void UpdateTaskAndroid::DidCreateIsolate(Dart_Isolate isolate) {
Internals::Create(isolate, CreateServiceProvider(
Shell::Shared().service_provider_context()),
nullptr);
}
void UpdateTaskAndroid::RunDartOnUIThread() {
headless_->Init("sky:updater");
headless_->RunFromSnapshotBuffer(kUpdaterSnapshotBuffer,
kUpdaterSnapshotBufferSize);
// TODO(mpcomplete): Have Dart notify us when done so we can notify Java
// and be deleted. Or can Dart talk to Java directly?
}
void UpdateTaskAndroid::Finish() {
// The Java side is responsible for deleting us when finished.
Java_UpdateService_onUpdateFinished(base::android::AttachCurrentThread(),
update_service_.obj());
}
void UpdateTaskAndroid::Destroy(JNIEnv* env, jobject jcaller) {
Shell::Shared().ui_task_runner()->DeleteSoon(FROM_HERE, headless_.release());
delete this;
}
+5 -6
View File
@@ -9,21 +9,17 @@
#include "base/android/scoped_java_ref.h"
#include "base/memory/scoped_ptr.h"
namespace blink {
class SkyHeadless;
}
#include "sky/engine/public/sky/sky_headless.h"
namespace sky {
namespace shell {
class UpdateTaskAndroid {
class UpdateTaskAndroid : public blink::SkyHeadless::Client {
public:
UpdateTaskAndroid(JNIEnv* env, jobject update_service);
virtual ~UpdateTaskAndroid();
void Start();
void Finish();
// This C++ object is owned by the Java UpdateTask. This is called by
// UpdateTask when it is destroyed.
@@ -32,6 +28,9 @@ class UpdateTaskAndroid {
private:
void RunDartOnUIThread();
// SkyHeadless::Client:
void DidCreateIsolate(Dart_Isolate isolate) override;
scoped_ptr<blink::SkyHeadless> headless_;
base::android::ScopedJavaGlobalRef<jobject> update_service_;
};