Skip to content

Commit

Permalink
Better support sparse downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
whscullin committed Jan 20, 2025
1 parent dabda76 commit cce5719
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 86 deletions.
17 changes: 10 additions & 7 deletions js/cards/cffa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import createBlockDisk from '../formats/block';
import {
BlockDisk,
BlockFormat,
BlockStorage,
Disk,
DRIVE_NUMBERS,
MassStorage,
MassStorageData,
MemoryBlockDisk,
} from 'js/formats/types';
Expand Down Expand Up @@ -96,9 +96,7 @@ export interface CFFAState {
disks: Array<CFFADiskState | null>;
}

export default class CFFA
implements Card, MassStorage<BlockFormat>, Restorable<CFFAState>
{
export default class CFFA implements Card, BlockStorage, Restorable<CFFAState> {
// CFFA internal Flags

private _disableSignalling = false;
Expand Down Expand Up @@ -505,7 +503,7 @@ export default class CFFA
diskState.disk.readOnly,
diskState.blocks
);
await this.setBlockVolume(idx, disk);
await this.setBlockDisk(idx, disk);
} else {
this.resetBlockVolume(idx);
}
Expand All @@ -528,7 +526,7 @@ export default class CFFA
}
}

async setBlockVolume(drive: number, disk: BlockDisk): Promise<void> {
async setBlockDisk(drive: number, disk: BlockDisk): Promise<void> {
drive = drive - 1;
const partition = this._partitions[drive];
if (!partition) {
Expand All @@ -549,6 +547,11 @@ export default class CFFA
}
}

async getBlockDisk(drive: number): Promise<BlockDisk | null> {
drive = drive - 1;
return this._partitions[drive];
}

// Assign a raw disk image to a drive. Must be 2mg or raw PO image.

setBinary(
Expand Down Expand Up @@ -576,7 +579,7 @@ export default class CFFA
};
const disk = createBlockDisk(format, options);

return this.setBlockVolume(drive, disk);
return this.setBlockDisk(drive, disk);
}

