22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5- import 'dart:io' ;
6-
75import 'package:basics/basics.dart' ;
86import 'package:clock/clock.dart' ;
9- import 'package:crypto/crypto.dart' ;
107import 'package:gcloud/service_scope.dart' as ss;
118import 'package:gcloud/storage.dart' ;
129import 'package:logging/logging.dart' ;
@@ -17,9 +14,9 @@ import 'package:retry/retry.dart';
1714import '../search/backend.dart' ;
1815import '../shared/datastore.dart' ;
1916import '../shared/storage.dart' ;
20- import '../shared/utils.dart' ;
2117import '../shared/versions.dart' ;
2218import '../task/global_lock.dart' ;
19+ import 'api_export/exported_api.dart' ;
2320import 'backend.dart' ;
2421import 'models.dart' ;
2522
@@ -28,20 +25,6 @@ final Logger _logger = Logger('export_api_to_bucket');
2825/// The default concurrency to upload API JSON files to the bucket.
2926const _defaultBucketUpdateConcurrency = 8 ;
3027
31- /// The default cache timeout for content.
32- const _pkgApiMaxCacheAge = Duration (minutes: 10 );
33- const _pkgNameCompletionDataMaxAge = Duration (hours: 8 );
34-
35- List <String > _apiPkgObjectNames (String package) => [
36- '$runtimeVersion /api/packages/$package ' ,
37- 'current/api/packages/$package ' ,
38- ];
39-
40- List <String > _apiPkgNameCompletionDataNames () => [
41- '$runtimeVersion /api/package-name-completion-data' ,
42- 'current/api/package-name-completion-data' ,
43- ];
44-
4528/// Sets the API Exporter service.
4629void registerApiExporter (ApiExporter value) =>
4730 ss.register (#_apiExporter, value);
@@ -50,14 +33,16 @@ void registerApiExporter(ApiExporter value) =>
5033ApiExporter ? get apiExporter => ss.lookup (#_apiExporter) as ApiExporter ? ;
5134
5235class ApiExporter {
36+ final ExportedApi _api;
5337 final Bucket _bucket;
5438 final int _concurrency;
5539 final _pkgLastUpdated = < String , _PkgUpdatedEvent > {};
5640
5741 ApiExporter ({
5842 required Bucket bucket,
5943 int concurrency = _defaultBucketUpdateConcurrency,
60- }) : _bucket = bucket,
44+ }) : _api = ExportedApi (storageService, bucket),
45+ _bucket = bucket,
6146 _concurrency = concurrency;
6247
6348 /// Runs a forever loop and tries to get a global lock.
@@ -89,12 +74,8 @@ class ApiExporter {
8974
9075 /// Gets and uploads the package name completion data.
9176 Future <void > uploadPkgNameCompletionData () async {
92- final bytes = await searchBackend.getPackageNameCompletionDataJsonGz ();
93- final bytesAndHash = _BytesAndHash (bytes);
94- for (final objectName in _apiPkgNameCompletionDataNames ()) {
95- await _upsert (objectName, bytesAndHash,
96- maxAge: _pkgNameCompletionDataMaxAge);
97- }
77+ await _api.packageNameCompletionData
78+ .write (await searchBackend.getPackageNameCompletionData ());
9879 }
9980
10081 /// Note: there is no global locking here, the full scan should be called
@@ -187,19 +168,11 @@ class ApiExporter {
187168 /// the endpoint name in the file location.
188169 Future <void > _uploadPackageToBucket (String package) async {
189170 final data = await retry (() => packageBackend.listVersions (package));
190- final rawBytes = jsonUtf8Encoder.convert (data.toJson ());
191- final bytes = gzip.encode (rawBytes);
192- final bytesAndHash = _BytesAndHash (bytes);
193-
194- for (final objectName in _apiPkgObjectNames (package)) {
195- await _upsert (objectName, bytesAndHash, maxAge: _pkgApiMaxCacheAge);
196- }
171+ await _api.package (package).versions.write (data);
197172 }
198173
199174 Future <void > _deletePackageFromBucket (String package) async {
200- for (final objectName in _apiPkgObjectNames (package)) {
201- await _bucket.tryDelete (objectName);
202- }
175+ await _api.package (package).delete ();
203176 }
204177
205178 Stream <_PkgUpdatedEvent > _queryRecentPkgUpdatedEvents (DateTime since) async * {
@@ -250,39 +223,6 @@ class ApiExporter {
250223 await deleteBucketFolderRecursively (_bucket, '$v /' , concurrency: 4 );
251224 }
252225 }
253-
254- Future <void > _upsert (
255- String objectName,
256- _BytesAndHash bytesAndHash, {
257- required Duration maxAge,
258- }) async {
259- if (await _isSameContent (objectName, bytesAndHash)) {
260- return ;
261- }
262- await uploadWithRetry (
263- _bucket,
264- objectName,
265- bytesAndHash.bytes.length,
266- () => Stream .value (bytesAndHash.bytes),
267- metadata: ObjectMetadata (
268- contentType: 'application/json; charset="utf-8"' ,
269- contentEncoding: 'gzip' ,
270- cacheControl: 'public, max-age=${maxAge .inSeconds }' ,
271- ),
272- );
273- }
274-
275- Future <bool > _isSameContent (
276- String objectName, _BytesAndHash bytesAndHash) async {
277- final info = await _bucket.tryInfo (objectName);
278- if (info == null ) {
279- return false ;
280- }
281- if (info.length != bytesAndHash.length) {
282- return false ;
283- }
284- return fixedTimeIntListEquals (info.md5Hash, bytesAndHash.md5Hash);
285- }
286226}
287227
288228typedef _PkgUpdatedEvent = ({String package, DateTime updated, bool isVisible});
@@ -296,12 +236,3 @@ extension on Package {
296236 _PkgUpdatedEvent asPkgUpdatedEvent () =>
297237 (package: name! , updated: updated! , isVisible: isVisible);
298238}
299-
300- class _BytesAndHash {
301- final List <int > bytes;
302-
303- _BytesAndHash (this .bytes);
304-
305- int get length => bytes.length;
306- late final md5Hash = md5.convert (bytes).bytes;
307- }
0 commit comments