Fixed bugs in the mass-storage implementation. Directories can be mounted via drag/drop, file information is now property reported including dates, and now large files >128k work correctly.

This commit is contained in:
Brendan Robert 2015-12-20 12:45:56 -06:00
parent cf87f30e35
commit 4021af3ac6
7 changed files with 111 additions and 78 deletions

View File

@ -123,7 +123,7 @@ public class JaceUIController {
return null;
}
for (File f : files) {
if (f.isFile()) return f;
if (f.exists()) return f;
}
return null;
}

View File

@ -30,11 +30,14 @@ import java.util.logging.Logger;
/**
* Prodos directory node
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class DirectoryNode extends DiskNode implements FileFilter {
// public static int FILE_ENTRY_SIZE = 38;
public static int FILE_ENTRY_SIZE = 0x027;
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock) throws IOException {
setBaseBlock(baseBlock);
init(ownerFilesystem, physicalDir);
@ -44,7 +47,6 @@ public class DirectoryNode extends DiskNode implements FileFilter {
init(ownerFilesystem, physicalDir);
}
private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile) throws IOException {
setPhysicalFile(physicalFile);
setType(EntryType.SUBDIRECTORY);
@ -78,7 +80,8 @@ public class DirectoryNode extends DiskNode implements FileFilter {
@Override
/**
* Checks contents of subdirectory for changes as well as directory itself (super class)
* Checks contents of subdirectory for changes as well as directory itself
* (super class)
*/
public boolean checkFile() throws IOException {
boolean success = true;
@ -90,7 +93,7 @@ public class DirectoryNode extends DiskNode implements FileFilter {
for (File f : realFileList) {
realFiles.add(f.getName());
}
for (Iterator<DiskNode> i = getChildren().iterator(); i.hasNext(); ) {
for (Iterator<DiskNode> i = getChildren().iterator(); i.hasNext();) {
DiskNode node = i.next();
if (realFiles.contains(node.getPhysicalFile().getName())) {
realFiles.remove(node.getPhysicalFile().getName());
@ -119,14 +122,15 @@ public class DirectoryNode extends DiskNode implements FileFilter {
checkFile();
if (block == 0) {
generateHeader(buffer);
for (int i=0; i < 12 && i < children.size(); i++)
generateFileEntry(buffer, 4 + (i+1) * FILE_ENTRY_SIZE, i);
for (int i = 0; i < 12 && i < children.size(); i++) {
generateFileEntry(buffer, 4 + (i + 1) * FILE_ENTRY_SIZE, i);
}
} else {
int start = (block * 13) - 1;
int end = start + 13;
int offset = 4;
for (int i=start; i < end && i < children.size(); i++) {
for (int i = start; i < end && i < children.size(); i++) {
// TODO: Add any parts that are not file entries.
generateFileEntry(buffer, offset, i);
offset += FILE_ENTRY_SIZE;
@ -136,7 +140,9 @@ public class DirectoryNode extends DiskNode implements FileFilter {
@Override
public boolean accept(File file) {
if (file.getName().endsWith("~")) return false;
if (file.getName().endsWith("~")) {
return false;
}
char c = file.getName().charAt(0);
if (c == '.' || c == '~') {
return false;
@ -146,24 +152,27 @@ public class DirectoryNode extends DiskNode implements FileFilter {
/**
* Generate the directory header found in the base block of a directory
*
* @param buffer where to write data
*/
@SuppressWarnings("static-access")
private void generateHeader(byte[] buffer) {
// System.out.println("Generating directory header");
// Previous block = 0
generateWord(buffer, 0,0);
generateWord(buffer, 0, 0);
// Next block
int nextBlock = 0;
if (!additionalNodes.isEmpty())
if (!additionalNodes.isEmpty()) {
nextBlock = additionalNodes.get(0).baseBlock;
}
generateWord(buffer, 0x02, nextBlock);
// Directory header + name length
// Volumme header = 0x0f0; Subdirectory header = 0x0e0
buffer[4]= (byte) ((baseBlock == 0x02 ? 0x0f0 : 0x0E0) + getName().length());
buffer[4] = (byte) ((baseBlock == 0x02 ? 0x0f0 : 0x0E0) + getName().length());
generateName(buffer, 5, this);
for (int i=0x014 ; i <= 0x01b; i++)
for (int i = 0x014; i <= 0x01b; i++) {
buffer[i] = 0;
}
generateTimestamp(buffer, 0x01c, getPhysicalFile().lastModified());
// Prodos 1.9
buffer[0x020] = 0x019;
@ -185,6 +194,7 @@ public class DirectoryNode extends DiskNode implements FileFilter {
/**
* Generate the entry of a directory
*
* @param buffer where to write data
* @param offset starting offset in buffer to write
* @param fileNumber number of file (indexed in Children array) to write
@ -192,17 +202,17 @@ public class DirectoryNode extends DiskNode implements FileFilter {
private void generateFileEntry(byte[] buffer, int offset, int fileNumber) throws IOException {
// System.out.println("Generating entry for "+children.get(fileNumber).getName());
DiskNode child = children.get(fileNumber);
child.allocate();
// Entry Type and length
buffer[offset] = (byte) ((child.getType().code << 4) + child.getName().length());
// Name
generateName(buffer, offset+1, child);
generateName(buffer, offset + 1, child);
// File type
buffer[offset + 0x010] = (byte) ((child instanceof DirectoryNode) ? 0x0f : ((FileNode) child).fileType);
// Key pointer
generateWord(buffer, offset + 0x011, child.getBaseBlock());
// Blocks used -- will report only one unless file is actually allocated
// child.allocate();
generateWord(buffer, offset + 0x013, 1 + child.additionalNodes.size());
generateWord(buffer, offset + 0x013, child.additionalNodes.size());
// EOF
// TODO: Verify this is the right thing to do -- is EOF total length or a modulo?
int length = ((int) child.physicalFile.length()) & 0x0ffffff;
@ -214,11 +224,12 @@ public class DirectoryNode extends DiskNode implements FileFilter {
buffer[offset + 0x01c] = 0x19;
// Minimum version = 0
buffer[offset + 0x01d] = 0;
// Access = all granted
buffer[offset + 0x01e] = (byte) 0x0ff;
// Access = Read-only
buffer[offset + 0x01e] = (byte) 0x001;
// AUX type
if (child instanceof FileNode)
if (child instanceof FileNode) {
generateWord(buffer, offset + 0x01f, ((FileNode) child).loadAddress);
}
// Modification date
generateTimestamp(buffer, offset + 0x021, child.physicalFile.lastModified());
// Key pointer for directory
@ -231,26 +242,22 @@ public class DirectoryNode extends DiskNode implements FileFilter {
// yyyyyyym mmmddddd - Byte 0,1
// ---hhhhh --mmmmmm - Byte 2,3
// buffer[offset+1] = (byte) (((c.get(Calendar.YEAR) - 1990) << 1) + ((c.get(Calendar.MONTH)>> 3) & 1));
buffer[offset+0] = 0;
buffer[offset+1] = 0;
buffer[offset+2] = 0;
buffer[offset+3] = 0;
buffer[offset+1] = (byte) (((c.get(Calendar.YEAR) - 2000) << 1) | ((c.get(Calendar.MONTH)+1)>> 3));
// buffer[offset+2] = (byte) ((c.get(Calendar.MONTH)>> 3) & 1);
// buffer[offset+3] = (byte) (((c.get(Calendar.MONTH)&7) + c.get(Calendar.DAY_OF_MONTH)) & 0x0ff);
// buffer[offset+0] = (byte) c.get(Calendar.HOUR_OF_DAY);
// buffer[offset+1] = (byte) c.get(Calendar.MINUTE);
buffer[offset+0] = (byte) (((((c.get(Calendar.MONTH)+1)&7)<<5) | c.get(Calendar.DAY_OF_MONTH)) & 0x0ff);
buffer[offset+3] = (byte) c.get(Calendar.HOUR_OF_DAY);
buffer[offset+2] = (byte) c.get(Calendar.MINUTE);
}
private void generateWord(byte[] buffer, int i, int value) {
// Little endian format
buffer[i] = (byte) (value & 0x0ff);
buffer[i+1] = (byte) ((value >> 8) & 0x0ff);
buffer[i + 1] = (byte) ((value >> 8) & 0x0ff);
}
private void generateName(byte[] buffer, int offset, DiskNode node) {
for (int i=0; i < node.getName().length(); i++) {
buffer[offset+i] = (byte) node.getName().charAt(i);
for (int i = 0; i < node.getName().length(); i++) {
buffer[offset + i] = (byte) node.getName().charAt(i);
}
}

View File

@ -100,7 +100,7 @@ public abstract class DiskNode {
}
}
public void refresh() {
public void refresh() throws IOException {
ownerFilesystem.deallocateEntry(this);
doRefresh();
allocationTime = System.currentTimeMillis();

View File

@ -21,6 +21,7 @@ package jace.hardware.massStorage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
/**
* Representation of a prodos file with a known file type and having a known
@ -81,16 +82,18 @@ public class FileNode extends DiskNode {
} else if (fileSize <= SAPLING_MAX_SIZE) {
setType(EntryType.SAPLING);
return EntryType.SAPLING;
} else {
setType(EntryType.TREE);
return EntryType.TREE;
}
setType(EntryType.TREE);
return EntryType.TREE;
}
@Override
public void setName(String name) {
String[] parts = name.split("\\.");
FileType t = null;
String[] parts = name.replaceAll("[^A-Za-z0-9]", ".").split("\\.");
FileType t = FileType.UNKNOWN;
int offset = 0;
String prodosName = name;
if (parts.length > 1) {
String extension = parts[parts.length - 1].toUpperCase();
String[] extParts = extension.split("#");
@ -103,17 +106,14 @@ public class FileNode extends DiskNode {
} catch (IllegalArgumentException ex) {
System.out.println("Not sure what extension " + extension + " is!");
}
name = "";
prodosName = "";
for (int i = 0; i < parts.length - 1; i++) {
name += (i > 0 ? "." + parts[i] : parts[i]);
prodosName += (i > 0 ? "." + parts[i] : parts[i]);
}
if (extParts[extParts.length - 1].equals("SYSTEM")) {
name += ".SYSTEM";
prodosName += ".SYSTEM";
}
}
if (t == null) {
t = FileType.UNKNOWN;
}
if (offset == 0) {
offset = t.defaultLoadAddress;
}
@ -121,12 +121,13 @@ public class FileNode extends DiskNode {
loadAddress = offset;
// Pass usable name (stripped of file extension and other type info) as name
super.setName(name);
super.setName(prodosName);
}
public FileNode(ProdosVirtualDisk ownerFilesystem, File file) throws IOException {
setOwnerFilesystem(ownerFilesystem);
setPhysicalFile(file);
setName(file.getName());
}
@Override
@ -135,16 +136,19 @@ public class FileNode extends DiskNode {
@Override
public void doAllocate() throws IOException {
int dataBlocks = (int) ((getPhysicalFile().length() / ProdosVirtualDisk.BLOCK_SIZE) + 1);
int treeBlocks;
if (dataBlocks > 1 && dataBlocks < 257) {
treeBlocks = 1;
} else {
treeBlocks = 1 + (dataBlocks / 256);
}
for (int i = 1; i < dataBlocks + treeBlocks; i++) {
SubNode subNode = new SubNode(i, this);
int dataBlocks = (int) ((getPhysicalFile().length()+ProdosVirtualDisk.BLOCK_SIZE-1) / ProdosVirtualDisk.BLOCK_SIZE);
int treeBlocks =(((dataBlocks * 2) + (ProdosVirtualDisk.BLOCK_SIZE-2)) / ProdosVirtualDisk.BLOCK_SIZE);
if (treeBlocks > 1) treeBlocks++;
// if (dataBlocks > 1 && (dataBlocks*2) < ProdosVirtualDisk.BLOCK_SIZE) {
// treeBlocks = 1;
// } else {
// treeBlocks = 1 + (dataBlocks * 2 / ProdosVirtualDisk.BLOCK_SIZE);
// }
System.out.println("Allocating "+(dataBlocks + treeBlocks)+" blocks for file "+getName()+"; data "+dataBlocks+"; tree "+treeBlocks);
for (int i = 0; i < dataBlocks + treeBlocks; i++) {
new SubNode(i, this);
}
setBaseBlock(additionalNodes.get(0).getBaseBlock());
}
@Override
@ -154,6 +158,9 @@ public class FileNode extends DiskNode {
@Override
public void readBlock(int block, byte[] buffer) throws IOException {
// System.out.println("Read block "+block+" of file "+getName());
int dataBlocks = (int) ((getPhysicalFile().length()+ProdosVirtualDisk.BLOCK_SIZE-1) / ProdosVirtualDisk.BLOCK_SIZE);
int treeBlocks =(((dataBlocks * 2) + (ProdosVirtualDisk.BLOCK_SIZE-2)) / ProdosVirtualDisk.BLOCK_SIZE);
if (treeBlocks > 1) treeBlocks++;
switch (this.getType()) {
case SEEDLING:
readFile(buffer, 0);
@ -163,20 +170,20 @@ public class FileNode extends DiskNode {
readFile(buffer, (block - 1));
} else {
// Generate seedling index block
generateIndex(buffer, 0, 256);
generateIndex(buffer, 0, dataBlocks);
}
break;
case TREE:
int dataBlocks = (int) ((getPhysicalFile().length() / ProdosVirtualDisk.BLOCK_SIZE) + 1);
int treeBlocks = (dataBlocks / 256);
if (block == 0) {
generateIndex(buffer, 0, treeBlocks);
} else if (block < treeBlocks) {
int start = treeBlocks + (block - 1 * 256);
int end = Math.min(start + 256, treeBlocks);
generateIndex(buffer, treeBlocks, end);
System.out.println("Reading index for "+getName());
generateIndex(buffer, 1, treeBlocks);
} else if (block <= treeBlocks) {
System.out.println("Reading tree block "+block+" for "+getName());
int start = treeBlocks + ((block - 1) * 256);
int end = treeBlocks + dataBlocks;
generateIndex(buffer, start, end);
} else {
readFile(buffer, (block - treeBlocks));
readFile(buffer, (block - treeBlocks - 1));
}
break;
}
@ -190,10 +197,22 @@ public class FileNode extends DiskNode {
}
private void generateIndex(byte[] buffer, int indexStart, int indexLimit) {
int pos = 0;
for (int i = indexStart; pos < 256 && i < indexLimit && i < additionalNodes.size(); i++, pos++) {
buffer[pos] = (byte) (additionalNodes.get(i).baseBlock & 0x0ff);
buffer[pos + 256] = (byte) ((additionalNodes.get(i).baseBlock >> 8) & 0x0ff);
System.out.println("Index block contents:");
Arrays.fill(buffer, (byte) 0);
for (int i = indexStart, count=0; count < 256 && i < indexLimit && i < additionalNodes.size(); i++, count++) {
int base = additionalNodes.get(i).baseBlock;
System.out.print(Integer.toHexString(base)+":");
buffer[count] = (byte) (base & 0x0ff);
buffer[count + 256] = (byte) (base >> 8);
}
System.out.println();
for (int i=0; i < 256; i++) {
System.out.printf("%02X ",buffer[i]&0x0ff);
}
System.out.println();
for (int i=256; i < 512; i++) {
System.out.printf("%02X ",buffer[i]&0x0ff);
}
System.out.println();
}
}
}

View File

@ -38,7 +38,7 @@ import java.util.logging.Logger;
* is a folder and not a disk image. FreespaceBitmap and the various Node
* classes are used to represent the filesystem structure.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class ProdosVirtualDisk implements IDisk {
@ -61,7 +61,7 @@ public class ProdosVirtualDisk implements IDisk {
DiskNode node = physicalMap.get(block);
Arrays.fill(ioBuffer, (byte) (block & 0x0ff));
if (node == null) {
System.out.println("Reading unknown block?!");
System.out.println("Reading unknown block " + Integer.toHexString(block));
for (int i = 0; i < BLOCK_SIZE; i++) {
memory.write(bufferAddress + i, (byte) 0, false, false);
}
@ -132,11 +132,15 @@ public class ProdosVirtualDisk implements IDisk {
}
// Mark space occupied by node
public void allocateEntry(DiskNode node) {
public void allocateEntry(DiskNode node) throws IOException {
physicalMap.put(node.baseBlock, node);
node.additionalNodes.stream().forEach((sub) -> {
physicalMap.put(sub.getBaseBlock(), sub);
});
for (DiskNode subnode : node.additionalNodes) {
int blockNum = getNextFreeBlock();
System.out.println("Allocating block " + Integer.toHexString(blockNum) + " for " + subnode.getName());
subnode.setBaseBlock(blockNum);
physicalMap.put(blockNum, subnode);
}
}
// Mark space occupied by nodes as free (remove allocation mapping)
@ -145,11 +149,11 @@ public class ProdosVirtualDisk implements IDisk {
if (physicalMap.get(node.baseBlock) != null && physicalMap.get(node.baseBlock).equals(node)) {
physicalMap.remove(node.baseBlock);
}
node.additionalNodes.stream().filter((sub) ->
(physicalMap.get(sub.getBaseBlock()) != null && physicalMap.get(sub.baseBlock).equals(sub))).
node.additionalNodes.stream().filter((sub)
-> (physicalMap.get(sub.getBaseBlock()) != null && physicalMap.get(sub.baseBlock).equals(sub))).
forEach((sub) -> {
physicalMap.remove(sub.getBaseBlock());
});
physicalMap.remove(sub.getBaseBlock());
});
}
// Is the specified block in use?
@ -200,7 +204,6 @@ public class ProdosVirtualDisk implements IDisk {
freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START);
allocateEntry(freespaceBitmap);
}
@Override

View File

@ -31,7 +31,6 @@ import java.io.IOException;
public class SubNode extends DiskNode {
int sequenceNumber;
private int seq;
public SubNode(int seq, DiskNode parent) throws IOException {
init(seq, parent);
@ -49,6 +48,11 @@ public class SubNode extends DiskNode {
parent.additionalNodes.add(this);
}
@Override
public String getName() {
return parent.getName() + "; block "+sequenceNumber;
}
@Override
public void doDeallocate() {
}

View File

@ -49,7 +49,7 @@ public enum DiskType {
}
static public DiskType determineType(File file) {
if (!file.exists()) return null;
if (file == null || !file.exists()) return null;
if (file.isDirectory()) return VIRTUAL;
if (file.getName().toLowerCase().endsWith("hdv")) {
return LARGE;