diff --git a/src/main/java/jace/JaceUIController.java b/src/main/java/jace/JaceUIController.java index 4bc3640..1bbc862 100644 --- a/src/main/java/jace/JaceUIController.java +++ b/src/main/java/jace/JaceUIController.java @@ -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; } diff --git a/src/main/java/jace/hardware/massStorage/DirectoryNode.java b/src/main/java/jace/hardware/massStorage/DirectoryNode.java index 1a0e93f..9dbb795 100644 --- a/src/main/java/jace/hardware/massStorage/DirectoryNode.java +++ b/src/main/java/jace/hardware/massStorage/DirectoryNode.java @@ -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 i = getChildren().iterator(); i.hasNext(); ) { + for (Iterator 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); } } diff --git a/src/main/java/jace/hardware/massStorage/DiskNode.java b/src/main/java/jace/hardware/massStorage/DiskNode.java index 5a291ad..e005482 100644 --- a/src/main/java/jace/hardware/massStorage/DiskNode.java +++ b/src/main/java/jace/hardware/massStorage/DiskNode.java @@ -100,7 +100,7 @@ public abstract class DiskNode { } } - public void refresh() { + public void refresh() throws IOException { ownerFilesystem.deallocateEntry(this); doRefresh(); allocationTime = System.currentTimeMillis(); diff --git a/src/main/java/jace/hardware/massStorage/FileNode.java b/src/main/java/jace/hardware/massStorage/FileNode.java index ba04920..e54dbca 100644 --- a/src/main/java/jace/hardware/massStorage/FileNode.java +++ b/src/main/java/jace/hardware/massStorage/FileNode.java @@ -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(); } -} +} \ No newline at end of file diff --git a/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java b/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java index 443fb17..ceeba10 100644 --- a/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java +++ b/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java @@ -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 diff --git a/src/main/java/jace/hardware/massStorage/SubNode.java b/src/main/java/jace/hardware/massStorage/SubNode.java index 4edde7f..200bff0 100644 --- a/src/main/java/jace/hardware/massStorage/SubNode.java +++ b/src/main/java/jace/hardware/massStorage/SubNode.java @@ -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() { } diff --git a/src/main/java/jace/library/DiskType.java b/src/main/java/jace/library/DiskType.java index 98de811..b8b517d 100644 --- a/src/main/java/jace/library/DiskType.java +++ b/src/main/java/jace/library/DiskType.java @@ -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;