async getBinary(drive: number): Promise<MassStorageData | null> {
Expand Down
11 changes: 8 additions & 3 deletions js/cards/smartport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { debug, toHex } from '../util';
import { rom as smartPortRom } from '../roms/cards/smartport';
import { Card, Restorable, byte, word, rom } from '../types';
import {
MassStorage,
BlockDisk,
BlockFormat,
MassStorageData,
DiskFormat,
Disk,
MemoryBlockDisk,
DRIVE_NUMBERS,
BlockStorage,
} from '../formats/types';
import { CPU6502, flags } from '@whscullin/cpu6502';
import {
Expand Down Expand Up @@ -154,7 +154,7 @@ const DEVICE_TYPE_SCSI_HD = 0x07;
// $0E: Clock
// $0F: Modem
export default class SmartPort
implements Card, MassStorage<BlockFormat>, Restorable<SmartPortState>
implements Card, BlockStorage, Restorable<SmartPortState>
{
private rom: rom;
private disks: BlockDisk[] = [];
Expand Down Expand Up @@ -662,12 +662,17 @@ export default class SmartPort

async setBlockDisk(driveNo: DriveNumber, disk: BlockDisk) {
this.disks[driveNo] = disk;
this.ext[driveNo] = disk.format;
const volumeName = await this.getVolumeName(driveNo);
const name = volumeName || disk.metadata.name;

this.callbacks?.label(driveNo, name);
}

async getBlockDisk(driveNo: DriveNumber): Promise<BlockDisk> {
return this.disks[driveNo];
}

resetBlockDisk(driveNo: DriveNumber) {
delete this.disks[driveNo];
}
Expand Down Expand Up @@ -710,7 +715,7 @@ export default class SmartPort
return null;
}
const disk = this.disks[drive];
const ext = this.ext[drive];
const ext = this.disks[drive].format;
const { readOnly } = disk;
const { name } = disk.metadata;
let data: ArrayBuffer;
Expand Down
80 changes: 53 additions & 27 deletions js/components/debugger/Disks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { h, Fragment } from 'preact';
import { h, Fragment, JSX } from 'preact';
import { useEffect, useMemo } from 'preact/hooks';
import cs from 'classnames';
import { Apple2 as Apple2Impl } from 'js/apple2';
Expand All @@ -8,6 +8,7 @@ import {
DriveNumber,
FloppyDisk,
isBlockDiskFormat,
isBlockStorage,
isNibbleDisk,
MassStorage,
} from 'js/formats/types';
Expand Down Expand Up @@ -144,6 +145,41 @@ const DirectoryListing = ({
setFileData,
}: DirectoryListingProps) => {
const [open, setOpen] = useState(depth === 0);
const [children, setChildren] = useState<JSX.Element[]>([]);
useEffect(() => {
const load = async () => {
const children: JSX.Element[] = [];
for (let idx = 0; idx < dirEntry.entries.length; idx++) {
const fileEntry = dirEntry.entries[idx];
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
const dirEntry = new Directory(volume, fileEntry);
await dirEntry.init();
children.push(
<DirectoryListing
key={idx}
depth={depth + 1}
volume={volume}
dirEntry={dirEntry}
setFileData={setFileData}
/>
);
} else {
children.push(
<FileListing
key={idx}
depth={depth + 1}
volume={volume}
fileEntry={fileEntry}
setFileData={setFileData}
/>
);
}
}
setChildren(children);
};
void load();
}, [depth, dirEntry, setFileData, volume]);

return (
<>
<tr>
Expand All @@ -167,31 +203,7 @@ const DirectoryListing = ({
<td>{formatDate(dirEntry.creation)}</td>
<td></td>
</tr>
{open &&
dirEntry.entries.map((fileEntry, idx) => {
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
const dirEntry = new Directory(volume, fileEntry);
return (
<DirectoryListing
key={idx}
depth={depth + 1}
volume={volume}
dirEntry={dirEntry}
setFileData={setFileData}
/>
);
} else {
return (
<FileListing
key={idx}
depth={depth + 1}
volume={volume}
fileEntry={fileEntry}
setFileData={setFileData}
/>
);
}
})}
{open && children}
</>
);
};
Expand Down Expand Up @@ -300,6 +312,21 @@ const DiskInfo = ({ massStorage, driveNo, setFileData }: DiskInfoProps) => {
const [proDOSData, setProDOSData] = useState<ProDOSData>();
useEffect(() => {
const load = async () => {
if (isBlockStorage(massStorage)) {
const disk = await massStorage.getBlockDisk(driveNo);
if (disk) {
const prodos = new ProDOSVolume(disk);
const vdh = await prodos.vdh();
const bitMap = await prodos.bitMap();
const freeBlocks = await bitMap.freeBlocks();
const freeCount = freeBlocks.length;
setProDOSData({ freeCount, prodos, vdh });
} else {
setProDOSData(undefined);
}
setDisk(disk);
return;
}
const massStorageData = await massStorage.getBinary(driveNo, 'po');
if (massStorageData) {
const { data, readOnly, ext } = massStorageData;
Expand Down Expand Up @@ -343,7 +370,6 @@ const DiskInfo = ({ massStorage, driveNo, setFileData }: DiskInfoProps) => {
}
setDisk(disk);
}
return null;
};
void load();
}, [massStorage, driveNo]);
Expand Down
83 changes: 60 additions & 23 deletions js/components/util/http_block_disk.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { HeaderData, read2MGHeader } from 'js/formats/2mg';
import {
BlockDisk,
BlockFormat,
DiskMetadata,
ENCODING_BLOCK,
} from 'js/formats/types';

class Deferred<T> {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: Error) => void;

constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}

export class HttpBlockDisk implements BlockDisk {
encoding: typeof ENCODING_BLOCK = ENCODING_BLOCK;
format: BlockFormat = 'po';
metadata: DiskMetadata;
readOnly: boolean = false;
headerData: HeaderData | null = null;

blocks: Uint8Array[] = [];
fetchMap: Promise<Response>[] = [];
fetchMap: Deferred<boolean>[] = [];

constructor(
name: string,
Expand All @@ -22,40 +37,61 @@ export class HttpBlockDisk implements BlockDisk {
this.metadata = { name };
}

private async getHeaderData(): Promise<HeaderData | null> {
if (this.format === '2mg') {
if (!this.headerData) {
const header = await fetch(this.url, {
headers: { range: 'bytes=0-63' },
});
const headerBody = await header.arrayBuffer();
this.headerData = read2MGHeader(headerBody);
}
return this.headerData;
}
return null;
}

async blockCount(): Promise<number> {
return this.contentLength;
const headerData = await this.getHeaderData();
if (headerData) {
return headerData.bytes >> 9;
} else {
return this.contentLength >> 9;
}
}

async read(blockNumber: number): Promise<Uint8Array> {
const blockCount = 5;
const blockShift = 5;
if (!this.blocks[blockNumber]) {
const fetchBlock = blockNumber >> blockCount;
const fetchPromise = this.fetchMap[fetchBlock];
if (fetchPromise !== undefined) {
const response = await fetchPromise;
if (!response.ok) {
throw new Error(`Error loading: ${response.statusText}`);
}
if (!response.body) {
throw new Error('Error loading: no body');
}
const fetchBlock = blockNumber >> blockShift;
const deferred = this.fetchMap[fetchBlock];
if (deferred !== undefined) {
await deferred.promise;
} else {
const start = 512 * (fetchBlock << blockCount);
const end = start + (512 << blockCount);
this.fetchMap[fetchBlock] = fetch(this.url, {
const deferred = new Deferred<boolean>();
this.fetchMap[fetchBlock] = deferred;
const headerData = await this.getHeaderData();
const headerSize = headerData?.offset ?? 0;
const start = 512 * (fetchBlock << blockShift) + headerSize;
const end = start + (512 << blockShift) - 1;
const response = await fetch(this.url, {
headers: { range: `bytes=${start}-${end}` },
});
const response = await this.fetchMap[fetchBlock];
if (!response.ok) {
throw new Error(`Error loading: ${response.statusText}`);
const error = new Error(
`Error loading: ${response.statusText}`
);
deferred.reject(error);
throw error;
}
if (!response.body) {
throw new Error('Error loading: no body');
const error = new Error('Error loading: no body');
deferred.reject(error);
throw error;
}
const blob = await response.blob();
const buffer = await new Response(blob).arrayBuffer();
const startBlock = fetchBlock << blockCount;
const endBlock = startBlock + (1 << blockCount);
const buffer = await response.arrayBuffer();
const startBlock = fetchBlock << blockShift;
const endBlock = startBlock + (1 << blockShift);
let startOffset = 0;
for (let idx = startBlock; idx < endBlock; idx++) {
const endOffset = startOffset + 512;
Expand All @@ -64,6 +100,7 @@ export class HttpBlockDisk implements BlockDisk {
);
startOffset += 512;
}
deferred.resolve(true);
}
}
return this.blocks[blockNumber];
Expand Down
7 changes: 4 additions & 3 deletions js/formats/prodos/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,21 @@ export function writeFileName(block: DataView, offset: word, name: string) {
return caseBits;
}

export function dumpDirectory(
export async function dumpDirectory(
volume: ProDOSVolume,
dirEntry: FileEntry,
depth: string
) {
const dir = new Directory(volume, dirEntry);
await dir.init();
let str = '';

for (let idx = 0; idx < dir.entries.length; idx++) {
const fileEntry = dir.entries[idx];
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
str += depth + fileEntry.name + '\n';
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
str += dumpDirectory(volume, fileEntry, depth + ' ');
str += await dumpDirectory(volume, fileEntry, depth + ' ');
}
}
}
Expand All @@ -113,7 +114,7 @@ export async function dump(volume: ProDOSVolume) {
const fileEntry = vdh.entries[idx];
str += fileEntry.name + '\n';
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
str += dumpDirectory(volume, fileEntry, ' ');
str += await dumpDirectory(volume, fileEntry, ' ');
}
}
return str;
Expand Down
Loading

0 comments on commit cce5719

Please sign in to comment.