mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
141 lines
4.8 KiB
Dart
141 lines
4.8 KiB
Dart
// Copyright 2017 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:convert';
|
|
import 'dart:io';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:image/image.dart';
|
|
import 'package:path/path.dart';
|
|
|
|
String authorizationToken;
|
|
|
|
class UploadError extends Error {
|
|
UploadError(this.message);
|
|
final String message;
|
|
@override
|
|
String toString() => 'UploadError($message)';
|
|
}
|
|
|
|
void logMessage(String s) { print(s); }
|
|
|
|
class Upload {
|
|
Upload(this.fromPath, this.largeName, this.smallName);
|
|
|
|
static math.Random random;
|
|
static const String uriAuthority = 'www.googleapis.com';
|
|
static const String uriPath = 'upload/storage/v1/b/flutter-catalog/o';
|
|
|
|
final String fromPath;
|
|
final String largeName;
|
|
final String smallName;
|
|
|
|
List<int> largeImage;
|
|
List<int> smallImage;
|
|
bool largeImageSaved = false;
|
|
int retryCount = 0;
|
|
bool isComplete = false;
|
|
|
|
// Exponential backoff per https://cloud.google.com/storage/docs/exponential-backoff
|
|
Duration get timeLimit {
|
|
if (retryCount == 0)
|
|
return const Duration(milliseconds: 1000);
|
|
random ??= math.Random();
|
|
return Duration(milliseconds: random.nextInt(1000) + math.pow(2, retryCount) * 1000);
|
|
}
|
|
|
|
Future<bool> save(HttpClient client, String name, List<int> content) async {
|
|
try {
|
|
final Uri uri = Uri.https(uriAuthority, uriPath, <String, String>{
|
|
'uploadType': 'media',
|
|
'name': name,
|
|
});
|
|
final HttpClientRequest request = await client.postUrl(uri);
|
|
request
|
|
..headers.contentType = ContentType('image', 'png')
|
|
..headers.add('Authorization', 'Bearer $authorizationToken')
|
|
..add(content);
|
|
|
|
final HttpClientResponse response = await request.close().timeout(timeLimit);
|
|
if (response.statusCode == HttpStatus.ok) {
|
|
logMessage('Saved $name');
|
|
await response.drain<void>();
|
|
} else {
|
|
// TODO(hansmuller): only retry on 5xx and 429 responses
|
|
logMessage('Request to save "$name" (length ${content.length}) failed with status ${response.statusCode}, will retry');
|
|
logMessage(await response.transform<String>(utf8.decoder).join());
|
|
}
|
|
return response.statusCode == HttpStatus.ok;
|
|
} on TimeoutException catch (_) {
|
|
logMessage('Request to save "$name" (length ${content.length}) timed out, will retry');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Future<bool> run(HttpClient client) async {
|
|
assert(!isComplete);
|
|
if (retryCount > 2)
|
|
throw UploadError('upload of "$fromPath" to "$largeName" and "$smallName" failed after 2 retries');
|
|
|
|
largeImage ??= await File(fromPath).readAsBytes();
|
|
smallImage ??= encodePng(copyResize(decodePng(largeImage), width: 300));
|
|
|
|
if (!largeImageSaved)
|
|
largeImageSaved = await save(client, largeName, largeImage);
|
|
isComplete = largeImageSaved && await save(client, smallName, smallImage);
|
|
|
|
retryCount += 1;
|
|
return isComplete;
|
|
}
|
|
|
|
static bool isNotComplete(Upload upload) => !upload.isComplete;
|
|
}
|
|
|
|
Future<void> saveScreenshots(List<String> fromPaths, List<String> largeNames, List<String> smallNames) async {
|
|
assert(fromPaths.length == largeNames.length);
|
|
assert(fromPaths.length == smallNames.length);
|
|
|
|
List<Upload> uploads = List<Upload>(fromPaths.length);
|
|
for (int index = 0; index < uploads.length; index += 1)
|
|
uploads[index] = Upload(fromPaths[index], largeNames[index], smallNames[index]);
|
|
|
|
while (uploads.any(Upload.isNotComplete)) {
|
|
final HttpClient client = HttpClient();
|
|
uploads = uploads.where(Upload.isNotComplete).toList();
|
|
await Future.wait<bool>(uploads.map<Future<bool>>((Upload upload) => upload.run(client)));
|
|
client.close(force: true);
|
|
}
|
|
}
|
|
|
|
|
|
// If path is lib/foo.png then screenshotName is foo.
|
|
String screenshotName(String path) => basenameWithoutExtension(path);
|
|
|
|
Future<void> saveCatalogScreenshots({
|
|
Directory directory, // Where the *.png screenshots are.
|
|
String commit, // The commit hash to be used as a cloud storage "directory".
|
|
String token, // Cloud storage authorization token.
|
|
String prefix, // Prefix for all file names.
|
|
}) async {
|
|
final List<String> screenshots = <String>[];
|
|
for (FileSystemEntity entity in directory.listSync()) {
|
|
if (entity is File && entity.path.endsWith('.png')) {
|
|
final File file = entity;
|
|
screenshots.add(file.path);
|
|
}
|
|
}
|
|
|
|
final List<String> largeNames = <String>[]; // Cloud storage names for the full res screenshots.
|
|
final List<String> smallNames = <String>[]; // Likewise for the scaled down screenshots.
|
|
for (String path in screenshots) {
|
|
final String name = screenshotName(path);
|
|
largeNames.add('$commit/$prefix$name.png');
|
|
smallNames.add('$commit/$prefix${name}_small.png');
|
|
}
|
|
|
|
authorizationToken = token;
|
|
await saveScreenshots(screenshots, largeNames, smallNames);
|
|
}
|