Inside the Einstein: The Disc, a TatungBytes course: XtalDOS listing the first practice disc image, on a rainbow sunburst
← Back to Modules
module
22

The add Command

Introduction

This is the command the whole course has been working towards: take a file from your computer and put it on an Einstein disc, so the Einstein can use it. Every piece is already written. add puts them together in the right order.

The Steps

  1. Check the name with split_name (S21), and refuse one already on the disc.
  2. Read the file from your computer. Refuse an empty one - there would be nothing to put in a block.
  3. Pad it to whole records. The disc counts in 128-byte records, so round the size up, and fill the gap with 1A - the Einstein's mark for the end of a text file (S16), and harmless at the end of any other file.
  4. Find space: as many free blocks as the file needs, lowest first, and one free slot (S20). If there are not enough, stop before writing anything.
  5. Write the data, one 2K block at a time, through block_sectors. The last block is filled out with E5, like the rest of the empty disc.
  6. Write the entry into the slot with make_entry (S21), and write the directory back.

The order matters. Data first, entry last: until the entry is written, the disc's directory still describes the disc exactly as it was.

Rounding up is S4's arithmetic: (size + 127) // 128 is the number of whole records needed for size bytes, and (length + 2047) // 2048 the number of blocks.

A Name For It

If you do not give a disc name, add uses the file's own name. os.path.basename takes the folder off a path: os.path.basename( "/Users/you/edisc/hello.py") is "hello.py", and split_name turns that into HELLO and PY.

The code

After make_entry's neighbours, below name_in_use:

def write_block(disc, block, data):
    places = block_sectors(block)
    for i in range(4):
        track, sector_id = places[i]
        write_sector(disc, track, sector_id, data[i * 512:i * 512 + 512])


def add_file(disc, path, disc_name):
    base, ext = split_name(disc_name)
    if name_in_use(disc, base + "." + ext):
        sys.exit(f"{base}.{ext} is already on this disc")
    with open(path, "rb") as f:
        data = f.read()
    if len(data) == 0:
        sys.exit(f"{path} is empty: there is nothing to add")
    if len(data) > 16384:
        sys.exit(f"{path} is over 16K: this version of edisc cannot add it yet")
    size = len(data)
    records = (size + 127) // 128
    data = data + bytes([0x1A]) * (records * 128 - size)
    count = (len(data) + 2047) // 2048
    blocks = free_blocks(disc)[:count]
    slots = free_slots(disc)
    if len(blocks) < count:
        sys.exit(f"The disc is full: {path} needs {count * 2}K")
    if len(slots) < 1:
        sys.exit("The disc's directory is full")
    for i in range(count):
        chunk = data[i * 2048:i * 2048 + 2048]
        write_block(disc, blocks[i], chunk + bytes([0xE5]) * (2048 - len(chunk)))
    directory = bytearray(read_directory(disc))
    slot = slots[0]
    directory[slot * 32:slot * 32 + 32] = make_entry(base, ext, 0, records, blocks)
    write_directory(disc, directory)
    print(f"Added {base}.{ext}: {size} bytes in block(s) {blocks}, slot {slot}")

free_blocks(disc)[:count] is the first count free blocks: the lowest. And before the else::

elif command == "add":
    disc = bytearray(disc)
    path = sys.argv[3]
    if len(sys.argv) > 4:
        add_file(disc, path, sys.argv[4])
    else:
        add_file(disc, path, os.path.basename(path))
    save_image(filename, disc)

(The whole program: s22-edisc.py.)

Run it

Put your first program, hello.py from S2, on a new disc, and numbers.py from S4 beside it under a shorter name:

python3 edisc.py create MINE.DSK
python3 edisc.py add MINE.DSK hello.py
python3 edisc.py add MINE.DSK numbers.py NUMS.PY
python3 edisc.py list MINE.DSK

What you should see (your byte counts may differ a little, if your files do)

Created MINE.DSK: a blank Einstein disc, 188K free
Added HELLO.PY: 89 bytes in block(s) [1], slot 0
Added NUMS.PY: 284 bytes in block(s) [2], slot 1
HELLO.PY         128 bytes    2K  1 extent(s)
NUMS.PY          384 bytes    2K  1 extent(s)
2 file(s): 4k Size, 184k Free, 190k Total

Then the Einstein, with MINE.DSK in drive 1:

0:DIR 1:
1: HELLO   .PY  : NUMS    .PY
4k Size, 184k Free, 190k Total

Your files, on an Einstein disc, listed by the Einstein - right names, right size, right free space.

A Staircase

Now DISP 1:HELLO.PY. If you saved hello.py on a Mac or Linux, you will see this:

XtalDOS displaying a file whose lines end with 0A alone

print("Hello from Python")
                          print(2 + 3)
                                      pr
int("The Einstein's discs have", 40, "tr
acks")

Nothing is wrong with the disc. Text files on the Einstein end each line with two bytes, 0D 0A: 0D (carriage return) sends the cursor back to the left edge, and 0A (line feed) moves it down a line. Macs and Linux end lines with 0A alone, so on the Einstein each line starts where the last one finished. (Editors on Windows usually write 0D 0A, and you may not see this at all.)

If a text file is meant for the Einstein, give it Einstein line endings first. data.replace(b"\n", b"\r\n") turns every 0A into 0D 0A; with that done, the same file shows properly:

The same file with 0D 0A line endings

Change one thing

  • Add a file to your copy FILES2.DSK from S21: add FILES2.DSK numbers.py. S20 predicted where the next file on that disc would go. Was it right? Check with free and entries.
  • add MINE.DSK hello.py again. What happens, and which line stopped it?
  • Write a small program that reads hello.py, does the replace above, and writes hello-einstein.py. Add that to a disc as HELLO2.PY and DISP it.
  • Try to add a file over 16K - S17's STORY.TXT, extracted in S18, will do. What does add say?

When it goes wrong

SymptomCause
DISP shows each line starting where the last one endedThe file's lines end with 0A alone; the Einstein expects 0D 0A
HELLO.PY is already on this discNames must be unique (in any mix of capitals)
big.txt is over 16K: this version of edisc cannot add it yetS23

Summary

add checks the name, reads the file, pads it with 1A to whole records, takes the lowest free blocks and the first free slot, writes the data a block at a time, and writes the entry last. The Einstein lists the result exactly as it lists its own files. Text going to the Einstein should have 0D 0A line endings.

Next

S23, Files Bigger Than 16K, Again - writing more than one extent.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.