Delete fling, fix race conditions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
41f2a22f3d
commit
16b3318f92
11 changed files with 109 additions and 54 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
# Authenticate as user
|
# Authenticate as user
|
||||||
POST http://localhost:8080/api/auth/user
|
POST http://localhost:8080/api/auth/user
|
||||||
|
Content-Type: application/json
|
||||||
{"shareId": "shareId", "authCode":"secret"}
|
{"shareId": "shareId", "authCode":"secret"}
|
||||||
-> jq-set-var :token .
|
-> jq-set-var :token .
|
||||||
|
|
||||||
|
@ -15,9 +16,30 @@ Content-Type: application/json
|
||||||
{"adminName": "admin", "adminPassword":"123"}
|
{"adminName": "admin", "adminPassword":"123"}
|
||||||
-> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position)))
|
-> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position)))
|
||||||
|
|
||||||
|
:token = Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUxNzY4OTksImV4cCI6MTU5NTM1Njg5OSwic3ViIjoiYWRtaW4ifQ.uRh_xBCrBiLQEBah9I8bYWM-Zph-V_pzQVdaGSU5Mlc
|
||||||
|
|
||||||
# Get all flings
|
# Get all flings
|
||||||
GET http://localhost:8080/api/fling
|
GET http://localhost:8080/api/fling
|
||||||
Authorization: Bearer :token
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
|
||||||
|
# Add a new fling
|
||||||
|
POST http://localhost:8080/api/fling
|
||||||
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
{"name": "Unshared Fling from querysheet", "expirationClicks": 12, "shared": false}
|
||||||
|
|
||||||
|
# Add a new fling
|
||||||
|
POST http://localhost:8080/api/fling
|
||||||
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
{"name": "Fling from querysheet with Auth", "expirationClicks": 12, "shared": true, "authCode": "abc"}
|
||||||
|
|
||||||
|
# Add a new fling
|
||||||
|
POST http://localhost:8080/api/fling
|
||||||
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
{"name": "Fling from querysheet with Auth and very long name", "expirationClicks": 12, "shared": true, "authCode": "abc"}
|
||||||
|
|
||||||
#
|
#
|
||||||
:flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e
|
:flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e
|
||||||
|
|
|
@ -129,7 +129,11 @@ public class FileSystemArchive implements ArchiveService {
|
||||||
|
|
||||||
Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip");
|
Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip");
|
||||||
log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath);
|
log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath);
|
||||||
|
if(Files.exists(zipDiskPath)) {
|
||||||
Files.delete(zipDiskPath);
|
Files.delete(zipDiskPath);
|
||||||
|
} else {
|
||||||
|
log.warn("No fling disk found at {}", zipDiskPath);
|
||||||
|
}
|
||||||
|
|
||||||
artifactRepository.findAllByFlingId(flingId).forEach(ar -> ar.setArchived(false));
|
artifactRepository.findAllByFlingId(flingId).forEach(ar -> ar.setArchived(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -218,6 +219,14 @@ public class FileSystemArchiveTest {
|
||||||
equalTo(false));
|
equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteFling_zipDiskNotFound_noThrow() throws IOException {
|
||||||
|
assertThat(Files.exists(tempDir.resolve(artifactEntity2.getFling().getId() + ".zip")),
|
||||||
|
equalTo(false));
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> fileSystemArchive.deleteFling(artifactEntity2.getFling().getId()));
|
||||||
|
}
|
||||||
|
|
||||||
private void repopulateArchivePath() throws IOException, URISyntaxException {
|
private void repopulateArchivePath() throws IOException, URISyntaxException {
|
||||||
Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
|
Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default function FlingAdmin() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (flingId) {
|
if (flingId) {
|
||||||
dispatch(setActiveFling(flingId))
|
dispatch(setActiveFling(flingId));
|
||||||
}
|
}
|
||||||
}, [flingId, dispatch]);
|
}, [flingId, dispatch]);
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,26 @@ import React, { useRef } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
import { flingClient } from '../../util/flingclient';
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
|
||||||
|
import { deleteFling } from "../../redux/actions";
|
||||||
|
|
||||||
function TileAction(props) {
|
function TileAction(props) {
|
||||||
let shareUrlRef = useRef(null);
|
let shareUrlRef = useRef(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
function copyShareUrl() {
|
||||||
|
shareUrlRef.current.focus();
|
||||||
|
shareUrlRef.current.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
let successful = document.execCommand('copy');
|
||||||
|
let msg = successful ? 'successful' : 'unsuccessful';
|
||||||
|
console.log('Copying to clipoard ' + msg);
|
||||||
|
} catch (err) {
|
||||||
|
log.error("Couldn't copy to clipboard: ", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tile-action dropdown">
|
<div className="tile-action dropdown">
|
||||||
|
@ -27,8 +43,7 @@ function TileAction(props) {
|
||||||
<li className="menu-item">
|
<li className="menu-item">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-switch">
|
<label className="form-switch">
|
||||||
<input type="checkbox"
|
<input type="checkbox" checked={props.fling.shared} />
|
||||||
checked={props.fling.shared} onChange={toggleShared} />
|
|
||||||
<i className="form-icon" />
|
<i className="form-icon" />
|
||||||
{props.fling.shared ? "Shared" : "Private"}
|
{props.fling.shared ? "Shared" : "Private"}
|
||||||
</label>
|
</label>
|
||||||
|
@ -36,7 +51,7 @@ function TileAction(props) {
|
||||||
</li>
|
</li>
|
||||||
<li className="menu-item">
|
<li className="menu-item">
|
||||||
<button className="btn btn-link text-warning pl-0"
|
<button className="btn btn-link text-warning pl-0"
|
||||||
onClick={deleteFling}>
|
onClick={ev => dispatch(deleteFling(props.fling.id))}>
|
||||||
<i className="icon icon-delete mr-1" /> Remove
|
<i className="icon icon-delete mr-1" /> Remove
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -44,34 +59,14 @@ function TileAction(props) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function copyShareUrl() {
|
|
||||||
shareUrlRef.current.focus();
|
|
||||||
shareUrlRef.current.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
let successful = document.execCommand('copy');
|
|
||||||
let msg = successful ? 'successful' : 'unsuccessful';
|
|
||||||
console.log('Copying to clipoard ' + msg);
|
|
||||||
} catch (err) {
|
|
||||||
log.error("Couldn't copy to clipboard: ", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteFling() {
|
|
||||||
await flingClient.deleteFling(props.fling.id);
|
|
||||||
await props.refreshFlingListFn();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleShared() {
|
|
||||||
await flingClient.putFling(props.fling.id, { "sharing": { "shared": !props.fling.shared } });
|
|
||||||
await props.refreshFlingListFn();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FlingTile(props) {
|
export default function FlingTile(props) {
|
||||||
|
const activeFling = useSelector((state) => state.flings.activeFling);
|
||||||
|
|
||||||
let tileClasses = classNames(
|
let tileClasses = classNames(
|
||||||
"tile", "tile-centered", "p-2", "c-hand",
|
"tile", "tile-centered", "p-2", "c-hand",
|
||||||
{ "active": props.activeFling === props.fling.id }
|
{ "active": activeFling ? activeFling.id === props.fling.id : false }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -81,11 +76,13 @@ export default function FlingTile(props) {
|
||||||
<NavLink to={`/admin/${props.fling.id}`}>
|
<NavLink to={`/admin/${props.fling.id}`}>
|
||||||
<div className="tile-title">{props.fling.name}</div>
|
<div className="tile-title">{props.fling.name}</div>
|
||||||
<small className="tile-subtitle text-gray">
|
<small className="tile-subtitle text-gray">
|
||||||
14MB · Public · 1 Jan, 2017
|
{`${props.fling.shared ? "Shared" : "Private"}` +
|
||||||
|
` · ${(new Date(props.fling.creationTime)).toLocaleDateString()}` +
|
||||||
|
` · ${props.fling.authCode ? "Protected" : "Unprotected"}`}
|
||||||
</small>
|
</small>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
<TileAction fling={props.fling} refreshFlingListFn={props.refreshFlingListFn} />
|
<TileAction fling={props.fling} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export const SET_FLINGS = "SET_FLINGS";
|
export const SET_FLINGS = "SET_FLINGS";
|
||||||
export const ADD_FLING = "ADD_FLING";
|
export const ADD_FLING = "ADD_FLING";
|
||||||
|
export const DELETE_FLING = "DELETE_FLING";
|
||||||
export const SET_ACTIVE_FLING = "SET_ACTIVE_FLING";
|
export const SET_ACTIVE_FLING = "SET_ACTIVE_FLING";
|
||||||
|
|
|
@ -24,15 +24,14 @@ function setActiveFlingAction(fling) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setActiveFling(id) {
|
function setActiveFling(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
log.debug(`Not setting active Fling. No id given.`);
|
log.debug(`Not setting active Fling. No id given.`);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const { flings: { flings } } = getState();
|
const { flings: { flings } } = getState();
|
||||||
let foundFling = flings.find(f => f.id === id);
|
const foundFling = flings.find(f => f.id === id);
|
||||||
|
|
||||||
if (foundFling) {
|
if (foundFling) {
|
||||||
log.info(`Found active fling ${id} in local storage`);
|
log.info(`Found active fling ${id} in local storage`);
|
||||||
|
@ -48,7 +47,7 @@ function setActiveFling(id) {
|
||||||
dispatch(setActiveFlingAction(fling))
|
dispatch(setActiveFlingAction(fling))
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
log.warn(`Could not find active fling. ` +
|
log.warn(`Could not find active fling: ${error} \n` +
|
||||||
`Resetting active fling`);
|
`Resetting active fling`);
|
||||||
dispatch(setActiveFlingAction(undefined));
|
dispatch(setActiveFlingAction(undefined));
|
||||||
})
|
})
|
||||||
|
@ -57,11 +56,34 @@ function setActiveFling(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function retrieveFlings() {
|
function retrieveFlings() {
|
||||||
return (dispatch) => {
|
return (dispatch, getState) => {
|
||||||
let flingClient = new FlingClient();
|
const { flings: { activeFling } } = getState();
|
||||||
|
const flingClient = new FlingClient();
|
||||||
|
|
||||||
flingClient.getFlings()
|
flingClient.getFlings()
|
||||||
.then(flings => dispatch(setFlingsAction(flings)));
|
.then(flings => {
|
||||||
|
dispatch(setFlingsAction(flings));
|
||||||
|
if (activeFling) {
|
||||||
|
dispatch(setActiveFling(activeFling.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { retrieveFlings, setActiveFling };
|
function deleteFling(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
if (!id) {
|
||||||
|
log.debug(`Not deleting Fling. No id given.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const flingClient = new FlingClient();
|
||||||
|
|
||||||
|
flingClient.deleteFling(id)
|
||||||
|
.then(() => dispatch(retrieveFlings()))
|
||||||
|
.catch(error =>
|
||||||
|
log.error(`Could not delete fling ${id}: ${error}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { retrieveFlings, setActiveFling, deleteFling };
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import log from "loglevel";
|
||||||
import produce from "immer";
|
import produce from "immer";
|
||||||
|
|
||||||
import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes";
|
import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes";
|
||||||
|
@ -16,7 +17,19 @@ export default produce((draft, action) => {
|
||||||
draft.flings = action.payload;
|
draft.flings = action.payload;
|
||||||
break;
|
break;
|
||||||
case ADD_FLING:
|
case ADD_FLING:
|
||||||
|
// Check storage again here, otherwise there could be a race
|
||||||
|
// condition due to async calls of SET_FLINGS and ADD_FLING
|
||||||
|
let foundFlingIdx = draft.flings.findIndex(fling =>
|
||||||
|
fling.id === action.payload.id);
|
||||||
|
|
||||||
|
if (foundFlingIdx === -1) {
|
||||||
|
log.debug(`Adding new fling with id ${action.payload.id}`)
|
||||||
draft.flings.push(action.payload);
|
draft.flings.push(action.payload);
|
||||||
|
} else {
|
||||||
|
log.debug(`Fling already exists. ` +
|
||||||
|
`Updating fling with id ${action.payload.id}`)
|
||||||
|
draft.flings[foundFlingIdx] = action.payload
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case SET_ACTIVE_FLING:
|
case SET_ACTIVE_FLING:
|
||||||
draft.activeFling = action.payload;
|
draft.activeFling = action.payload;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const FLING_FILTERS = {
|
|
||||||
ALL: "all"
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { FLING_FILTERS } from "../selectorTypes";
|
|
||||||
|
|
||||||
export const flingSelector = (store, flingFilter) => {
|
|
||||||
switch(flingFilter) {
|
|
||||||
case FLING_FILTERS.ALL:
|
|
||||||
return store.flings.flings;
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue