11import 'dart:convert' ;
22import 'dart:io' show gzip;
33
4+ import 'package:logging/logging.dart' ;
45import 'package:path/path.dart' as p;
56
67import 'package:pub_dev/dartdoc/dartdoc_page.dart' ;
@@ -42,6 +43,8 @@ const _safeMimeTypes = {
4243 // should not add it here.
4344};
4445
46+ final _logger = Logger ('task.handlers' );
47+
4548Future <shelf.Response > handleDartDoc (
4649 shelf.Request request,
4750 String package,
@@ -80,26 +83,75 @@ Future<shelf.Response> handleDartDoc(
8083 // Handle HTML requests
8184 final isHtml = ext == 'html' ;
8285 if (isHtml) {
83- final htmlBytes = await cache
84- .dartdocHtmlBytes (package, resolvedDocUrlVersion.urlSegment, path)
85- .get (() async {
86+ // try cached bytes first
87+ final htmlBytesCacheEntry =
88+ cache.dartdocHtmlBytes (package, resolvedDocUrlVersion.urlSegment, path);
89+ final htmlBytes = await htmlBytesCacheEntry.get ();
90+ if (htmlBytes != null ) {
91+ return htmlBytesResponse (htmlBytes);
92+ }
93+
94+ // check cached status for redirect or missing pages
95+ final statusCacheEntry = cache.dartdocPageStatus (
96+ package, resolvedDocUrlVersion.urlSegment, path);
97+ final cachedStatus = await statusCacheEntry.get ();
98+ final cachedStatusCode = cachedStatus? .code;
99+
100+ shelf.Response redirectPathResponse (String redirectPath) {
101+ final newPath = p.normalize (p.joinAll ([
102+ p.dirname (path),
103+ redirectPath,
104+ if (! redirectPath.endsWith ('.html' )) 'index.html' ,
105+ ]));
106+ return redirectResponse (pkgDocUrl (
107+ package,
108+ version: resolvedDocUrlVersion.urlSegment,
109+ relativePath: newPath,
110+ ));
111+ }
112+
113+ if (cachedStatusCode == DocPageStatusCode .redirect) {
114+ final redirectPath = cachedStatus! .redirectPath;
115+ if (redirectPath == null ) {
116+ _logger.shout ('DocPageStatus redirect without path.' );
117+ return notFoundHandler (request);
118+ }
119+ return redirectPathResponse (redirectPath);
120+ }
121+
122+ if (cachedStatusCode == DocPageStatusCode .missing) {
123+ return notFoundHandler (request,
124+ body: cachedStatus! .errorMessage ?? 'Documentation page not found.' );
125+ }
126+
127+ // try loading bytes;
128+ final (status, bytes) = await () async {
129+ final dataGz = await taskBackend.dartdocFile (package, version, path);
130+ if (dataGz == null ) {
131+ return (
132+ DocPageStatus (
133+ code: DocPageStatusCode .missing,
134+ errorMessage: await _missingPageErrorMessage (
135+ package,
136+ version,
137+ isLatestStable: resolvedDocUrlVersion.isLatestStable,
138+ ),
139+ ),
140+ null , // bytes
141+ );
142+ }
143+
86144 try {
87- final dataGz = await taskBackend.dartdocFile (package, version, path);
88- if (dataGz == null ) {
89- return const < int > []; // store empty string for missing data
90- }
91145 final dataJson = gzippedUtf8JsonCodec.decode (dataGz);
92146 if (path.endsWith ('-sidebar.html' )) {
93147 final sidebar =
94148 DartDocSidebar .fromJson (dataJson as Map <String , dynamic >);
95- return utf8.encode (sidebar.content);
149+ return ( DocPageStatus . ok (), utf8.encode (sidebar.content) );
96150 }
97- var page = DartDocPage .fromJson (dataJson as Map <String , dynamic >);
98151
99- // NOTE: If the loaded page is redirecting, we try to load it and render
100- // the page it is pointing to. Instead, we should redirect the page
101- // to the new URL.
102- // TODO: restructure cache logic and implement proper redirect
152+ final page = DartDocPage .fromJson (dataJson as Map <String , dynamic >);
153+
154+ // check for redirect page
103155 final redirectPath = page.redirectPath;
104156 if (page.isEmpty () &&
105157 redirectPath != null &&
@@ -109,11 +161,14 @@ Future<shelf.Response> handleDartDoc(
109161 redirectPath,
110162 if (! redirectPath.endsWith ('.html' )) 'index.html' ,
111163 ]));
112- final newDataGz =
113- await taskBackend.dartdocFile (package, version, newPath);
114- if (newDataGz != null ) {
115- final newDataJson = gzippedUtf8JsonCodec.decode (newDataGz);
116- page = DartDocPage .fromJson (newDataJson as Map <String , dynamic >);
164+ if (await taskBackend.hasDartdocTaskResult (
165+ package, version, newPath)) {
166+ return (DocPageStatus .redirect (redirectPath), null );
167+ } else {
168+ return (
169+ DocPageStatus .missing ('Dartdoc redirect is missing.' ),
170+ null , // bytes
171+ );
117172 }
118173 }
119174
@@ -125,42 +180,25 @@ Future<shelf.Response> handleDartDoc(
125180 path: path,
126181 searchQueryParameter: searchQueryParameter,
127182 ));
128- return utf8.encode (html.toString ());
129- } on FormatException {
130- // store empty string for invalid data, we treat it as a bug in
131- // the documentation generation.
132- return const < int > [];
133- }
134- });
135- // We use empty string to indicate missing file or bug in the file
136- if (htmlBytes == null || htmlBytes.isEmpty) {
137- final status = await taskBackend.packageStatus (package);
138- final vs = status.versions[version];
139- if (vs == null ) {
140- return notFoundHandler (
141- request,
142- body: resolvedDocUrlVersion.isLatestStable
143- ? 'Analysis has not started yet.'
144- : 'Version not selected for analysis.' ,
145- );
183+ return (DocPageStatus .ok (), utf8.encode (html.toString ()));
184+ } on FormatException catch (e, st) {
185+ _logger.shout (
186+ 'Unable to parse dartdoc page for $package $version ' , e, st);
187+ return (DocPageStatus .missing ('Unable to render page.' ), null );
146188 }
147- String ? message;
148- switch (vs.status) {
149- case PackageVersionStatus .pending:
150- case PackageVersionStatus .running:
151- message = 'Analysis has not finished yet.' ;
152- break ;
153- case PackageVersionStatus .failed:
154- message =
155- 'Analysis has failed, no `dartdoc` output has been generated.' ;
156- break ;
157- case PackageVersionStatus .completed:
158- message = '`dartdoc` did not generate this page.' ;
159- break ;
160- }
161- return notFoundHandler (request, body: message);
189+ }();
190+ await statusCacheEntry.set (status);
191+
192+ switch (status.code) {
193+ case DocPageStatusCode .ok:
194+ await htmlBytesCacheEntry.set (bytes! );
195+ return htmlBytesResponse (bytes);
196+ case DocPageStatusCode .redirect:
197+ return redirectPathResponse (status.redirectPath! );
198+ case DocPageStatusCode .missing:
199+ return notFoundHandler (request,
200+ body: status.errorMessage ?? 'Documentation page not found.' );
162201 }
163- return htmlBytesResponse (htmlBytes);
164202 }
165203
166204 // Handle any non-HTML request
@@ -191,6 +229,34 @@ Future<shelf.Response> handleDartDoc(
191229 );
192230}
193231
232+ Future <String > _missingPageErrorMessage (
233+ String package,
234+ String version, {
235+ required bool isLatestStable,
236+ }) async {
237+ final status = await taskBackend.packageStatus (package);
238+ final vs = status.versions[version];
239+ if (vs == null ) {
240+ return isLatestStable
241+ ? 'Analysis has not started yet.'
242+ : 'Version not selected for analysis.' ;
243+ }
244+ String ? message;
245+ switch (vs.status) {
246+ case PackageVersionStatus .pending:
247+ case PackageVersionStatus .running:
248+ message = 'Analysis has not finished yet.' ;
249+ break ;
250+ case PackageVersionStatus .failed:
251+ message = 'Analysis has failed, no `dartdoc` output has been generated.' ;
252+ break ;
253+ case PackageVersionStatus .completed:
254+ message = '`dartdoc` did not generate this page.' ;
255+ break ;
256+ }
257+ return message;
258+ }
259+
194260/// Handles GET `/packages/<package>/versions/<version>/gen-res/<path|[^]*>`
195261Future <shelf.Response > handleTaskResource (
196262 shelf.Request request,
0 commit comments