Skip to content
Open
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
@@ -0,0 +1,126 @@
/*******************************************************************************
* PathVisio, a tool for data visualization and analysis using biological pathways
* Copyright 2006-2022 BiGCaT Bioinformatics, WikiPathways
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
******************************************************************************/
package org.pathvisio.core.util;

import java.io.File;
import java.text.DecimalFormat;

/**
* Utility class for checking available disk space.
*/
public class DiskSpaceValidator {

private static final long SAFETY_BUFFER_BYTES = 10 * 1024 * 1024; // 10 MB

/**
* Checks if there is enough space on the disk partition containing the
* specified path.
*
* @param path The file or directory path to check space for
* @param requiredSizeBytes The estimated size required for the operation
* @return true if there is enough space, false if there is insufficient space
* or if space cannot be determined for large files (>50 MB)
*/
public static boolean hasEnoughSpace(File path, long requiredSizeBytes) {
if (path == null) {
return false;
}

long usableSpace = getUsableSpace(path);

// If we couldn't determine space, a warning is printed and we proceed anyway
if (usableSpace == -1) {
// Can't verify, warn user
System.err.println("Warning: Could not determine available disk space for " + path);
// For now, let's be conservative and say we DON'T have space
// unless the file is tiny
if (requiredSizeBytes > 50 * 1024 * 1024) { // > 50 MB
return false; // too risky, block it
}
return true; // small file, probably fine
}
long totalNeeded = requiredSizeBytes + SAFETY_BUFFER_BYTES;
if (usableSpace < totalNeeded) {
// Not enough space
return false;
}
// We have enough space
return true;

}

/**
* Gets the usable space on the partition containing the given path.
* Walks up the directory tree if the path itself doesn't exist.
*
* @param path The path to check
* @return The usable space in bytes, or -1 if it cannot be determined
*/
public static long getUsableSpace(File path) {
if (path == null) {
return -1;
}

File current = path;

// If path is a string representation that hasn't been created yet,
// we might need to find the nearest existing parent
while (current != null && !current.exists()) {
current = current.getParentFile();
}

if (current == null) {
// Try to fallback to user home or roots if provided path was totally invalid
// relative path
// But for now, returning -1 is safer as per spec "fail-safe"
return -1;
}

try {
// getUsableSpace() returns 0 if the abstract path name does not name a
// partition.
// But we've walked up to an existing file/dir, so it should work.
return current.getUsableSpace();
} catch (SecurityException e) {
return -1;
}
}

/**
* Returns a human readable string describing the available and total space.
*
* @param path The path to check
* @return A formatted string, e.g. "Available: 50 MB / Total: 500 GB"
*/
public static String getReadableSpaceInfo(File path) {
long available = getUsableSpace(path);

if (available == -1) {
return "Available space: Unknown";
}

return "Available: " + formatSize(available);
}

private static String formatSize(long size) {
if (size <= 0)
return "0 B";
final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.pathvisio.core.util;

import java.io.File;
import junit.framework.TestCase;

/**
* Tests for DiskSpaceValidator.
*/
public class DiskSpaceValidatorTest extends TestCase {

public void testGetUsableSpace() {
// Test with valid existing path (current directory)
File currentDir = new File(".");
long space = DiskSpaceValidator.getUsableSpace(currentDir);
assertTrue("Space should be positive for current directory", space > 0);

// Test with null
assertEquals("Space should be -1 for null path", -1, DiskSpaceValidator.getUsableSpace(null));
}

public void testHasEnoughSpace() {
File currentDir = new File(".");

// Ask for a small amount (1 byte)
assertTrue("Should have space for 1 byte", DiskSpaceValidator.hasEnoughSpace(currentDir, 1));

// Ask for an impossibly large amount
assertFalse("Should not have space for max long bytes",
DiskSpaceValidator.hasEnoughSpace(currentDir, Long.MAX_VALUE - (1024 * 1024 * 200))); // Subtract buffer
// to avoid
// overflow issues
// if logic
// changes
}

public void testNonExistentPath() {
// Test with a path that definitely doesn't exist, but whose parent does
File cur = new File(".");
File nonExistent = new File(cur, "this_file_does_not_exist_" + System.currentTimeMillis());

long space = DiskSpaceValidator.getUsableSpace(nonExistent);
assertTrue("Should be able to find space for non-existent file by checking parent", space > 0);
}

public void testReadableSpaceInfo() {
File currentDir = new File(".");
String info = DiskSpaceValidator.getReadableSpaceInfo(currentDir);
assertNotNull(info);
assertTrue("Info string should contain 'Available'", info.contains("Available"));
}

public void testLargeFileWhenSpaceUnknown() {
// This tests the conservative approach for large files when space can't be
// determined
// Hard to test -1 scenario directly without mocking
// but we can at least document the expected behavior

// Test that small files (under 50 MB) would pass
File currentDir = new File(".");
assertTrue("Small files should be allowed",
DiskSpaceValidator.hasEnoughSpace(currentDir, 10 * 1024 * 1024)); // 10 MB
}

public void testHasEnoughSpaceWithNull() {
assertFalse("Should return false for null path",
DiskSpaceValidator.hasEnoughSpace(null, 1000));
}

public void testSafetyMargin() {
File currentDir = new File(".");
long available = DiskSpaceValidator.getUsableSpace(currentDir);

// Request available space minus 5 MB
// Validator adds 10 MB safety margin, so total needed = available - 5 MB + 10
// MB = available + 5 MB
// This exceeds available space, so should fail

if (available > 20 * 1024 * 1024) { // Only test if we have enough space to work with
long requestSize = available - (5 * 1024 * 1024); // Request (available - 5 MB)

// After adding 10 MB safety margin, this becomes (available + 5 MB), which
// should fail
assertFalse("Should account for safety margin",
DiskSpaceValidator.hasEnoughSpace(currentDir, requestSize));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,82 +17,95 @@
package org.pathvisio.desktop.data;

import java.awt.Component;
import java.io.File;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import org.bridgedb.gui.SimpleFileFilter;
import org.bridgedb.rdb.construct.DataDerby;
import org.pathvisio.core.preferences.GlobalPreference;
import org.pathvisio.core.preferences.PreferenceManager;
import org.pathvisio.core.util.DiskSpaceValidator;

/**
* user interface functions for single-file Derby databases.
*/
public class DBConnDerby extends DataDerby implements DBConnectorSwing
{
public class DBConnDerby extends DataDerby implements DBConnectorSwing {
static final String DB_EXT_NAME_GEX = "Expression datasets";
static final String DB_EXT_NAME_GDB = "Synonym databases";

//TODO: reduce redundancy between openChooseDbDialog and openNewDbDialog,
public String openChooseDbDialog(Component parent)
{
// TODO: reduce redundancy between openChooseDbDialog and openNewDbDialog,
public String openChooseDbDialog(Component parent) {
JFileChooser jfc = new JFileChooser();
jfc.setDialogType(JFileChooser.OPEN_DIALOG);
if (getDbType() == TYPE_GDB)
{
if (getDbType() == TYPE_GDB) {
jfc.setCurrentDirectory(PreferenceManager.getCurrent().getFile(GlobalPreference.DIR_LAST_USED_PGDB));
jfc.addChoosableFileFilter(new SimpleFileFilter(DB_EXT_NAME_GDB, "*.bridge|*.pgdb", true));
}
else
{
} else {
jfc.setCurrentDirectory(PreferenceManager.getCurrent().getFile(GlobalPreference.DIR_LAST_USED_PGEX));
jfc.addChoosableFileFilter(new SimpleFileFilter(DB_EXT_NAME_GEX, "*." + DB_FILE_EXT_GEX, true));
}

int status = jfc.showDialog (parent, "Open database");
if(status == JFileChooser.APPROVE_OPTION)
{
if (getDbType() == TYPE_GDB)
{
PreferenceManager.getCurrent().setFile (GlobalPreference.DIR_LAST_USED_PGDB, jfc.getCurrentDirectory());
}
else
{
PreferenceManager.getCurrent().setFile (GlobalPreference.DIR_LAST_USED_PGEX, jfc.getCurrentDirectory());
int status = jfc.showDialog(parent, "Open database");
if (status == JFileChooser.APPROVE_OPTION) {
if (getDbType() == TYPE_GDB) {
PreferenceManager.getCurrent().setFile(GlobalPreference.DIR_LAST_USED_PGDB, jfc.getCurrentDirectory());
} else {
PreferenceManager.getCurrent().setFile(GlobalPreference.DIR_LAST_USED_PGEX, jfc.getCurrentDirectory());
}
return jfc.getSelectedFile().toString();
}
return null;
}

public String openNewDbDialog(Component parent, String defaultName)
{
public String openNewDbDialog(Component parent, String defaultName) {
JFileChooser jfc = new JFileChooser();
jfc.setDialogType(JFileChooser.SAVE_DIALOG);

if (getDbType() == TYPE_GDB)
{
if (getDbType() == TYPE_GDB) {
jfc.setCurrentDirectory(PreferenceManager.getCurrent().getFile(GlobalPreference.DIR_LAST_USED_PGDB));
jfc.addChoosableFileFilter(new SimpleFileFilter(DB_EXT_NAME_GDB, "*." + DB_FILE_EXT_GDB, true));
}
else
{
} else {
jfc.setCurrentDirectory(PreferenceManager.getCurrent().getFile(GlobalPreference.DIR_LAST_USED_PGEX));
jfc.addChoosableFileFilter(new SimpleFileFilter(DB_EXT_NAME_GEX, "*." + DB_FILE_EXT_GEX, true));
}

int status = jfc.showDialog (parent, "Choose filename for database");
if(status == JFileChooser.APPROVE_OPTION)
{
if (getDbType() == TYPE_GDB)
{
PreferenceManager.getCurrent().setFile (GlobalPreference.DIR_LAST_USED_PGDB, jfc.getCurrentDirectory());
int status = jfc.showDialog(parent, "Choose filename for database");
if (status == JFileChooser.APPROVE_OPTION) {
File file = jfc.getSelectedFile();

// Issue #13: Check for disk space in pgex files before creating new database
// files
// Using conservative size estimates. TO DO: calculate from actual data by
// creating a getSourceDataSize() method
long requiredSize;
if (getDbType() == TYPE_GDB) {
requiredSize = 100L * 1024 * 1024; // default values before change is made
} else {
requiredSize = 50L * 1024 * 1024; // default values before change is made
}
else
{
PreferenceManager.getCurrent().setFile (GlobalPreference.DIR_LAST_USED_PGEX, jfc.getCurrentDirectory());

if (!DiskSpaceValidator.hasEnoughSpace(file, requiredSize)) {
String dbType = (getDbType() == TYPE_GDB) ? "synonym database" : "expression dataset";
String spaceInfo = DiskSpaceValidator.getReadableSpaceInfo(file);

JOptionPane.showMessageDialog(parent,
"Not enough free disk space to create the " + dbType + ".\n" +
"Required: approx. " + (requiredSize / (1024 * 1024)) + " MB (plus safety buffer)\n" +
spaceInfo + "\n\n" +
"Please choose a different location or free up some space.",
"Insufficient Disk Space",
JOptionPane.ERROR_MESSAGE);
return null;
}
return jfc.getSelectedFile().toString();

if (getDbType() == TYPE_GDB) {
PreferenceManager.getCurrent().setFile(GlobalPreference.DIR_LAST_USED_PGDB, jfc.getCurrentDirectory());
} else {
PreferenceManager.getCurrent().setFile(GlobalPreference.DIR_LAST_USED_PGEX, jfc.getCurrentDirectory());
}
return file.toString();
}
return null;
}
Expand Down