main.dart 3.75 KB
Newer Older
1 2 3 4
// 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.

5 6 7 8
import 'dart:async';
import 'dart:io';

import 'package:mojo/core.dart';
9
import 'package:flutter/http.dart' as http;
10 11
import 'package:flutter/services.dart';
import 'package:flx/bundle.dart';
12 13 14 15 16
import 'package:sky_services/updater/update_service.mojom.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart' as yaml;

import 'pipe_to_file.dart';
17 18
import 'version.dart';

19 20
const String kManifestFile = 'flutter.yaml';
const String kBundleFile = 'app.flx';
21 22 23

UpdateServiceProxy _initUpdateService() {
  UpdateServiceProxy updateService = new UpdateServiceProxy.unbound();
24
  shell.connectToService(null, updateService);
25 26 27 28 29 30 31 32 33 34 35 36
  return updateService;
}

final UpdateServiceProxy _updateService = _initUpdateService();

String cachedDataDir = null;
Future<String> getDataDir() async {
  if (cachedDataDir == null)
    cachedDataDir = await getAppDataDir();
  return cachedDataDir;
}

37 38 39 40 41 42
class UpdateFailure extends Error {
  UpdateFailure(this._message);
  String _message;
  String toString() => _message;
}

43
class UpdateTask {
44
  UpdateTask();
45

46
  Future run() async {
47 48
    try {
      await _runImpl();
49 50 51
    } on UpdateFailure catch (e) {
      print('Update failed: $e');
    } catch (e, stackTrace) {
52
      print('Update failed: $e');
53
      print('Stack: $stackTrace');
54 55 56 57 58
    } finally {
      _updateService.ptr.notifyUpdateCheckComplete();
    }
  }

59
  Future _runImpl() async {
60 61 62 63 64 65 66 67
    _dataDir = await getDataDir();

    await _readLocalManifest();
    yaml.YamlMap remoteManifest = await _fetchManifest();
    if (!_shouldUpdate(remoteManifest)) {
      print('Update skipped. No new version.');
      return;
    }
68 69
    await _fetchBundle();
    await _validateBundle();
70 71 72 73
    await _replaceBundle();
    print('Update success.');
  }

74
  Map _currentManifest;
75 76 77
  String _dataDir;
  String _tempPath;

78 79 80 81
  Future _readLocalManifest() async {
    String bundlePath = path.join(_dataDir, kBundleFile);
    Bundle bundle = await Bundle.readHeader(bundlePath);
    _currentManifest = bundle.manifest;
82 83 84
  }

  Future<yaml.YamlMap> _fetchManifest() async {
85
    String manifestUrl = _currentManifest['update-url'] + '/' + kManifestFile;
86
    String manifestData = (await http.get(manifestUrl)).body;
87 88 89 90 91 92 93 94 95
    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);
  }

96
  Future _fetchBundle() async {
97 98
    // TODO(mpcomplete): Use the cache dir. We need an equivalent of mkstemp().
    _tempPath = path.join(_dataDir, 'tmp.skyx');
99
    String bundleUrl = _currentManifest['update-url'] + '/' + kBundleFile;
100
    UrlResponse response = await fetchUrl(bundleUrl);
Adam Barth's avatar
Adam Barth committed
101 102
    int result = await PipeToFile.copyToFile(response.body, _tempPath);
    if (result != MojoResult.kOk)
103 104 105 106 107 108 109 110 111 112
      throw new UpdateFailure('Failure fetching new package: ${response.statusLine}');
  }

  Future _validateBundle() async {
    Bundle bundle = await Bundle.readHeader(_tempPath);

    if (bundle == null)
      throw new UpdateFailure('Remote package not a valid FLX file.');
    if (bundle.manifest['key'] != _currentManifest['key'])
      throw new UpdateFailure('Remote package key does not match.');
113 114
    if (!await bundle.verifyContent())
      throw new UpdateFailure('Invalid package signature or hash. This package has been tampered with.');
115 116
  }

117
  Future _replaceBundle() async {
118 119 120
    String bundlePath = path.join(_dataDir, kBundleFile);
    await new File(_tempPath).rename(bundlePath);
  }
121 122 123
}

void main() {
124
  UpdateTask task = new UpdateTask();
125
  task.run();
126
}