Gallery can delete all data.
This commit is contained in:
parent
bef63769df
commit
cc6caa39d7
6 changed files with 85 additions and 58 deletions
|
|
@ -298,8 +298,8 @@ class BaseCamera(OFMThing, ABC):
|
||||||
"""The schema (BaseModel) for passing data to the gallery."""
|
"""The schema (BaseModel) for passing data to the gallery."""
|
||||||
return CaptureInfo
|
return CaptureInfo
|
||||||
|
|
||||||
def get_data_for_gallery(self) -> list[CaptureInfo]:
|
def _all_captures(self) -> list[str]:
|
||||||
"""Return all the information about the saved captures."""
|
"""Return the full path for all captures on disk."""
|
||||||
files = os.listdir(self._data_dir)
|
files = os.listdir(self._data_dir)
|
||||||
captures = []
|
captures = []
|
||||||
for filename in files:
|
for filename in files:
|
||||||
|
|
@ -308,15 +308,30 @@ class BaseCamera(OFMThing, ABC):
|
||||||
BASE_IMAGE_FORMATS["jpeg"].path_matches(filename)
|
BASE_IMAGE_FORMATS["jpeg"].path_matches(filename)
|
||||||
or BASE_IMAGE_FORMATS["png"].path_matches(filename)
|
or BASE_IMAGE_FORMATS["png"].path_matches(filename)
|
||||||
):
|
):
|
||||||
captures.append(
|
captures.append(full_path)
|
||||||
|
return captures
|
||||||
|
|
||||||
|
def get_data_for_gallery(self) -> list[CaptureInfo]:
|
||||||
|
"""Return all the information about the saved captures."""
|
||||||
|
return [
|
||||||
CaptureInfo(
|
CaptureInfo(
|
||||||
name=filename,
|
name=os.path.basename(capture),
|
||||||
created=os.path.getctime(full_path),
|
created=os.path.getctime(capture),
|
||||||
modified=os.path.getmtime(full_path),
|
modified=os.path.getmtime(capture),
|
||||||
thing=self.name,
|
thing=self.name,
|
||||||
)
|
)
|
||||||
)
|
for capture in self._all_captures()
|
||||||
return captures
|
]
|
||||||
|
|
||||||
|
def delete_all_gallery_items(self) -> None:
|
||||||
|
"""Delete all the captures on the microscope.
|
||||||
|
|
||||||
|
Use with extreme caution.
|
||||||
|
"""
|
||||||
|
for capture in self._all_captures():
|
||||||
|
lt.raise_if_cancelled()
|
||||||
|
self.logger.info(f"Deleting: {capture}")
|
||||||
|
os.remove(capture)
|
||||||
|
|
||||||
@lt.endpoint(
|
@lt.endpoint(
|
||||||
"delete",
|
"delete",
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ class GalleryCompatibleThing(Protocol):
|
||||||
# Ignore D102: No docstrings for the protocol.
|
# Ignore D102: No docstrings for the protocol.
|
||||||
def get_data_for_gallery(self) -> list[BaseModel]: ... # noqa: D102
|
def get_data_for_gallery(self) -> list[BaseModel]: ... # noqa: D102
|
||||||
|
|
||||||
|
def delete_all_gallery_items(self) -> None: ... # noqa: D102
|
||||||
|
|
||||||
|
|
||||||
class GalleryThing(lt.Thing):
|
class GalleryThing(lt.Thing):
|
||||||
"""A Thing for communicating with the front end gallery."""
|
"""A Thing for communicating with the front end gallery."""
|
||||||
|
|
@ -95,5 +97,18 @@ class GalleryThing(lt.Thing):
|
||||||
"""
|
"""
|
||||||
data_list = []
|
data_list = []
|
||||||
for thing in self.gallery_providing_things.values():
|
for thing in self.gallery_providing_things.values():
|
||||||
data_list += [model.model_dump() for model in thing.get_data_for_gallery()]
|
try:
|
||||||
|
data_list += [
|
||||||
|
model.model_dump() for model in thing.get_data_for_gallery()
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
# If any of the providers errors producing a list, log the exception
|
||||||
|
# and continue.
|
||||||
|
self.logger.exception(e)
|
||||||
return data_list
|
return data_list
|
||||||
|
|
||||||
|
@lt.action
|
||||||
|
def delete_all_data(self) -> None:
|
||||||
|
"""Delete all the gallery data on this microscope."""
|
||||||
|
for thing in self.gallery_providing_things.values():
|
||||||
|
thing.delete_all_gallery_items()
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,18 @@ class SmartScanThing(OFMThing):
|
||||||
ongoing=ongoing_name, include_ongoing=False
|
ongoing=ongoing_name, include_ongoing=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def delete_all_gallery_items(self) -> None:
|
||||||
|
"""Delete all the scans on the microscope.
|
||||||
|
|
||||||
|
**This will irreversibly remove all scanned data from the microscope!**
|
||||||
|
|
||||||
|
Use with extreme caution.
|
||||||
|
"""
|
||||||
|
for scan_name in self._scan_dir_manager.all_scans:
|
||||||
|
lt.raise_if_cancelled()
|
||||||
|
self.logger.info(f"Deleting: {scan_name}")
|
||||||
|
self._delete_scan(scan_name)
|
||||||
|
|
||||||
# Note that the default detector name is set at init. This is over written if
|
# Note that the default detector name is set at init. This is over written if
|
||||||
# setting is loaded from disk.
|
# setting is loaded from disk.
|
||||||
@lt.setting
|
@lt.setting
|
||||||
|
|
@ -608,20 +620,6 @@ class SmartScanThing(OFMThing):
|
||||||
if not deleted_scan_success:
|
if not deleted_scan_success:
|
||||||
raise HTTPException(400, "Couldn't delete scan, check log for details")
|
raise HTTPException(400, "Couldn't delete scan, check log for details")
|
||||||
|
|
||||||
@lt.endpoint(
|
|
||||||
"delete",
|
|
||||||
"scans",
|
|
||||||
)
|
|
||||||
def delete_all_scans(self) -> None:
|
|
||||||
"""Delete all the scans on the microscope.
|
|
||||||
|
|
||||||
**This will irreversibly remove all scanned data from the
|
|
||||||
microscope!**
|
|
||||||
Use with extreme caution.
|
|
||||||
"""
|
|
||||||
for scan_name in self._scan_dir_manager.all_scans:
|
|
||||||
self._delete_scan(scan_name)
|
|
||||||
|
|
||||||
@lt.action
|
@lt.action
|
||||||
def purge_empty_scans(self) -> None:
|
def purge_empty_scans(self) -> None:
|
||||||
"""Delete all scan folders containing no images at the top level."""
|
"""Delete all scan folders containing no images at the top level."""
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ class MinimalGalleryClass:
|
||||||
"""Return a list of Mock Gallery Data."""
|
"""Return a list of Mock Gallery Data."""
|
||||||
return [MockGalleryData(mock="mock", foobar="foobar")]
|
return [MockGalleryData(mock="mock", foobar="foobar")]
|
||||||
|
|
||||||
|
def delete_all_gallery_items(self) -> None:
|
||||||
|
"""Mock deleting all the data."""
|
||||||
|
|
||||||
|
|
||||||
class MinimalGallerySource(OFMThing, MinimalGalleryClass):
|
class MinimalGallerySource(OFMThing, MinimalGalleryClass):
|
||||||
"""Minimal example of a Thing that can show data in the gallery.
|
"""Minimal example of a Thing that can show data in the gallery.
|
||||||
|
|
@ -60,6 +63,9 @@ class BadGallerySource(OFMThing):
|
||||||
"""Return a list of Mock Gallery Data."""
|
"""Return a list of Mock Gallery Data."""
|
||||||
return [MockGalleryData(mock="mock", foobar="foobar")]
|
return [MockGalleryData(mock="mock", foobar="foobar")]
|
||||||
|
|
||||||
|
def delete_all_gallery_items(self) -> None:
|
||||||
|
"""Mock deleting all the data."""
|
||||||
|
|
||||||
|
|
||||||
def test_gallery_compatible_protocol():
|
def test_gallery_compatible_protocol():
|
||||||
"""Check the gallery compatible protocol detects compatible classes only."""
|
"""Check the gallery compatible protocol detects compatible classes only."""
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,11 @@ def test_public_delete_scan(entered_smart_scan_thing, caplog):
|
||||||
assert len(caplog.records) == 1
|
assert len(caplog.records) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_delete_all_scans(entered_smart_scan_thing, caplog):
|
def test_delete_all_scans(entered_smart_scan_thing, caplog, mocker):
|
||||||
"""Check the delete_all_scan API really does delete all the scans."""
|
"""Check the delete_all_scan API really does delete all the scans."""
|
||||||
|
mock_raise_if_cancelled = mocker.patch(
|
||||||
|
"openflexure_microscope_server.things.smart_scan.lt.raise_if_cancelled",
|
||||||
|
)
|
||||||
smart_scan_thing = entered_smart_scan_thing
|
smart_scan_thing = entered_smart_scan_thing
|
||||||
with caplog.at_level(logging.INFO):
|
with caplog.at_level(logging.INFO):
|
||||||
fake_scan_names = [
|
fake_scan_names = [
|
||||||
|
|
@ -295,12 +298,17 @@ def test_delete_all_scans(entered_smart_scan_thing, caplog):
|
||||||
fake_scan_path = os.path.join(SCAN_DIR, fake_scan_name)
|
fake_scan_path = os.path.join(SCAN_DIR, fake_scan_name)
|
||||||
os.makedirs(fake_scan_path)
|
os.makedirs(fake_scan_path)
|
||||||
assert os.path.exists(fake_scan_path)
|
assert os.path.exists(fake_scan_path)
|
||||||
smart_scan_thing.delete_all_scans()
|
smart_scan_thing.delete_all_gallery_items()
|
||||||
for fake_scan_name in fake_scan_names:
|
for fake_scan_name in fake_scan_names:
|
||||||
fake_scan_path = os.path.join(SCAN_DIR, fake_scan_name)
|
fake_scan_path = os.path.join(SCAN_DIR, fake_scan_name)
|
||||||
assert not os.path.exists(fake_scan_path)
|
assert not os.path.exists(fake_scan_path)
|
||||||
# No logs generated
|
# No One log generated per scan deleted
|
||||||
assert len(caplog.records) == 0
|
assert len(caplog.records) == 4
|
||||||
|
for record in caplog.records:
|
||||||
|
assert record.message.startswith("Deleting: fake_scan_000")
|
||||||
|
assert record.levelname == "INFO"
|
||||||
|
# Check that raise_if_cancelled is called once for each scan.
|
||||||
|
assert mock_raise_if_cancelled.call_count == 4
|
||||||
|
|
||||||
|
|
||||||
def _run_only_outer_scan(
|
def _run_only_outer_scan(
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,19 @@
|
||||||
@error="modalError"
|
@error="modalError"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="gallery-button">
|
||||||
<button
|
<action-button
|
||||||
class="uk-button uk-button-default uk-width-1-1 gallery-button"
|
class="uk-width-1-1"
|
||||||
type="button"
|
thing="gallery"
|
||||||
@click="deleteAllScans()"
|
action="delete_all_data"
|
||||||
>
|
submit-label="Delete All"
|
||||||
Delete All Scans
|
:can-terminate="true"
|
||||||
</button>
|
:button-primary="false"
|
||||||
|
:modal-progress="true"
|
||||||
|
:requires-confirmation="true"
|
||||||
|
:confirmation-message="'<p>Are you sure you want to delete all gallery data from the microscope?</p><p>This is <b>irreversible</b>!</p>'"
|
||||||
|
@error="modalError"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
|
|
@ -69,11 +74,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
|
||||||
import PaginateLinks from "@/components/genericComponents/paginateLinks.vue";
|
import PaginateLinks from "@/components/genericComponents/paginateLinks.vue";
|
||||||
import actionButton from "../labThingsComponents/actionButton.vue";
|
import actionButton from "../labThingsComponents/actionButton.vue";
|
||||||
import galleryCard from "./galleryComponents/galleryCard.vue";
|
import galleryCard from "./galleryComponents/galleryCard.vue";
|
||||||
import galleryModal from "./galleryComponents/galleryViewer.vue/index.js";
|
import galleryModal from "./galleryComponents/galleryViewer.vue";
|
||||||
import { eventBus } from "../../eventBus.js";
|
import { eventBus } from "../../eventBus.js";
|
||||||
import { useIntersectionObserver } from "@vueuse/core";
|
import { useIntersectionObserver } from "@vueuse/core";
|
||||||
import { useSettingsStore } from "@/stores/settings.js";
|
import { useSettingsStore } from "@/stores/settings.js";
|
||||||
|
|
@ -104,11 +108,6 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(useSettingsStore, ["baseUri", "ready"]),
|
...mapState(useSettingsStore, ["baseUri", "ready"]),
|
||||||
scansUri() {
|
|
||||||
// The scans URI is currently used for creating endpoint URIs.
|
|
||||||
// The actual property does not exist. So allowUndefined=true
|
|
||||||
return this.thingPropertyUrl("smart_scan", "scans", true);
|
|
||||||
},
|
|
||||||
noItems() {
|
noItems() {
|
||||||
return !this.all_items || this.all_items?.length === 0;
|
return !this.all_items || this.all_items?.length === 0;
|
||||||
},
|
},
|
||||||
|
|
@ -188,20 +187,6 @@ export default {
|
||||||
this.all_items = [];
|
this.all_items = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteAllScans() {
|
|
||||||
try {
|
|
||||||
await this.modalConfirm(
|
|
||||||
"Are you sure you want to delete all scans from the microscope? " +
|
|
||||||
"This is <b>irreversible</b>!",
|
|
||||||
);
|
|
||||||
await axios.delete(`${this.scansUri}`);
|
|
||||||
await this.refreshGallery();
|
|
||||||
this.modalNotify("Deleted all scans.");
|
|
||||||
} catch (e) {
|
|
||||||
// if the confirmation was cancelled, it's rejected with null error
|
|
||||||
if (e) this.modalError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showItem(itemData) {
|
showItem(itemData) {
|
||||||
if (itemData.card_type === "Scan") {
|
if (itemData.card_type === "Scan") {
|
||||||
if (itemData.dzi) {
|
if (itemData.dzi) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue