1
1
import * as zip from '@zip.js/zip.js' ;
2
2
import FileSaver from 'file-saver' ;
3
3
4
- import { StrictDict } from 'utils' ;
5
4
import { RequestKeys } from 'data/constants/requests' ;
6
5
import { selectors } from 'data/redux' ;
6
+ import { locationId } from 'data/constants/app' ;
7
+ import { stringifyUrl } from 'data/services/lms/utils' ;
7
8
8
9
import { networkRequest } from './requests' ;
9
10
import * as module from './download' ;
10
11
11
- export const ERRORS = StrictDict ( {
12
- fetchFailed : 'Fetch failed' ,
12
+ export const DownloadException = ( files ) => ( {
13
+ files,
14
+ name : 'DownloadException' ,
13
15
} ) ;
14
16
15
17
/**
@@ -21,22 +23,13 @@ export const genManifest = (files) => files.map(
21
23
( file ) => `Filename: ${ file . name } \nDescription: ${ file . description } \nSize: ${ file . size } ` ,
22
24
) . join ( '\n\n' ) ;
23
25
24
- /**
25
- * Returns the zip filename
26
- * @return {string } - zip download file name
27
- */
28
- export const zipFileName = ( ) => {
29
- const currentDate = new Date ( ) . getTime ( ) ;
30
- return `ora-files-download-${ currentDate } .zip` ;
31
- } ;
32
-
33
26
/**
34
27
* Zip the blob output of a set of files with a manifest file.
35
28
* @param {obj[] } files - list of file entries with downloadUrl, name, and description
36
29
* @param {blob[] } blobs - file content blobs
37
30
* @return {Promise } - zip async process promise.
38
31
*/
39
- export const zipFiles = async ( files , blobs ) => {
32
+ export const zipFiles = async ( files , blobs , username ) => {
40
33
const zipWriter = new zip . ZipWriter ( new zip . BlobWriter ( 'application/zip' ) ) ;
41
34
await zipWriter . add ( 'manifest.txt' , new zip . TextReader ( module . genManifest ( files ) ) ) ;
42
35
@@ -50,39 +43,71 @@ export const zipFiles = async (files, blobs) => {
50
43
}
51
44
52
45
const zipFile = await zipWriter . close ( ) ;
53
- FileSaver . saveAs ( zipFile , module . zipFileName ( ) ) ;
46
+ const zipName = `${ username } -${ locationId } .zip` ;
47
+ FileSaver . saveAs ( zipFile , zipName ) ;
54
48
} ;
55
49
50
+ /**
51
+ * generate url with additional timestamp for cache busting.
52
+ * This is implemented for fixing issue with the browser not
53
+ * allowing the user to fetch the same url as the image tag.
54
+ * @param {string } url
55
+ * @returns {string }
56
+ */
57
+ export const getTimeStampUrl = ( url ) => stringifyUrl ( url , {
58
+ ora_grading_download_timestamp : new Date ( ) . getTime ( ) ,
59
+ } ) ;
60
+
56
61
/**
57
62
* Download a file and return its blob is successful, or null if not.
58
63
* @param {obj } file - file entry with downloadUrl
59
- * @return {blob } - file blob or null
64
+ * @return {Promise } - file blob or null
60
65
*/
61
- export const downloadFile = ( file ) => fetch ( file . downloadUrl ) . then ( resp => (
62
- resp . ok ? resp . blob ( ) : null
63
- ) ) ;
66
+ export const downloadFile = ( file ) => fetch (
67
+ module . getTimeStampUrl ( file . downloadUrl ) ,
68
+ ) . then ( ( response ) => {
69
+ if ( ! response . ok ) {
70
+ // This is necessary because some of the error such as 404 does not throw.
71
+ // Due to that inconsistency, I have decide to share catch statement like this.
72
+ throw new Error ( response . statusText ) ;
73
+ }
74
+ return response . blob ( ) ;
75
+ } ) ;
64
76
65
77
/**
66
78
* Download blobs given file objects. Returns a promise map.
67
79
* @param {obj[] } files - list of file entries with downloadUrl, name, and description
68
80
* @return {Promise[] } - Promise map of download attempts (null for failed fetches)
69
81
*/
70
- export const downloadBlobs = ( files ) => Promise . all ( files . map ( module . downloadFile ) ) ;
82
+ export const downloadBlobs = async ( files ) => {
83
+ const blobs = [ ] ;
84
+ const errors = [ ] ;
85
+
86
+ // eslint-disable-next-line no-restricted-syntax
87
+ for ( const file of files ) {
88
+ try {
89
+ // eslint-disable-next-line no-await-in-loop
90
+ blobs . push ( await module . downloadFile ( file ) ) ;
91
+ } catch ( error ) {
92
+ errors . push ( file . name ) ;
93
+ }
94
+ }
95
+ if ( errors . length ) {
96
+ throw DownloadException ( errors ) ;
97
+ }
98
+ return blobs ;
99
+ } ;
71
100
72
101
/**
73
102
* Download all files for the selected submission as a zip file.
74
103
* Throw error and do not download zip if any of the files fail to fetch.
75
104
*/
76
105
export const downloadFiles = ( ) => ( dispatch , getState ) => {
77
106
const { files } = selectors . grading . selected . response ( getState ( ) ) ;
107
+ const username = selectors . grading . selected . username ( getState ( ) ) ;
78
108
dispatch ( networkRequest ( {
79
109
requestKey : RequestKeys . downloadFiles ,
80
- promise : module . downloadBlobs ( files ) . then ( blobs => {
81
- if ( blobs . some ( blob => blob === null ) ) {
82
- throw Error ( ERRORS . fetchFailed ) ;
83
- }
84
- return module . zipFiles ( files , blobs ) ;
85
- } ) ,
110
+ promise : module . downloadBlobs ( files ) . then ( blobs => module . zipFiles ( files , blobs , username ) ) ,
86
111
} ) ) ;
87
112
} ;
88
113
0 commit comments