Looks like we're going to have to rework the reader/writer/source/sink

interfaces, so do fluxsink. This lets us test for overwriting a flux
file on writing in the GUI. HG: Enter commit message.
This commit is contained in:
David Given
2025-10-14 21:54:59 +02:00
parent 2de8b52e56
commit 2d6cb22e3a
14 changed files with 578 additions and 417 deletions

View File

@@ -52,7 +52,7 @@ need to apply extra options to change the format if desired.
## Options
- :
- $format:
- `143`: 143kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod I
- `287`: 287kB 5.25" DSDD hard-sectored; Micropolis MetaFloppy Mod I
- `315`: 315kB 5.25" SSDD hard-sectored; Micropolis MetaFloppy Mod II

View File

@@ -410,7 +410,7 @@ static ReadGroupResult readGroup(const DiskLayout& diskLayout,
void writeTracks(const DiskLayout& diskLayout,
FluxSink& fluxSink,
FluxSink& fluxSinkFactory,
std::function<std::unique_ptr<const Fluxmap>(
const std::shared_ptr<const LogicalTrackLayout>&)> producer,
std::function<bool(const std::shared_ptr<const LogicalTrackLayout>&)>
@@ -419,63 +419,67 @@ void writeTracks(const DiskLayout& diskLayout,
{
log(BeginOperationLogMessage{"Encoding and writing to disk"});
if (fluxSink.isHardware())
if (fluxSinkFactory.isHardware())
measureDiskRotation();
int index = 0;
for (auto& ch : logicalLocations)
{
log(OperationProgressLogMessage{
index * 100 / (unsigned)logicalLocations.size()});
index++;
testForEmergencyStop();
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
auto fluxSink = fluxSinkFactory.create();
int index = 0;
for (auto& ch : logicalLocations)
{
for (int offset = 0; offset < ltl->groupSize;
offset += diskLayout.headWidth)
log(OperationProgressLogMessage{
index * 100 / (unsigned)logicalLocations.size()});
index++;
testForEmergencyStop();
const auto& ltl = diskLayout.layoutByLogicalLocation.at(ch);
int retriesRemaining = globalConfig()->decoder().retries();
for (;;)
{
unsigned physicalCylinder = ltl->physicalCylinder + offset;
unsigned physicalHead = ltl->physicalHead;
log(BeginWriteOperationLogMessage{
physicalCylinder, ltl->physicalHead});
if (offset == globalConfig()->drive().group_offset())
for (int offset = 0; offset < ltl->groupSize;
offset += diskLayout.headWidth)
{
auto fluxmap = producer(ltl);
if (!fluxmap)
goto erase;
unsigned physicalCylinder = ltl->physicalCylinder + offset;
unsigned physicalHead = ltl->physicalHead;
fluxSink.writeFlux(
physicalCylinder, physicalHead, *fluxmap);
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
log(BeginWriteOperationLogMessage{
physicalCylinder, ltl->physicalHead});
Fluxmap blank;
fluxSink.writeFlux(physicalCylinder, physicalHead, blank);
log("erased");
if (offset == globalConfig()->drive().group_offset())
{
auto fluxmap = producer(ltl);
if (!fluxmap)
goto erase;
fluxSink->addFlux(
physicalCylinder, physicalHead, *fluxmap);
log("writing {0} ms in {1} bytes",
int(fluxmap->duration() / 1e6),
fluxmap->bytes());
}
else
{
erase:
/* Erase this track rather than writing. */
Fluxmap blank;
fluxSink->addFlux(
physicalCylinder, physicalHead, blank);
log("erased");
}
log(EndWriteOperationLogMessage());
}
log(EndWriteOperationLogMessage());
if (verifier(ltl))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
if (verifier(ltl))
break;
if (retriesRemaining == 0)
error("fatal error on write");
log("retrying; {} retries remaining", retriesRemaining);
retriesRemaining--;
}
}
@@ -672,105 +676,111 @@ void readDiskCommand(const DiskLayout& diskLayout,
Decoder& decoder,
DecodedDisk& decodedDisk)
{
std::unique_ptr<FluxSink> outputFluxSink;
std::unique_ptr<FluxSink> outputFluxSinkFactory;
if (globalConfig()->decoder().has_copy_flux_to())
outputFluxSink =
outputFluxSinkFactory =
FluxSink::create(globalConfig()->decoder().copy_flux_to());
log(BeginOperationLogMessage{"Reading and decoding disk"});
unsigned index = 0;
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
{
log(OperationProgressLogMessage{
index * 100 / (unsigned)diskLayout.layoutByLogicalLocation.size()});
index++;
testForEmergencyStop();
auto [trackFluxes, trackSectors] =
readAndDecodeTrack(diskLayout, fluxSource, decoder, ltl);
for (const auto& flux : trackFluxes)
decodedDisk.tracksByPhysicalLocation.emplace(
CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead},
flux);
for (const auto& sector : trackSectors)
decodedDisk.sectorsByPhysicalLocation.emplace(
sector->physicalLocation.value(), sector);
if (outputFluxSink)
std::unique_ptr<FluxSink::Sink> outputFluxSink;
if (outputFluxSinkFactory)
outputFluxSink = outputFluxSinkFactory->create();
unsigned index = 0;
for (auto& [logicalLocation, ltl] : diskLayout.layoutByLogicalLocation)
{
for (const auto& data : trackFluxes)
outputFluxSink->writeFlux(data->ptl->physicalCylinder,
data->ptl->physicalHead,
*data->fluxmap);
}
log(OperationProgressLogMessage{
index * 100 /
(unsigned)diskLayout.layoutByLogicalLocation.size()});
index++;
if (globalConfig()->decoder().dump_records())
{
std::vector<std::shared_ptr<const Record>> sorted_records;
testForEmergencyStop();
for (const auto& data : trackFluxes)
sorted_records.insert(sorted_records.end(),
data->records.begin(),
data->records.end());
auto [trackFluxes, trackSectors] =
readAndDecodeTrack(diskLayout, fluxSource, decoder, ltl);
for (const auto& flux : trackFluxes)
decodedDisk.tracksByPhysicalLocation.emplace(
CylinderHead{
flux->ptl->physicalCylinder, flux->ptl->physicalHead},
flux);
for (const auto& sector : trackSectors)
decodedDisk.sectorsByPhysicalLocation.emplace(
sector->physicalLocation.value(), sector);
std::sort(sorted_records.begin(),
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
if (outputFluxSink)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
for (const auto& data : trackFluxes)
outputFluxSink->addFlux(data->ptl->physicalCylinder,
data->ptl->physicalHead,
*data->fluxmap);
}
}
if (globalConfig()->decoder().dump_sectors())
{
auto sectors = collectSectors(trackSectors, false);
std::ranges::sort(sectors,
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sectors)
if (globalConfig()->decoder().dump_records())
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
std::vector<std::shared_ptr<const Record>> sorted_records;
for (const auto& data : trackFluxes)
sorted_records.insert(sorted_records.end(),
data->records.begin(),
data->records.end());
std::sort(sorted_records.begin(),
sorted_records.end(),
[](const auto& o1, const auto& o2)
{
return o1->startTime < o2->startTime;
});
std::cout << "\nRaw (undecoded) records follow:\n\n";
for (const auto& record : sorted_records)
{
std::cout << fmt::format("I+{:.2f}us with {:.2f}us clock\n",
record->startTime / 1000.0,
record->clock / 1000.0);
hexdump(std::cout, record->rawData);
std::cout << std::endl;
}
}
if (globalConfig()->decoder().dump_sectors())
{
auto sectors = collectSectors(trackSectors, false);
std::ranges::sort(sectors,
[](const auto& o1, const auto& o2)
{
return *o1 < *o2;
});
std::cout << "\nDecoded sectors follow:\n\n";
for (const auto& sector : sectors)
{
std::cout << fmt::format(
"{}.{:02}.{:02}: I+{:.2f}us with {:.2f}us clock: "
"status {}\n",
sector->logicalCylinder,
sector->logicalHead,
sector->logicalSector,
sector->headerStartTime / 1000.0,
sector->clock / 1000.0,
Sector::statusToString(sector->status));
hexdump(std::cout, sector->data);
std::cout << std::endl;
}
}
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFluxes, trackSectors});
std::vector<std::shared_ptr<const Sector>> all_sectors;
for (auto& [ch, sector] : decodedDisk.sectorsByPhysicalLocation)
all_sectors.push_back(sector);
all_sectors = collectSectors(all_sectors);
decodedDisk.image = std::make_shared<Image>(all_sectors);
/* Log a _copy_ of the decodedDisk structure so that the logger
* doesn't see the decodedDisk get mutated in subsequent reads. */
log(DiskReadLogMessage{std::make_shared<DecodedDisk>(decodedDisk)});
}
/* track can't be modified below this point. */
log(TrackReadLogMessage{trackFluxes, trackSectors});
std::vector<std::shared_ptr<const Sector>> all_sectors;
for (auto& [ch, sector] : decodedDisk.sectorsByPhysicalLocation)
all_sectors.push_back(sector);
all_sectors = collectSectors(all_sectors);
decodedDisk.image = std::make_shared<Image>(all_sectors);
/* Log a _copy_ of the decodedDisk structure so that the logger doesn't
* see the decodedDisk get mutated in subsequent reads. */
log(DiskReadLogMessage{std::make_shared<DecodedDisk>(decodedDisk)});
}
if (!decodedDisk.image)

View File

@@ -17,227 +17,243 @@
#include <sys/types.h>
#include "fmt/chrono.h"
namespace
static uint32_t ticks_to_a2r(uint32_t ticks)
{
uint32_t ticks_to_a2r(uint32_t ticks)
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
}
class A2RSink : public FluxSink::Sink
{
public:
A2RSink(const std::string& filename):
_filename(filename),
_bytes{},
_writer(_bytes.writer())
{
return ticks * NS_PER_TICK / A2R_NS_PER_TICK;
time_t now{std::time(nullptr)};
auto t = gmtime(&now);
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
}
class A2RFluxSink : public FluxSink
~A2RSink()
{
public:
A2RFluxSink(const A2RFluxSinkProto& lconfig):
_config(lconfig),
_bytes{},
_writer{_bytes.writer()}
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_minCylinder = minCylinder;
_maxCylinder = maxCylinder;
_minHead = minHead;
_maxHead = maxHead;
log("A2R: writing A2R {} file containing {} tracks...",
(_minHead == _maxHead) ? "single sided" : "double sided",
_maxCylinder - _minCylinder + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{
time_t now{std::time(nullptr)};
auto t = gmtime(&now);
_metadata["image_date"] = fmt::format("{:%FT%TZ}", *t);
writer.append(i.first);
writer.write_8('\t');
writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
public:
void addFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
}
~A2RFluxSink()
// Writing from an image (as opposed to from a floppy) will
// contain exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
auto [minCylinder, maxCylinder, minHead, maxHead] =
diskLayout->getPhysicalBounds();
_minCylinder = minCylinder;
_maxCylinder = maxCylinder;
_minHead = minHead;
_maxHead = maxHead;
log("A2R: writing A2R {} file containing {} tracks...",
(_minHead == _maxHead) ? "single sided" : "double sided",
_maxCylinder - _minCylinder + 1);
writeHeader();
writeInfo();
writeStream();
writeMeta();
std::ofstream of(
_config.filename(), std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
_bytes.writeTo(of);
of.close();
}
private:
void writeChunkAndData(uint32_t chunk_id, const Bytes& data)
{
_writer.write_le32(chunk_id);
_writer.write_le32(data.size());
_writer += data;
}
void writeHeader()
{
static const uint8_t a2r2_fileheader[] = {
'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a};
_writer += Bytes(a2r2_fileheader, sizeof(a2r2_fileheader));
}
void writeInfo()
{
Bytes info;
auto writer = info.writer();
writer.write_8(A2R_INFO_CHUNK_VERSION);
auto version_str_padded = fmt::format("{: <32}", "FluxEngine");
assert(version_str_padded.size() == 32);
writer.append(version_str_padded);
writer.write_8(
(globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
? A2R_DISK_525
: A2R_DISK_35);
writer.write_8(1); // write protected
writer.write_8(1); // synchronized
writeChunkAndData(A2R_CHUNK_INFO, info);
}
void writeMeta()
{
Bytes meta;
auto writer = meta.writer();
for (auto& i : _metadata)
{
writer.append(i.first);
writer.write_8('\t');
writer.append(i.second);
writer.write_8('\n');
}
writeChunkAndData(A2R_CHUNK_META, meta);
}
void writeStream()
{
// A STRM always ends with a 255, even though this could ALSO
// indicate the first byte of a multi-byte sequence
_strmWriter.write_8(255);
writeChunkAndData(A2R_CHUNK_STRM, _strmBytes);
}
void writeFlux(int cylinder, int head, const Fluxmap& fluxmap) override
{
if (!fluxmap.bytes())
{
return;
}
// Writing from an image (as opposed to from a floppy) will contain
// exactly one revolution and no index events.
auto is_image = [](auto& fluxmap)
{
FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an image
// to an a2r
return fmr.eof();
};
// Write the flux data into its own Bytes
Bytes trackBytes;
auto trackWriter = trackBytes.writer();
auto write_one_flux = [&](unsigned ticks)
{
auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap);
fmr.skipToEvent(F_BIT_INDEX);
// but maybe there is no index, if we're writing from an
// image to an a2r
return fmr.eof();
};
auto write_flux = [&](unsigned maxTicks = ~0u)
{
unsigned ticksSinceLastPulse = 0;
// Write the flux data into its own Bytes
Bytes trackBytes;
auto trackWriter = trackBytes.writer();
while (!fmr.eof() && totalTicks < maxTicks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains 450
// degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy and
// should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
}
uint32_t chunk_size = 10 + trackBytes.size();
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
_strmWriter.write_8(cylinder);
else
_strmWriter.write_8((cylinder << 1) | head);
_strmWriter.write_8(A2R_TIMING);
_strmWriter.write_le32(trackBytes.size());
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
_strmWriter += trackBytes;
}
operator std::string() const override
auto write_one_flux = [&](unsigned ticks)
{
return fmt::format("a2r({})", _config.filename());
auto value = ticks_to_a2r(ticks);
while (value > 254)
{
trackWriter.write_8(255);
value -= 255;
}
trackWriter.write_8(value);
};
int revolution = 0;
uint32_t loopPoint = 0;
uint32_t totalTicks = 0;
FluxmapReader fmr(fluxmap);
auto write_flux = [&](unsigned maxTicks = ~0u)
{
unsigned ticksSinceLastPulse = 0;
while (!fmr.eof() && totalTicks < maxTicks)
{
unsigned ticks;
int event;
fmr.getNextEvent(event, ticks);
ticksSinceLastPulse += ticks;
totalTicks += ticks;
if (event & F_BIT_PULSE)
{
write_one_flux(ticksSinceLastPulse);
ticksSinceLastPulse = 0;
}
if (event & F_BIT_INDEX && revolution == 0)
{
loopPoint = totalTicks;
revolution += 1;
}
}
};
if (is_image(fluxmap))
{
// A timing stream with no index represents exactly one
// revolution with no index. However, a2r nominally contains
// 450 degress of rotation, 250ms at 300rpm.
write_flux();
loopPoint = totalTicks;
fmr.rewind();
revolution += 1;
write_flux(totalTicks * 5 / 4);
}
else
{
// We have an index, so this is a real read from a floppy
// and should be "one revolution plus a bit"
fmr.skipToEvent(F_BIT_INDEX);
write_flux();
}
private:
const A2RFluxSinkProto& _config;
Bytes _bytes;
ByteWriter _writer;
Bytes _strmBytes;
ByteWriter _strmWriter{_strmBytes.writer()};
std::map<std::string, std::string> _metadata;
int _minHead;
int _maxHead;
int _minCylinder;
int _maxCylinder;
};
} // namespace
uint32_t chunk_size = 10 + trackBytes.size();
if (globalConfig()->drive().drive_type() == DRIVETYPE_APPLE2)
_strmWriter.write_8(cylinder);
else
_strmWriter.write_8((cylinder << 1) | head);
_strmWriter.write_8(A2R_TIMING);
_strmWriter.write_le32(trackBytes.size());
_strmWriter.write_le32(ticks_to_a2r(loopPoint));
_strmWriter += trackBytes;
}
private:
std::string _filename;
Bytes _bytes;
ByteWriter _writer;
Bytes _strmBytes;
ByteWriter _strmWriter{_strmBytes.writer()};
std::map<std::string, std::string> _metadata;
int _minHead;
int _maxHead;
int _minCylinder;
int _maxCylinder;
};
class A2RFluxSink : public FluxSink
{
public:
A2RFluxSink(const A2RFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<A2RSink>(_config.filename());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
operator std::string() const override
{
return fmt::format("a2r({})", _config.filename());
}
private:
const A2RFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createA2RFluxSink(
const A2RFluxSinkProto& config)

View File

@@ -1,4 +1,5 @@
#include "lib/core/globals.h"
#include "lib/core/logger.h"
#include "lib/config/flags.h"
#include "lib/data/fluxmap.h"
#include "lib/core/bytes.h"
@@ -11,27 +12,29 @@
#include <sys/stat.h>
#include <sys/types.h>
class AuFluxSink : public FluxSink
class AuSink : public FluxSink::Sink
{
public:
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
~AuFluxSink()
AuSink(const std::string& directory, bool indexMarkers):
_directory(directory),
_indexMarkers(indexMarkers)
{
std::cerr << "Warning: do not play these files, or you will break your "
"speakers and/or ears!\n";
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
~AuSink()
{
log("Warning: do not play these files, or you will break your "
"speakers and/or ears!");
}
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
unsigned totalTicks = fluxmap.ticks() + 2;
unsigned channels = _config.index_markers() ? 2 : 1;
unsigned channels = _indexMarkers ? 2 : 1;
mkdir(_config.directory().c_str(), 0744);
mkdir(_directory.c_str(), 0744);
std::ofstream of(
fmt::format(
"{}/c{:02d}.h{:01d}.au", _config.directory(), track, head),
fmt::format("{}/c{:02d}.h{:01d}.au", _directory, track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
@@ -73,7 +76,7 @@ public:
if (event & F_BIT_PULSE)
data[timestamp * channels + 0] = 0x7f;
if (_config.index_markers() && (event & F_BIT_INDEX))
if (_indexMarkers && (event & F_BIT_INDEX))
data[timestamp * channels + 1] = 0x7f;
}
@@ -81,6 +84,27 @@ public:
}
}
private:
std::string _directory;
bool _indexMarkers;
};
class AuFluxSink : public FluxSink
{
public:
AuFluxSink(const AuFluxSinkProto& config): _config(config) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<AuSink>(
_config.directory(), _config.index_markers());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
operator std::string() const override
{
return fmt::format("au({})", _config.directory());

View File

@@ -16,15 +16,10 @@
#include <sys/types.h>
#include <filesystem>
class Fl2FluxSink : public FluxSink
class Fl2Sink : public FluxSink::Sink
{
public:
Fl2FluxSink(const Fl2FluxSinkProto& lconfig):
Fl2FluxSink(lconfig.filename())
{
}
Fl2FluxSink(const std::string& filename): _filename(filename)
Fl2Sink(const std::string& filename): _filename(filename)
{
std::ofstream of(filename);
if (!of.is_open())
@@ -33,7 +28,7 @@ public:
std::filesystem::remove(filename);
}
~Fl2FluxSink()
~Fl2Sink()
{
log("FL2: writing {}", _filename);
@@ -54,27 +49,46 @@ public:
saveFl2File(_filename, proto);
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
auto& vector = _data[std::make_pair(track, head)];
vector.push_back(fluxmap.rawBytes());
}
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private:
std::string _filename;
std::map<std::pair<unsigned, unsigned>, std::vector<Bytes>> _data;
};
class Fl2FluxSink : public FluxSink
{
public:
Fl2FluxSink(const std::string& filename): _filename(filename) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<Fl2Sink>(_filename);
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_filename);
}
public:
operator std::string() const override
{
return fmt::format("fl2({})", _filename);
}
private:
const std::string _filename;
};
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(
const Fl2FluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config));
return std::unique_ptr<FluxSink>(new Fl2FluxSink(config.filename()));
}
std::unique_ptr<FluxSink> FluxSink::createFl2FluxSink(

View File

@@ -4,6 +4,7 @@
#include "lib/config/flags.h"
#include "lib/data/locations.h"
#include <ostream>
#include <filesystem>
class Fluxmap;
class FluxSinkProto;
@@ -41,13 +42,26 @@ public:
static std::unique_ptr<FluxSink> create(const FluxSinkProto& config);
public:
/* Writes a fluxmap to a track and side. */
virtual void writeFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void writeFlux(const CylinderHead& location, const Fluxmap& fluxmap)
class Sink
{
writeFlux(location.cylinder, location.head, fluxmap);
}
public:
Sink() {}
virtual ~Sink() {}
public:
/* Writes a fluxmap to a track and side. */
virtual void addFlux(int track, int side, const Fluxmap& fluxmap) = 0;
void addFlux(const CylinderHead& location, const Fluxmap& fluxmap)
{
addFlux(location.cylinder, location.head, fluxmap);
}
};
public:
/* Creates a writer object. */
virtual std::unique_ptr<Sink> create() = 0;
/* Returns whether this is writing to real hardware or not. */
@@ -56,6 +70,14 @@ public:
return false;
}
/* Returns the path (filename or directory) being written to, if there is
* one. */
virtual std::optional<std::filesystem::path> getPath() const
{
return {};
}
virtual operator std::string() const = 0;
};

View File

@@ -8,15 +8,9 @@
#include "lib/fluxsink/fluxsink.h"
#include "lib/fluxsink/fluxsink.pb.h"
class HardwareFluxSink : public FluxSink
class HardwareSink : public FluxSink::Sink
{
public:
HardwareFluxSink(const HardwareFluxSinkProto& conf): _config(conf) {}
~HardwareFluxSink() {}
public:
void writeFlux(int track, int side, const Fluxmap& fluxmap) override
void addFlux(int track, int side, const Fluxmap& fluxmap) override
{
auto& drive = globalConfig()->drive();
usbSetDrive(drive.drive(), drive.high_density(), drive.index_mode());
@@ -25,6 +19,15 @@ public:
return usbWrite(
side, fluxmap.rawBytes(), drive.hard_sector_threshold_ns());
}
};
class HardwareFluxSink : public FluxSink
{
public:
std::unique_ptr<Sink> create() override
{
return std::make_unique<HardwareSink>();
}
bool isHardware() const override
{
@@ -33,15 +36,12 @@ public:
operator std::string() const override
{
return fmt::format("drive {}", globalConfig()->drive().drive());
return "hardware {}";
}
private:
const HardwareFluxSinkProto& _config;
};
std::unique_ptr<FluxSink> FluxSink::createHardwareFluxSink(
const HardwareFluxSinkProto& config)
{
return std::unique_ptr<FluxSink>(new HardwareFluxSink(config));
return std::unique_ptr<FluxSink>();
}

View File

@@ -36,10 +36,13 @@ static void appendChecksum(uint32_t& checksum, const Bytes& bytes)
checksum += br.read_8();
}
class ScpFluxSink : public FluxSink
class ScpSink : public FluxSink::Sink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig)
ScpSink(const std::string& filename, uint8_t typeByte, bool alignWithIndex):
_filename(filename),
_typeByte(typeByte),
_alignWithIndex(alignWithIndex)
{
// FIXME: should use a passed-in DiskLayout object.
auto diskLayout = createDiskLayout();
@@ -50,7 +53,7 @@ public:
_fileheader.file_id[1] = 'C';
_fileheader.file_id[2] = 'P';
_fileheader.version = 0x18; /* Version 1.8 of the spec */
_fileheader.type = _config.type_byte();
_fileheader.type = _typeByte;
_fileheader.start_track = strackno(minCylinder, minHead);
_fileheader.end_track = strackno(maxCylinder, maxHead);
_fileheader.flags = SCP_FLAG_INDEXED;
@@ -72,7 +75,7 @@ public:
_fileheader.end_track - _fileheader.start_track + 1);
}
~ScpFluxSink()
~ScpSink()
{
uint32_t checksum = 0;
appendChecksum(checksum,
@@ -82,7 +85,7 @@ public:
write_le32(_fileheader.checksum, checksum);
log("SCP: writing output file");
std::ofstream of(_config.filename(), std::ios::out | std::ios::binary);
std::ofstream of(_filename, std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
of.write((const char*)&_fileheader, sizeof(_fileheader));
@@ -90,8 +93,7 @@ public:
of.close();
}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
ByteWriter trackdataWriter(_trackdata);
trackdataWriter.seekToEnd();
@@ -99,7 +101,8 @@ public:
if (strack >= std::size(_fileheader.track))
{
log("SCP: cannot write track {} head {}, there are not not enough "
log("SCP: cannot write track {} head {}, there are not not "
"enough "
"Track Data Headers.",
track,
head);
@@ -117,7 +120,7 @@ public:
int revolution =
-1; // -1 indicates that we are before the first index pulse
if (_config.align_with_index())
if (_alignWithIndex)
{
fmr.skipToEvent(F_BIT_INDEX);
revolution = 0;
@@ -136,9 +139,9 @@ public:
totalTicks += ticks;
revTicks += ticks;
// if we haven't output any revolutions yet by the end of the track,
// assume that the whole track is one rev
// also discard any duplicate index pulses
// if we haven't output any revolutions yet by the end of the
// track, assume that the whole track is one rev also discard
// any duplicate index pulses
if (((fmr.eof() && revolution <= 0) ||
((event & F_BIT_INDEX)) && revTicks > 0))
{
@@ -181,6 +184,32 @@ public:
trackdataWriter += fluxdata;
}
private:
std::string _filename;
uint8_t _typeByte;
bool _alignWithIndex;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
class ScpFluxSink : public FluxSink
{
public:
ScpFluxSink(const ScpFluxSinkProto& lconfig): _config(lconfig) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<ScpSink>(_config.filename(),
_config.type_byte(),
_config.align_with_index());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.filename());
}
public:
operator std::string() const override
{
return fmt::format("scp({})", _config.filename());
@@ -188,8 +217,6 @@ public:
private:
const ScpFluxSinkProto& _config;
ScpHeader _fileheader = {0};
Bytes _trackdata;
};
std::unique_ptr<FluxSink> FluxSink::createScpFluxSink(

View File

@@ -11,18 +11,16 @@
#include <sys/stat.h>
#include <sys/types.h>
class VcdFluxSink : public FluxSink
class VcdSink : public FluxSink::Sink
{
public:
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
VcdSink(const std::string& directory): _directory(directory) {}
public:
void writeFlux(int track, int head, const Fluxmap& fluxmap) override
void addFlux(int track, int head, const Fluxmap& fluxmap) override
{
mkdir(_config.directory().c_str(), 0744);
mkdir(_directory.c_str(), 0744);
std::ofstream of(
fmt::format(
"{}/c{:02d}.h{:01d}.vcd", _config.directory(), track, head),
fmt::format("{}/c{:02d}.h{:01d}.vcd", _directory, track, head),
std::ios::out | std::ios::binary);
if (!of.is_open())
error("cannot open output file");
@@ -64,6 +62,26 @@ public:
of << "\n";
}
private:
const std::string _directory;
};
class VcdFluxSink : public FluxSink
{
public:
VcdFluxSink(const VcdFluxSinkProto& config): _config(config) {}
std::unique_ptr<Sink> create() override
{
return std::make_unique<VcdSink>(_config.directory());
}
std::optional<std::filesystem::path> getPath() const override
{
return std::make_optional(_config.directory());
}
public:
operator std::string() const override
{
return fmt::format("vcd({})", _config.directory());

View File

@@ -53,13 +53,14 @@ int mainConvert(int argc, const char* argv[])
minHead,
maxHead);
auto fluxSink = FluxSink::create(globalConfig());
auto fluxSinkFactory = FluxSink::create(globalConfig());
auto fluxSink = fluxSinkFactory->create();
for (const auto& physicalLocation : diskLayout->physicalLocations)
{
auto fi = fluxSource->readFlux(physicalLocation);
while (fi->hasNext())
fluxSink->writeFlux(physicalLocation, *fi->next());
fluxSink->addFlux(physicalLocation, *fi->next());
}
return 0;

View File

@@ -3,6 +3,7 @@
#include <hex/api/imhex_api/hex_editor.hpp>
#include <hex/api/content_registry/settings.hpp>
#include <hex/api/task_manager.hpp>
#include <popups/popup_question.hpp>
#include <toasts/toast_notification.hpp>
#include "lib/core/globals.h"
#include "lib/core/utils.h"
@@ -580,6 +581,29 @@ void Datastore::beginWrite()
globalConfig().getVerificationFluxSourceProto());
}
auto path = fluxSink->getPath();
if (path.has_value() && std::filesystem::exists(*path))
{
{
bool result;
wtRunSynchronouslyOnUiThread((
std::function<void()>)[&] {
hex::ui::PopupQuestion::open(
"fluxengine.messages.writingFluxToFile"_lang,
[&]
{
result = true;
},
[&]
{
result = false;
});
});
if (!result)
throw EmergencyStopException();
}
}
auto image = diskFlux->image;
writeDiskCommand(*diskLayout,
*image,
@@ -717,7 +741,9 @@ void Datastore::writeFluxFile(const std::fs::path& path)
error("no loaded image");
if (diskFlux->image->getGeometry().totalBytes !=
diskLayout->totalBytes)
error("loaded image is not the right size for this format");
error(
"loaded image is not the right size for this "
"format");
globalConfig().setFluxSink(path.string());
auto fluxSource = FluxSource::createMemoryFluxSource(*diskFlux);

View File

@@ -101,9 +101,9 @@ void DiskProvider::writeRaw(u64 offset, const void* buffer, size_t size)
[[nodiscard]] u64 DiskProvider::getActualSize() const
{
const auto& diskFlux = Datastore::getDecodedDisk();
if (diskFlux && diskFlux->image)
return diskFlux->image->getGeometry().totalBytes;
const auto& diskLayout = Datastore::getDiskLayout();
if (diskLayout)
return diskLayout->totalBytes;
return 0;
}

View File

@@ -52,5 +52,7 @@
"fluxengine.view.status.writeFlux": "Writing flux file to disk",
"fluxengine.view.status.readImage": "Reading image file from disk",
"fluxengine.view.status.writeImage": "Writing image file to disk",
"fluxengine.view.status.blankFilesystem": "Creating blank filesystem"
"fluxengine.view.status.blankFilesystem": "Creating blank filesystem",
"fluxengine.messages.writingFluxToFile": "The current configuration is to write to a flux file rather than to hardware. Is this what you intended?"
}

View File

@@ -271,7 +271,8 @@ int main(int argc, const char* argv[])
int version = sqlGetVersion(db);
{
auto fluxsink = FluxSink::createFl2FluxSink(outFilename);
auto fluxSink = FluxSink::createFl2FluxSink(outFilename)->create();
for (const auto& locations : sqlFindFlux(db))
{
unsigned cylinder = locations.first;
@@ -295,7 +296,7 @@ int main(int argc, const char* argv[])
"bug)",
version);
}
fluxsink->writeFlux(cylinder, head, fluxmap);
fluxSink->addFlux(cylinder, head, fluxmap);
std::cout << '.' << std::flush;
}