Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ import { Study } from './types';
import {
clearPreviousOPFSVersionData,
deleteFoldersFromOPFS,
formatSize,
getOPFSData,
hybridGlobalFilter,
purgeOldFilesFromOPFS,
} from './utils';
import { OPFS_PURGE_METADATA } from './constants';

const columnHelper = createColumnHelper<Study>();

Expand Down Expand Up @@ -66,23 +69,21 @@ const columns: ColumnDef<Study>[] = [
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
StudyInstanceUID
<ArrowUpDown className="h-4 w-4" />
StudyInstanceUID <ArrowUpDown className="h-4 w-4" />
</Button>
);
},
cell: ({ row }) => <div>{row.getValue('study-uid')}</div>,
},
{
accessorKey: 'study-description',
columnHelper.accessor('study-description', {
accessorFn: row => row['study-description'] || '',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
StudyDescription
<ArrowUpDown className="h-4 w-4" />
StudyDescription <ArrowUpDown className="h-4 w-4" />
</Button>
);
},
Expand All @@ -97,7 +98,7 @@ const columns: ColumnDef<Study>[] = [
</Tooltip>
);
},
},
}),
columnHelper.accessor('study-modalities', {
accessorFn: row => row['study-modalities'].sort().join(', '),
header: ({ column }) => {
Expand All @@ -106,8 +107,7 @@ const columns: ColumnDef<Study>[] = [
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Modalities
<ArrowUpDown className="h-4 w-4" />
Modalities <ArrowUpDown className="h-4 w-4" />
</Button>
);
},
Expand All @@ -126,18 +126,7 @@ const columns: ColumnDef<Study>[] = [
columnHelper.accessor('study-size', {
accessorFn: row => {
const size: number = row['study-size'];
const oneGB = 1024 * 1024 * 1024,
oneMB = 1024 * 1024,
oneKB = 1024;

if (size >= oneGB) {
return `${(size / oneGB).toFixed(2)} GB`;
} else if (size >= oneMB) {
return `${(size / oneMB).toFixed(2)} MB`;
} else if (size >= oneKB) {
return `${(size / oneKB).toFixed(2)} KB`;
}
return `${size} bytes`;
return formatSize(size);
},
sortingFn: (rowA, rowB, columnId) => {
const sizeA = rowA.original[columnId];
Expand All @@ -157,27 +146,30 @@ const columns: ColumnDef<Study>[] = [
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Size
<ArrowUpDown className="h-4 w-4" />
Size <ArrowUpDown className="h-4 w-4" />
</Button>
);
},
cell: ({ row }) => <div>{row.getValue('study-size')}</div>,
}),
columnHelper.accessor('study-last-modified', {
accessorFn: row => new Date(row['study-last-modified']).toGMTString(),
accessorFn: row => new Date(row['study-last-modified']),
sortingFn: 'datetime',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
Last Modified
<ArrowUpDown className="h-4 w-4" />
Last Modified <ArrowUpDown className="h-4 w-4" />
</Button>
);
},
cell: ({ row }) => <div>{row.getValue('study-last-modified')}</div>,
cell: ({ row }) => {
const value: Date = row.getValue('study-last-modified');
const formattedDate = value.toDateString() + ', ' + value.toLocaleTimeString();
return <div>{formattedDate}</div>;
},
}),
{
id: 'actions',
Expand Down Expand Up @@ -247,6 +239,7 @@ export default function OPFSManagementTool() {

const refreshOPFSData = async () => {
const fetchedData = await getOPFSData();
table.toggleAllPageRowsSelected(false);
setData(fetchedData);
};

Expand All @@ -265,6 +258,19 @@ export default function OPFSManagementTool() {
}
};

const purgeOldFiles = async (time: number) => {
await purgeOldFilesFromOPFS(time);
refreshOPFSData();
};

const calculateTotalSize = (list: Study[]) => {
const totalSize = list.reduce((total, study) => {
return total + study['study-size'];
}, 0);

return formatSize(totalSize);
};

return (
<div className="w-full p-6">
<div className="flex items-center py-4">
Expand Down Expand Up @@ -300,6 +306,28 @@ export default function OPFSManagementTool() {
>
Debug Copy
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="ml-2"
>
Purge <ChevronDown />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Files older than</DropdownMenuLabel>
{OPFS_PURGE_METADATA.map(option => (
<DropdownMenuItem
key={option.label}
className="capitalize"
onClick={() => purgeOldFiles(option.time)}
>
{option.label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
Expand Down Expand Up @@ -378,6 +406,12 @@ export default function OPFSManagementTool() {
<div className="text-muted-foreground flex-1 text-sm">
{table.getFilteredSelectedRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s) selected.
{table.getFilteredSelectedRowModel().rows.length
? ` ${calculateTotalSize(data.filter((study, index) => rowSelection[index]))}.`
: ''}
</div>
<div className="text-muted-foreground flex-1 text-sm">
Total size: {calculateTotalSize(data)}
</div>
<div className="space-x-2">
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
export const OPFS_VERSION_STORAGE_KEY = 'gh-opfs-path-version';
export const CURRENT_OPFS_VERSION = 1;

export const OPFS_PURGE_METADATA = [
{
label: 'All',
time: null,
},
{
label: '1 Hour',
time: 1000 * 60 * 60,
},
{
label: '4 Hours',
time: 1000 * 60 * 60 * 4,
},
{
label: '12 Hours',
time: 1000 * 60 * 60 * 12,
},
{
label: '1 Day',
time: 1000 * 60 * 60 * 24,
},
{
label: '1 Week',
time: 1000 * 60 * 60 * 24 * 7,
},
];
70 changes: 70 additions & 0 deletions extensions/cornerstone/src/components/OPFSManagementTool/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,61 @@ export async function deleteFoldersFromOPFS(folderPaths: string[]) {
}
}

export async function purgeOldFilesFromOPFS(maxAgeMs?: number): Promise<void> {
if (!maxAgeMs) {
try {
const rootHandle = await getOPFSRootHandle();
// @ts-ignore
await rootHandle.remove({ recursive: true });
} catch (error) {
console.warn(`Error purging files: ${error.message}`);
}
return;
}

const cutoffTime = Date.now() - maxAgeMs;

async function traverseAndClean(dirHandle: FileSystemDirectoryHandle): Promise<void> {
const entries: (FileSystemFileHandle | FileSystemDirectoryHandle)[] = [];
// @ts-ignore
for await (const subDirHandle of dirHandle.values()) {
entries.push(subDirHandle);
}

await Promise.all(
entries.map(async subDirHandle => {
if (!maxAgeMs) {
await dirHandle.removeEntry(subDirHandle.name, { recursive: true });
}

if (subDirHandle.kind === 'file') {
const fileHandle = subDirHandle as FileSystemFileHandle;
const file = await fileHandle.getFile();

if (file.lastModified < cutoffTime) {
await dirHandle.removeEntry(file.name);
}
} else if (subDirHandle.kind === 'directory') {
await traverseAndClean(subDirHandle);

// @ts-ignore
if ((await subDirHandle.values().next()).done) {
// Subdirectory is empty: DELETE it from the parent
await dirHandle.removeEntry(subDirHandle.name);
}
}
})
);
}

try {
const rootHandle = await getOPFSRootHandle();
await traverseAndClean(rootHandle);
} catch (error) {
console.warn(`Error clearing partial files: ${error.message}`);
}
}

export function hybridGlobalFilter(
row: Row<Study>,
columnId: string,
Expand All @@ -297,3 +352,18 @@ export function hybridGlobalFilter(
// This runs if the input wasn't a valid regex pattern OR if the try-catch failed.
return cellValueString.includes(filterValueString.toLowerCase());
}

export function formatSize(size = 0): string {
const oneGB = 1024 * 1024 * 1024,
oneMB = 1024 * 1024,
oneKB = 1024;

if (size >= oneGB) {
return `${(size / oneGB).toFixed(2)} GB`;
} else if (size >= oneMB) {
return `${(size / oneMB).toFixed(2)} MB`;
} else if (size >= oneKB) {
return `${(size / oneKB).toFixed(2)} KB`;
}
return `${size} bytes`;
}