github.com/minio/console@v1.4.1/web-app/src/screens/Console/ObjectBrowser/objectBrowserThunks.ts (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2022 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import { createAsyncThunk } from "@reduxjs/toolkit"; 18 import { AppState } from "../../../store"; 19 import { encodeURLString, getClientOS } from "../../../common/utils"; 20 import { BucketObjectItem } from "../Buckets/ListBuckets/Objects/ListObjects/types"; 21 import { makeid, storeCallForObjectWithID } from "./transferManager"; 22 import { 23 download, 24 downloadSelectedAsZip, 25 } from "../Buckets/ListBuckets/Objects/utils"; 26 import { 27 cancelObjectInList, 28 completeObject, 29 failObject, 30 setAnonymousAccessOpen, 31 setDownloadRenameModal, 32 setMaxShareLinkExpTime, 33 setNewObject, 34 setPreviewOpen, 35 setSelectedPreview, 36 setShareFileModalOpen, 37 updateProgress, 38 } from "./objectBrowserSlice"; 39 import { setSnackBarMessage } from "../../../systemSlice"; 40 import { DateTime } from "luxon"; 41 import { api } from "api"; 42 43 export const downloadSelected = createAsyncThunk( 44 "objectBrowser/downloadSelected", 45 async (bucketName: string, { getState, rejectWithValue, dispatch }) => { 46 const state = getState() as AppState; 47 48 const downloadObject = (object: BucketObjectItem) => { 49 const identityDownload = encodeURLString( 50 `${bucketName}-${object.name}-${new Date().getTime()}-${Math.random()}`, 51 ); 52 53 const ID = makeid(8); 54 55 const downloadCall = download( 56 bucketName, 57 encodeURLString(object.name), 58 object.version_id, 59 object.size, 60 null, 61 ID, 62 (progress) => { 63 dispatch( 64 updateProgress({ 65 instanceID: identityDownload, 66 progress: progress, 67 }), 68 ); 69 }, 70 () => { 71 dispatch(completeObject(identityDownload)); 72 }, 73 (msg: string) => { 74 dispatch(failObject({ instanceID: identityDownload, msg })); 75 }, 76 () => { 77 dispatch(cancelObjectInList(identityDownload)); 78 }, 79 () => { 80 dispatch( 81 setSnackBarMessage( 82 "File download will be handled directly by the browser.", 83 ), 84 ); 85 }, 86 ); 87 storeCallForObjectWithID(ID, downloadCall); 88 dispatch( 89 setNewObject({ 90 ID, 91 bucketName, 92 done: false, 93 instanceID: identityDownload, 94 percentage: 0, 95 prefix: object.name, 96 type: "download", 97 waitingForFile: true, 98 failed: false, 99 cancelled: false, 100 errorMessage: "", 101 }), 102 ); 103 }; 104 105 if (state.objectBrowser.selectedObjects.length !== 0) { 106 let itemsToDownload: BucketObjectItem[] = []; 107 108 const filterFunction = (currValue: BucketObjectItem) => 109 state.objectBrowser.selectedObjects.includes(currValue.name); 110 111 itemsToDownload = state.objectBrowser.records.filter(filterFunction); 112 113 // In case just one element is selected, then we trigger download modal validation. 114 if (itemsToDownload.length === 1) { 115 if ( 116 itemsToDownload[0].name.length > 200 && 117 getClientOS().toLowerCase().includes("win") 118 ) { 119 dispatch(setDownloadRenameModal(itemsToDownload[0])); 120 return; 121 } else { 122 downloadObject(itemsToDownload[0]); 123 } 124 } else { 125 if (itemsToDownload.length === 1) { 126 downloadObject(itemsToDownload[0]); 127 } else if (itemsToDownload.length > 1) { 128 const fileName = `${DateTime.now().toFormat( 129 "LL-dd-yyyy-HH-mm-ss", 130 )}_files_list.zip`; 131 132 // We are enforcing zip download when multiple files are selected for better user experience 133 const multiObjList = itemsToDownload.reduce((dwList: any[], bi) => { 134 // Download objects/prefixes(recursively) as zip 135 // Skip any deleted files selected via "Show deleted objects" in selection and log for debugging 136 const isDeleted = bi?.delete_flag; 137 if (bi && !isDeleted) { 138 dwList.push(bi.name); 139 } else { 140 console.log(`Skipping ${bi?.name} from download.`); 141 } 142 return dwList; 143 }, []); 144 145 await downloadSelectedAsZip(bucketName, multiObjList, fileName); 146 return; 147 } 148 } 149 } 150 }, 151 ); 152 153 export const openPreview = createAsyncThunk( 154 "objectBrowser/openPreview", 155 async (_, { getState, rejectWithValue, dispatch }) => { 156 const state = getState() as AppState; 157 158 if (state.objectBrowser.selectedObjects.length === 1) { 159 let fileObject: BucketObjectItem | undefined; 160 161 const findFunction = (currValue: BucketObjectItem) => 162 state.objectBrowser.selectedObjects.includes(currValue.name); 163 164 fileObject = state.objectBrowser.records.find(findFunction); 165 166 if (fileObject) { 167 dispatch(setSelectedPreview(fileObject)); 168 dispatch(setPreviewOpen(true)); 169 } 170 } 171 }, 172 ); 173 174 export const openShare = createAsyncThunk( 175 "objectBrowser/openShare", 176 async (_, { getState, rejectWithValue, dispatch }) => { 177 const state = getState() as AppState; 178 179 if (state.objectBrowser.selectedObjects.length === 1) { 180 let fileObject: BucketObjectItem | undefined; 181 182 const findFunction = (currValue: BucketObjectItem) => 183 state.objectBrowser.selectedObjects.includes(currValue.name); 184 185 fileObject = state.objectBrowser.records.find(findFunction); 186 187 if (fileObject) { 188 dispatch(setSelectedPreview(fileObject)); 189 dispatch(setShareFileModalOpen(true)); 190 } 191 } 192 }, 193 ); 194 195 export const openAnonymousAccess = createAsyncThunk( 196 "objectBrowser/openAnonymousAccess", 197 async (_, { getState, dispatch }) => { 198 const state = getState() as AppState; 199 200 if ( 201 state.objectBrowser.selectedObjects.length === 1 && 202 state.objectBrowser.selectedObjects[0].endsWith("/") 203 ) { 204 dispatch(setAnonymousAccessOpen(true)); 205 } 206 }, 207 ); 208 209 export const getMaxShareLinkExpTime = createAsyncThunk( 210 "objectBrowser/maxShareLinkExpTime", 211 async (_, { rejectWithValue, dispatch }) => { 212 return api.buckets 213 .getMaxShareLinkExp() 214 .then((res) => { 215 dispatch(setMaxShareLinkExpTime(res.data.exp)); 216 }) 217 .catch(async (res) => { 218 return rejectWithValue(res.error); 219 }); 220 }, 221 );