Finally, we need to specify the extension(s) and magic signature of the format.
Firstly, the extension is defined in `FileType::from_ext()`:
```rust
pub fn from_ext<E>(ext: E) -> Option<Self>
where
E: AsRef<OsStr>,
{
let ext = ext.as_ref().to_str()?.to_ascii_lowercase();
match ext.as_str() {
"foo" => Some(Self::Foo),
// ...
}
}
```
Then we can check the magic signature in `FileType::quick_type_guess()`:
```rust
fn quick_type_guess(buf: &[u8]) -> Option<Self> {
use crate::mpeg::header::verify_frame_sync;
// Safe to index, since we return early on an empty buffer
match buf[0] {
0x0F if buf.starts_with(0x0F00) => Some(Self::Foo),
// ...
}
}
```
Now that we have the `FileType` variant fully specified, we need to add it to `lofty_attr`.
Go to [lofty_attr/src/internal.rs](../lofty_attr/src/internal.rs) and add the variant to `LOFTY_FILE_TYPES`.
```rust
const LOFTY_FILE_TYPES: [&str; N] = [
"Foo", // ...
];
```
### The File Struct
Now we can define our file struct in `src/foo/mod.rs`.
Unless there is additional information to provide from the format, such as [`Mp4File::ftyp()`](https://docs.rs/lofty/latest/lofty/mp4/struct.Mp4File.html#method.ftyp),
this file can simply be a struct definition (with exports as necessary).
```rust
mod read;
mod properties;
use crate::ape::tag::ApeTag;
use crate::id3::v2::tag::Id3v2Tag;
use crate::properties::FileProperties;
// This does most of the work
use lofty_attr::LoftyFile;
/// A Foo file
#[derive(LoftyFile)]
#[lofty(read_fn = "read::read_from")]
pub struct FooFile {
#[lofty(tag_type = "Id3v2")]
pub(crate) id3v2_tag: Option<Id3v2Tag>,
#[lofty(tag_type = "Ape")]
pub(crate) ape_tag: Option<ApeTag>,
pub(crate) properties: FileProperties,
}
```
And the file is now defined!
This is essentially the same as the [custom resolver example](https://github.com/Serial-ATA/lofty-rs/blob/main/examples/custom_resolver/src/main.rs),
except we do not need to go through the `FileResolver` API nor specify a `FileType` (this is handled by `lofty_attr`).
### Reading the File
The file reading is handled in `read.rs`, housing a function with the following signature:
* You will need to verify the file's magic signature again
* You should only gather the information necessary for property reading (such as additional chunks) only if
`parse_options.read_properties` is true.
* There should be no handling of properties here, that is saved for `properties.rs`
* There are many utilities to easily find and parse tags, such as `crate::id3::{find_id3v2, find_id3v1, find_lyrics3v2}, crate::ape::tag::read::{read_ape_tag}, etc.`
* And most importantly, look at existing implementations! There is a high chance that what is being attempted has already been done
Before creating integration tests, make a version of your file that has all possible tags in it. For example, a Foo file with an ID3v2 tag and an APE tag.
Put this file in `tests/files/assets/minimal/full_test.{ext}`