Fix object download (#466)

If an object is within a folder the object downloaded now only has the object's name.
Also, it now supports object version downloading.
This commit is contained in:
Cesar N
2020-12-03 11:37:53 -06:00
committed by GitHub
parent 726bfe623c
commit d15472f417
9 changed files with 43 additions and 42 deletions

View File

@@ -153,12 +153,9 @@ const ListObjects = ({
setLastAsFile, setLastAsFile,
}: IListObjectsProps) => { }: IListObjectsProps) => {
const [records, setRecords] = useState<BucketObject[]>([]); const [records, setRecords] = useState<BucketObject[]>([]);
const [totalRecords, setTotalRecords] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const [deleteOpen, setDeleteOpen] = useState<boolean>(false); const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false); const [createFolderOpen, setCreateFolderOpen] = useState<boolean>(false);
const [deleteError, setDeleteError] = useState<string>("");
const [selectedObject, setSelectedObject] = useState<string>(""); const [selectedObject, setSelectedObject] = useState<string>("");
const [selectedBucket, setSelectedBucket] = useState<string>(""); const [selectedBucket, setSelectedBucket] = useState<string>("");
const [filterObjects, setFilterObjects] = useState<string>(""); const [filterObjects, setFilterObjects] = useState<string>("");
@@ -179,8 +176,6 @@ const ListObjects = ({
.then((res: BucketObjectsList) => { .then((res: BucketObjectsList) => {
setSelectedBucket(bucketName); setSelectedBucket(bucketName);
setRecords(res.objects || []); setRecords(res.objects || []);
setTotalRecords(!res.objects ? 0 : res.total);
setError("");
// In case no objects were retrieved, We check if item is a file // In case no objects were retrieved, We check if item is a file
if (!res.objects && extraPath !== "") { if (!res.objects && extraPath !== "") {
verifyIfIsFile(); verifyIfIsFile();
@@ -190,7 +185,6 @@ const ListObjects = ({
}) })
.catch((err: any) => { .catch((err: any) => {
setLoading(false); setLoading(false);
setError(err);
}); });
}, [loading, match]); }, [loading, match]);
@@ -221,7 +215,6 @@ const ListObjects = ({
}) })
.catch((err: any) => { .catch((err: any) => {
setLoading(false); setLoading(false);
setError(err);
}); });
}; };
@@ -263,7 +256,7 @@ const ListObjects = ({
xhr.open("POST", uploadUrl, true); xhr.open("POST", uploadUrl, true);
xhr.withCredentials = false; xhr.withCredentials = false;
xhr.onload = function (event) { xhr.onload = function(event) {
// TODO: handle status // TODO: handle status
if (xhr.status === 401 || xhr.status === 403) { if (xhr.status === 401 || xhr.status === 403) {
showSnackBarMessage("An error occurred while uploading the file."); showSnackBarMessage("An error occurred while uploading the file.");
@@ -307,8 +300,8 @@ const ListObjects = ({
setSelectedObject(object); setSelectedObject(object);
}; };
const downloadObject = (object: string) => { const downloadObject = (object: BucketObject) => {
download(selectedBucket, object); download(selectedBucket, object.name, object.version_id);
}; };
const openPath = (idElement: string) => { const openPath = (idElement: string) => {
@@ -366,7 +359,7 @@ const ListObjects = ({
const tableActions = [ const tableActions = [
{ type: "view", onClick: openPath, sendOnlyId: true }, { type: "view", onClick: openPath, sendOnlyId: true },
{ type: "download", onClick: downloadObject, sendOnlyId: true }, { type: "download", onClick: downloadObject },
{ type: "delete", onClick: confirmDeleteObject, sendOnlyId: true }, { type: "delete", onClick: confirmDeleteObject, sendOnlyId: true },
]; ];

View File

@@ -19,6 +19,7 @@ export interface BucketObject {
size: number; size: number;
last_modified: Date; last_modified: Date;
content_type: string; content_type: string;
version_id: string;
} }
export interface BucketObjectsList { export interface BucketObjectsList {

View File

@@ -223,13 +223,13 @@ const ObjectDetails = ({
setDeleteTagModalOpen(true); setDeleteTagModalOpen(true);
}; };
const downloadObject = (path: string) => { const downloadObject = (object: IFileInfo) => {
download(bucketName, path); download(bucketName, pathInBucket, object.version_id);
}; };
const tableActions = [ const tableActions = [
{ type: "share", onClick: shareObject, sendOnlyId: true }, { type: "share", onClick: shareObject, sendOnlyId: true },
{ type: "download", onClick: downloadObject, sendOnlyId: true }, { type: "download", onClick: downloadObject },
]; ];
const filteredRecords = versions.filter((version) => const filteredRecords = versions.filter((version) =>
@@ -408,7 +408,7 @@ const ObjectDetails = ({
size="small" size="small"
className={classes.actionsIcon} className={classes.actionsIcon}
onClick={() => { onClick={() => {
downloadObject(pathInBucket); downloadObject(actualInfo);
}} }}
> >
<DownloadIcon /> <DownloadIcon />

View File

@@ -14,22 +14,28 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
import storage from "local-storage-fallback"; import { isNullOrUndefined } from "util";
export const download = (bucketName: string, objectName: string) => { export const download = (
bucketName: string,
objectPath: string,
versionID: any
) => {
const anchor = document.createElement("a"); const anchor = document.createElement("a");
document.body.appendChild(anchor); document.body.appendChild(anchor);
const token: string = storage.getItem("token")!;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const allPathData = objectPath.split("/");
const objectName = allPathData[allPathData.length - 1];
xhr.open( let path = `/api/v1/buckets/${bucketName}/objects/download?prefix=${objectPath}`;
"GET", if (!isNullOrUndefined(versionID) && versionID !== "null") {
`/api/v1/buckets/${bucketName}/objects/download?prefix=${objectName}`, path = path.concat(`&version_id=${versionID}`);
true }
);
xhr.open("GET", path, true);
xhr.responseType = "blob"; xhr.responseType = "blob";
xhr.onload = function (e) { xhr.onload = function(e) {
if (this.status === 200) { if (this.status === 200) {
const blob = new Blob([this.response], { const blob = new Blob([this.response], {
type: "octet/stream", type: "octet/stream",

View File

@@ -550,8 +550,7 @@ func init() {
{ {
"type": "string", "type": "string",
"name": "version_id", "name": "version_id",
"in": "query", "in": "query"
"required": true
} }
], ],
"responses": { "responses": {
@@ -5510,8 +5509,7 @@ func init() {
{ {
"type": "string", "type": "string",
"name": "version_id", "name": "version_id",
"in": "query", "in": "query"
"required": true
} }
], ],
"responses": { "responses": {

View File

@@ -59,10 +59,9 @@ type DownloadObjectParams struct {
*/ */
Prefix string Prefix string
/* /*
Required: true
In: query In: query
*/ */
VersionID string VersionID *string
} }
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -135,21 +134,18 @@ func (o *DownloadObjectParams) bindPrefix(rawData []string, hasKey bool, formats
// bindVersionID binds and validates parameter VersionID from query. // bindVersionID binds and validates parameter VersionID from query.
func (o *DownloadObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error { func (o *DownloadObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
if !hasKey {
return errors.Required("version_id", "query", rawData)
}
var raw string var raw string
if len(rawData) > 0 { if len(rawData) > 0 {
raw = rawData[len(rawData)-1] raw = rawData[len(rawData)-1]
} }
// Required: true // Required: false
// AllowEmptyValue: false // AllowEmptyValue: false
if err := validate.RequiredString("version_id", "query", raw); err != nil { if raw == "" { // empty values pass all other validations
return err return nil
} }
o.VersionID = raw o.VersionID = &raw
return nil return nil
} }

View File

@@ -34,7 +34,7 @@ type DownloadObjectURL struct {
BucketName string BucketName string
Prefix string Prefix string
VersionID string VersionID *string
_basePath string _basePath string
// avoid unkeyed usage // avoid unkeyed usage
@@ -82,7 +82,10 @@ func (o *DownloadObjectURL) Build() (*url.URL, error) {
qs.Set("prefix", prefixQ) qs.Set("prefix", prefixQ)
} }
versionIDQ := o.VersionID var versionIDQ string
if o.VersionID != nil {
versionIDQ = *o.VersionID
}
if versionIDQ != "" { if versionIDQ != "" {
qs.Set("version_id", versionIDQ) qs.Set("version_id", versionIDQ)
} }

View File

@@ -228,10 +228,14 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo
return object, nil return object, nil
} }
func downloadObject(ctx context.Context, client MCClient, versionID string) (io.ReadCloser, error) { func downloadObject(ctx context.Context, client MCClient, versionID *string) (io.ReadCloser, error) {
// TODO: handle encripted files // TODO: handle encripted files
var reader io.ReadCloser var reader io.ReadCloser
reader, pErr := client.get(ctx, mc.GetOptions{VersionID: versionID}) var version string
if versionID != nil {
version = *versionID
}
reader, pErr := client.get(ctx, mc.GetOptions{VersionID: version})
if pErr != nil { if pErr != nil {
return nil, pErr.Cause return nil, pErr.Cause
} }

View File

@@ -330,7 +330,7 @@ paths:
type: string type: string
- name: version_id - name: version_id
in: query in: query
required: true required: false
type: string type: string
responses: responses:
200: 200: