---
title: "ADF S3 Class Objects"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{ADF Objects}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

The `adfExplorer` package has defined several S3 class objects to make interaction with
virtual ADF devices easier. This page presents these objects, what they are for and how
they can be used.

## `adf_device`

### What is it?

Amiga Disk Files (ADF) or file representations of hardware disks. The `adf_device` class is
used to represent a connection to such files. It can be seen as a virtual device. The file
remains on disk, the `adf_device` opens a file connection to it. Underneath the S3 class
the object has the type `externalptr`.

### How can it be initialized?

The `adf_device` can be initiated by opening an ADF or ADZ (a zipped ADF) file with
`connect_adf()`.

```{r setup}
library(adfExplorer, warn.conflicts = FALSE)
adz_file <- system.file("example.adz", package = "adfExplorer")
my_device <- connect_adf(adz_file)
```

It can also be initiated by creating a new device with `create_adf_device()`. As
the device needs to be stored as a file, you need to specify its destination path.
The example below uses a temporary file for this purpose.

```{r create}
adf_file <- tempfile(fileext = ".adf")
new_device <- create_adf_device(adf_file)
```

### What can I do with it?

Well, that depends. When you just created a device with `create_adf_device()`, like the
object named `new_device` in the example above, it does not contain a file system (see
`vignette("file_system_modes")`). Virtual disks without a file system, could contain
unspecified data or a custom track loader, which can contain instructions running
independently from the operating system. You can inspect those disks by reading and writing
[blocks](#adf_block), the logical unit of data on a disk.

When a virtual disk does contain a file system, like the one opened with `connect_adf()`
stored as the object named `my_device`, you can do a lot more. You can query the files and
directories on the disk. Read, write, copy, move, manipulate and delete those files.
The example below shows how to list the entries (files and directories) on the disk's
root.

```{r list-entries}
list_adf_entries(my_device)
```

### End of life

As any `externalptr` object an `adf_device` will be cleaned up automatically by R's garbage collector
when it goes out of scope. However, as it maintains an open connection to a file on disk,
it is always wise to `close()` the device when you are done with it. Calling `close()` on an
`adf_device` will also automatically close all nested connections to files on the virtual device
(see also [`adf_file_con`](#adf_file_con))

```{r close-devices}
close(new_device)
## Let's keep `my_device` open to be used in examples below
```

## `adf_file_con`

### What is it?

It is a connection to a file on a virtual device represented by the [`adf_device`](#adf_device) class
objects. Well... Technically, it isn't a connection really, because CRAN's policy does not allow
to call non API entry points into R. This would be required to setup a proper connection. Instead,
the `adf_file_con` is a mockup using an `externalptr` type to mimic R connections.
In essence, it behaves very much like any other connection in R (more details below).

### How can it be initialized?

An `adf_file_con` object can be initiated using a call to `adf_file_con()`. For this purpose,
you first need to connect to a virtual device containing a file system. We'll use `my_device`
opened in earlier examples. We can use the path to a file on a virtual device to open
a connection as shown below.

```{r open-con}
con <- adf_file_con(my_device, "DF0:mods/mod.intro")

summary(con)
```

If you want to open writable connections to a virtual device, you need to initiate the
device without write protection. A writable connection can also be used to create new
files on the virtual device.

### What can I do with it?

Depending on how you set the option `writable` when calling `adf_file_con`, you can either
read and / or write to the connection. By default the connection is opened as read-only.
You can use `readBin()` to read binary data from the connection.

```{r read-bin}
readBin(con, "raw", 20L)
```

Note that `adf_file_con()` always opens the connection as 'binary', but you can also use it
for reading and writing text. So basically, `readLines()`, `writeBin()` and `writeLines()`
are all available (the latter two obviously when the connection is writable).

In addition, you can tell where the current byte offset in the file is located with `seek()`.
You can also use it to set the offset to a specific location.

```{r seek}
seek(con)
seek(con, 30L)
```

### End of life

Like any connection it is good practice to `close()` it when you are done. Any `adf_file_con`
still open when its parent `adf_device` is closed, will automatically be closed. It can
no longer be accessed when the virtual device is not available.

```{r close-file}
close(con)
```

## `virtual_path`

### What is it?

It is a vectorised `list` of `list`s. The nested list contains two elements:

  1. An `adf_device` class object
  2. A `character` string, specifying a path to a file or directory on the virtual device.

The outer list just contains a collection of these.

### How can it be initialized?

It can be initialised by calling `virtual_path()`.

```{r virtual-path}
virtual_path(my_device, "DF0:s/startup-sequence")
virtual_path(my_device, "idontexist")
```

Note that the file does not necessarily have to exist in order to create a virtual path.
It doesn't create any kind of connection to the file, but you can use it to open one. 

### What can I do with it?

The `virtual_path` is a means to refer to files and directory on a virtual device.
It also helps to set up processes with the pipe operator (`|>`).
There is a `vignette("virtual_path")` dedicated to describing the object and its usage
in more detail.

### End of life

It is just a list. You can just call `rm()` on it to get rid of it.

## `adf_block`

### What is it?

A representation of `raw` data of a logical unit on a virtual device of 512 bytes.
It is simply a `vector` of `raw` data.

### How can it be initialized?

A new block block can be created with `new_adf_block()`. It will be initialised with
null bytes. You can also coerce a `raw` `vector` to an `adf_block`.

```{r init-block}
block1 <- new_adf_block()
## a block with random data
block2 <- as_adf_block(as.raw(sample.int(n=256L, size = 512L, replace = TRUE) - 1L))
```

You can also intialise a block by reading it from a virtual device

```{r read-block}
## This will read the initial 'boot' block
## from the virtual device
block3 <- read_adf_block(my_device, 0L)
```

### What can I do with it?

You can read blocks from a virtual disk with `read_adf_block()` and
write them to a specific sector on the virtual disk with `write_adf_block()`.
But be careful you can damage the file system or track loader of
the virtual disk if you don't know what you are doing.
<!-- add link to block vignette with more detail here once it is written -->

### End of life

It can just be removed from memory by calling `rm()`. As there is no actual
link to the virtual device, removing an `adf_block` class object from
memory does not affect your virtual device.

```{r remove-block}
rm(block1, block2, block3)
```