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:
@@ -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 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user