package com.bytezone.diskbrowser.disk; import java.awt.AWTEventMulticaster; import java.awt.Color; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.nio.file.Path; import java.util.ArrayList; import java.util.BitSet; import java.util.Enumeration; import java.util.List; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import com.bytezone.diskbrowser.applefile.AbstractFile; import com.bytezone.diskbrowser.applefile.AppleFileSource; import com.bytezone.diskbrowser.applefile.BootSector; import com.bytezone.diskbrowser.gui.DataSource; // -----------------------------------------------------------------------------------// public abstract class AbstractFormattedDisk implements FormattedDisk // -----------------------------------------------------------------------------------// { protected Disk disk; protected FormattedDisk parent; // used by Dual-dos disks protected ActionListener actionListenerList; protected JTree catalogTree; protected Path originalPath; protected List sectorTypesList = new ArrayList<> (); protected List fileEntries = new ArrayList<> (); public SectorType[] sectorTypes; protected BootSector bootSector; public final SectorType emptySector = new SectorType ("Unused (empty)", Color.white); public final SectorType usedSector = new SectorType ("Unused (data)", Color.yellow); protected int falsePositives; protected int falseNegatives; protected Dimension gridLayout; protected BitSet freeBlocks; protected BitSet usedBlocks; // still to be populated - currently using stillAvailable () // ---------------------------------------------------------------------------------// public AbstractFormattedDisk (Disk disk) // ---------------------------------------------------------------------------------// { this.disk = disk; freeBlocks = new BitSet (disk.getTotalBlocks ()); usedBlocks = new BitSet (disk.getTotalBlocks ()); /* * All formatted disks will have empty and/or used sectors, so set them * here, and let the actual subclass add its sector types later. This list * is used to hold one of each sector type so that the DiskLayoutPanel can * draw its grid and key correctly. Every additional type that the instance * creates should be added here too. */ sectorTypesList.add (emptySector); sectorTypesList.add (usedSector); /* * Hopefully every used sector will be changed by the subclass to something * sensible, but deleted files will always leave the sector as used/unknown * as it contains data. */ setSectorTypes (); setGridLayout (); /* * Create the disk name as the root for the catalog tree. Subclasses will * have to append their catalog entries to this node. */ String name = getName (); if (name.endsWith (".tmp")) name = "tmp.dsk"; DefaultAppleFileSource afs = // new DefaultAppleFileSource (name, disk.toString (), this); new DefaultAppleFileSource (name, new DefaultDataSource (disk), this); DefaultMutableTreeNode root = new DefaultMutableTreeNode (afs); DefaultTreeModel treeModel = new DefaultTreeModel (root); catalogTree = new JTree (treeModel); treeModel.setAsksAllowsChildren (true); // allows empty nodes to appear as folders /* * Add an ActionListener to the disk in case the interleave or blocksize * changes */ disk.addActionListener (new ActionListener () { @Override public void actionPerformed (ActionEvent e) { setSectorTypes (); } }); } // ---------------------------------------------------------------------------------// protected void setEmptyByte (byte value) // ---------------------------------------------------------------------------------// { getDisk ().setEmptyByte (value); setSectorTypes (); } // ---------------------------------------------------------------------------------// private void setSectorTypes () // ---------------------------------------------------------------------------------// { sectorTypes = new SectorType[disk.getTotalBlocks ()]; for (DiskAddress da : disk) sectorTypes[da.getBlockNo ()] = disk.isBlockEmpty (da) ? emptySector : usedSector; setGridLayout (); } // ---------------------------------------------------------------------------------// private void setGridLayout () // ---------------------------------------------------------------------------------// { int totalBlocks = disk.getTotalBlocks (); Dimension newGridLayout = switch (totalBlocks) { case 280 -> new Dimension (8, 35); case 455 -> new Dimension (13, 35); case 560 -> new Dimension (16, 35); case 704 -> new Dimension (16, 44); case 768 -> new Dimension (16, 48); case 800 -> new Dimension (8, 100); case 1600 -> { if (disk.getBlocksPerTrack () == 32) yield new Dimension (32, 50); else yield new Dimension (16, 100); } case 2048 -> new Dimension (8, 256); case 3200 -> new Dimension (16, 200); default -> { int[] sizes = { 32, 20, 16, 8 }; for (int size : sizes) if ((totalBlocks % size) == 0) yield new Dimension (size, totalBlocks / size); yield null; } }; if (newGridLayout == null) System.out.println ("Unusable total blocks : " + totalBlocks); else gridLayout = newGridLayout; } // ---------------------------------------------------------------------------------// @Override public Dimension getGridLayout () // ---------------------------------------------------------------------------------// { return gridLayout; } // ---------------------------------------------------------------------------------// @Override public Disk getDisk () // ---------------------------------------------------------------------------------// { return disk; } // ---------------------------------------------------------------------------------// @Override public FormattedDisk getParent () // ---------------------------------------------------------------------------------// { return parent; } // ---------------------------------------------------------------------------------// @Override public void setParent (FormattedDisk disk) // ---------------------------------------------------------------------------------// { parent = disk; } // ---------------------------------------------------------------------------------// @Override public void setOriginalPath (Path path) // ---------------------------------------------------------------------------------// { this.originalPath = path; DefaultMutableTreeNode root = getCatalogTreeRoot (); if (root.getUserObject () == null) root.setUserObject (new DefaultAppleFileSource (getName (), disk.toString (), this)); } // ---------------------------------------------------------------------------------// @Override public Path getOriginalPath () // ---------------------------------------------------------------------------------// { return originalPath; } // ---------------------------------------------------------------------------------// @Override public String getAbsolutePath () // ---------------------------------------------------------------------------------// { if (originalPath != null) return originalPath.toString (); return disk.getFile ().getAbsolutePath (); } // ---------------------------------------------------------------------------------// @Override public String getDisplayPath () // ---------------------------------------------------------------------------------// { String home = System.getProperty ("user.home"); String path = originalPath != null ? originalPath.toString () : disk.getFile ().getAbsolutePath (); int pos = path.indexOf (home); if (pos == 0 || (path.startsWith ("/Volumes/") && pos > 0)) return "~" + path.substring (home.length () + pos); return disk.getFile ().getAbsolutePath (); } // ---------------------------------------------------------------------------------// @Override public boolean isTempDisk () // ---------------------------------------------------------------------------------// { return originalPath != null; } // ---------------------------------------------------------------------------------// @Override public String getName () // ---------------------------------------------------------------------------------// { if (originalPath != null) { Path path = originalPath.getFileName (); if (path != null) return path.toString (); } return disk.getFile ().getName (); } // ---------------------------------------------------------------------------------// @Override public void writeFile (AbstractFile file) // ---------------------------------------------------------------------------------// { System.out.println ("not implemented yet"); } // ---------------------------------------------------------------------------------// @Override public List getCatalogList () // ---------------------------------------------------------------------------------// { return fileEntries; } // ---------------------------------------------------------------------------------// @Override public AppleFileSource getFile (String uniqueName) // ---------------------------------------------------------------------------------// { for (AppleFileSource afs : fileEntries) if (afs.getUniqueName ().equals (uniqueName)) return afs; return null; } // ---------------------------------------------------------------------------------// @Override public JTree getCatalogTree () // ---------------------------------------------------------------------------------// { return catalogTree; } // ---------------------------------------------------------------------------------// public DefaultMutableTreeNode getCatalogTreeRoot () // ---------------------------------------------------------------------------------// { return (DefaultMutableTreeNode) catalogTree.getModel ().getRoot (); } // ---------------------------------------------------------------------------------// public void makeNodeVisible (DefaultMutableTreeNode node) // ---------------------------------------------------------------------------------// { catalogTree.makeVisible ( new TreePath (((DefaultTreeModel) catalogTree.getModel ()).getPathToRoot (node))); } // ---------------------------------------------------------------------------------// protected DefaultMutableTreeNode findNode (DefaultMutableTreeNode node, String name) // ---------------------------------------------------------------------------------// { Enumeration children = node.breadthFirstEnumeration (); if (children != null) while (children.hasMoreElements ()) { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) children.nextElement (); if (childNode.getUserObject ().toString ().indexOf (name) > 0) return childNode; } return null; } /* * These routines just hand back the information that was created above, and * added to by the subclass. */ // ---------------------------------------------------------------------------------// @Override public SectorType getSectorType (int block) // ---------------------------------------------------------------------------------// { return getSectorType (disk.getDiskAddress (block)); } // ---------------------------------------------------------------------------------// @Override public SectorType getSectorType (int track, int sector) // ---------------------------------------------------------------------------------// { return getSectorType (disk.getDiskAddress (track, sector)); } // ---------------------------------------------------------------------------------// @Override public SectorType getSectorType (DiskAddress da) // ---------------------------------------------------------------------------------// { return sectorTypes[da.getBlockNo ()]; } // ---------------------------------------------------------------------------------// @Override public List getSectorTypeList () // ---------------------------------------------------------------------------------// { return sectorTypesList; } // ---------------------------------------------------------------------------------// @Override public void setSectorType (int block, SectorType type) // ---------------------------------------------------------------------------------// { if (block < sectorTypes.length) sectorTypes[block] = type; else System.out.println ("setSectorType: Invalid block number: " + block); } // Override this so that the correct sector type can be displayed // ---------------------------------------------------------------------------------// @Override public DataSource getFormattedSector (DiskAddress da) // ---------------------------------------------------------------------------------// { if (da.isZero () && bootSector != null) return bootSector; SectorType sectorType = sectorTypes[da.getBlockNo ()]; byte[] buffer = disk.readBlock (da); // String address = String.format ("%02X %02X", da.getTrackNo (), da.getSectorNo ()); String address = String.format ("%02X", da.getBlockNo ()); if (sectorType == emptySector) return new DefaultSector ("Empty sector at " + address, disk, buffer, da); if (sectorType == usedSector) return new DefaultSector ("Orphan sector at " + address, disk, buffer, da); String name = getSectorFilename (da); if (!name.isEmpty ()) name = " : " + name; return new DefaultSector ("Data sector at " + address + name, disk, buffer, da); } /* * Override this with something useful */ // ---------------------------------------------------------------------------------// @Override public AppleFileSource getCatalog () // ---------------------------------------------------------------------------------// { return new DefaultAppleFileSource (disk.toString (), this); } // ---------------------------------------------------------------------------------// @Override public String getSectorFilename (DiskAddress da) // ---------------------------------------------------------------------------------// { for (AppleFileSource entry : fileEntries) if (entry.contains (da)) return (entry).getUniqueName (); return ""; } // ---------------------------------------------------------------------------------// @Override public int clearOrphans () // ---------------------------------------------------------------------------------// { System.out.println ("Not implemented yet"); return 0; } // ---------------------------------------------------------------------------------// @Override public boolean isSectorFree (DiskAddress da) // ---------------------------------------------------------------------------------// { return freeBlocks.get (da.getBlockNo ()); } // ---------------------------------------------------------------------------------// @Override public boolean isSectorFree (int blockNo) // ---------------------------------------------------------------------------------// { return freeBlocks.get (blockNo); } // representation of the Free Sector Table // ---------------------------------------------------------------------------------// @Override public void setSectorFree (int block, boolean free) // ---------------------------------------------------------------------------------// { if (block < 0 || block >= freeBlocks.size ()) { System.out.printf ("Block %d not in range : 0-%d%n", block, freeBlocks.size () - 1); return; } //assert block < freeBlocks.size () : String.format ("Set free block # %6d, size %6d", // block, freeBlocks.size ()); freeBlocks.set (block, free); } // Check that the sector hasn't already been flagged as part of the disk structure // ---------------------------------------------------------------------------------// @Override public boolean stillAvailable (DiskAddress da) // ---------------------------------------------------------------------------------// { return stillAvailable (da.getBlockNo ()); } // ---------------------------------------------------------------------------------// @Override public boolean stillAvailable (int blockNo) // ---------------------------------------------------------------------------------// { return sectorTypes[blockNo] == usedSector || sectorTypes[blockNo] == emptySector; } // ---------------------------------------------------------------------------------// @Override public void verify () // ---------------------------------------------------------------------------------// { System.out.println ("Sectors to clean :"); for (int i = 0, max = disk.getTotalBlocks (); i < max; i++) { if (freeBlocks.get (i)) { if (sectorTypes[i] == usedSector) System.out.printf ("%04X clean%n", i); } else { if (sectorTypes[i] == usedSector) System.out.printf ("%04X *** error ***%n", i); } } } // VTOC flags sector as free, but it is in use by a file // ---------------------------------------------------------------------------------// @Override public int falsePositiveBlocks () // ---------------------------------------------------------------------------------// { return falsePositives; } // VTOC flags sector as in use, but no file is using it (and not in the DOS tracks) // ---------------------------------------------------------------------------------// @Override public int falseNegativeBlocks () // ---------------------------------------------------------------------------------// { return falseNegatives; } // ---------------------------------------------------------------------------------// public void addActionListener (ActionListener actionListener) // ---------------------------------------------------------------------------------// { actionListenerList = AWTEventMulticaster.add (actionListenerList, actionListener); } // ---------------------------------------------------------------------------------// public void removeActionListener (ActionListener actionListener) // ---------------------------------------------------------------------------------// { actionListenerList = AWTEventMulticaster.remove (actionListenerList, actionListener); } // ---------------------------------------------------------------------------------// public void notifyListeners (String text) // ---------------------------------------------------------------------------------// { if (actionListenerList != null) actionListenerList .actionPerformed (new ActionEvent (this, ActionEvent.ACTION_PERFORMED, text)); } // ---------------------------------------------------------------------------------// @Override public String toString () // ---------------------------------------------------------------------------------// { StringBuilder text = new StringBuilder (); text.append (String.format ("Disk name ............. %s%n", getDisplayPath ())); text.append (String.format ("Block size..... %d%n", disk.getBlockSize ())); text.append (String.format ("Interleave..... %d", disk.getInterleave ())); return text.toString (); } }