mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
Merge pull request #574 from davidgiven/vfs
Dramatic GUI overhaul and addition of the new file browser.
This commit is contained in:
2
Makefile
2
Makefile
@@ -41,7 +41,7 @@ AR ?= $(CCPREFIX)ar
|
||||
PKG_CONFIG ?= pkg-config
|
||||
WX_CONFIG ?= wx-config
|
||||
PROTOC ?= protoc
|
||||
CFLAGS ?= -g -O3
|
||||
CFLAGS ?= -g -O0
|
||||
CXXFLAGS += -std=c++17
|
||||
LDFLAGS ?=
|
||||
PLATFORM ?= UNIX
|
||||
|
||||
11
README.md
11
README.md
@@ -4,10 +4,9 @@ FluxEngine
|
||||
(If you're reading this on GitHub, the formatting's a bit messed up. [Try the
|
||||
version on cowlark.com instead.](http://cowlark.com/fluxengine/))
|
||||
|
||||
**Breaking news!** As of 2021-05-21, the command line environment has changed
|
||||
_substantially_ (to make it more consistent and flexible, and allow some new
|
||||
backend features like multi-format IBM scheme disks, which are popular with
|
||||
CP/M). If things don't work the way you expect, please check the documentation.
|
||||
**Breaking news!** As of 2022-09-09, there's new filesystem support. Read (and
|
||||
sometimes write) files directly from (and to) your disks! It works in the GUI,
|
||||
too. See the details below.
|
||||
|
||||
What?
|
||||
-----
|
||||
@@ -67,6 +66,10 @@ following friendly articles:
|
||||
- [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track
|
||||
drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II
|
||||
|
||||
- [Direct filesystem access](doc/filesystem.md) ∾ imaging files is a pain
|
||||
∾ accessing files directly ∾ features and limitation ∾ it works on disk
|
||||
images too, you say?
|
||||
|
||||
- [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact
|
||||
science ∾ the sector map ∾ clock detection and the histogram
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ RETCODE adfRenameEntry(struct Volume *vol, SECTNUM pSect, char *oldName,
|
||||
BOOL intl;
|
||||
RETCODE rc;
|
||||
|
||||
if (strcmp(oldName,newName)==0)
|
||||
if ((pSect == nPSect) && (strcmp(oldName,newName)==0))
|
||||
return RC_OK;
|
||||
|
||||
intl = isINTL(vol->dosType) || isDIRCACHE(vol->dosType);
|
||||
|
||||
@@ -2350,6 +2350,28 @@ static FRESULT dir_read (
|
||||
/* Directory handling - Find an object in the directory */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
static int compare_filenames(const char* s1, const char* s2, size_t n)
|
||||
{
|
||||
#if FF_CASE_INSENSITIVE_COMPARISONS
|
||||
while (n--)
|
||||
{
|
||||
char c1 = *s1++;
|
||||
char c2 = *s2++;
|
||||
int d;
|
||||
|
||||
if (IsLower(c1)) c1 -= 0x20; /* To upper ASCII char */
|
||||
if (IsLower(c2)) c2 -= 0x20; /* To upper ASCII char */
|
||||
|
||||
d = c1 - c2;
|
||||
if (d)
|
||||
return d;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return memcmp(s1, s2, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
|
||||
DIR* dp /* Pointer to the directory object with the file name */
|
||||
)
|
||||
@@ -2409,13 +2431,13 @@ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
|
||||
}
|
||||
} else { /* An SFN entry is found */
|
||||
if (ord == 0 && sum == sum_sfn(dp->dir)) break; /* LFN matched? */
|
||||
if (!(dp->fn[NSFLAG] & NS_LOSS) && !memcmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */
|
||||
if (!(dp->fn[NSFLAG] & NS_LOSS) && !compare_filenames(dp->dir, dp->fn, 11)) break; /* SFN matched? */
|
||||
ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
|
||||
}
|
||||
}
|
||||
#else /* Non LFN configuration */
|
||||
dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK;
|
||||
if (!(dp->dir[DIR_Attr] & AM_VOL) && !memcmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
|
||||
if (!(dp->dir[DIR_Attr] & AM_VOL) && !compare_filenames(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
|
||||
#endif
|
||||
res = dir_next(dp, 0); /* Next entry */
|
||||
} while (res == FR_OK);
|
||||
@@ -2979,9 +3001,7 @@ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not cr
|
||||
sfn[i++] = d;
|
||||
} else { /* SBC */
|
||||
if (strchr("*+,:;<=>[]|\"\?\x7F", (int)c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */
|
||||
#if FF_ALLOW_MIXED_CASE == 0
|
||||
if (IsLower(c)) c -= 0x20; /* To upper */
|
||||
#endif
|
||||
sfn[i++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,9 +162,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#define FF_ALLOW_MIXED_CASE 1
|
||||
/* This option doesn't uppercase filenames before processing, in case the disk has
|
||||
* low case filenames in it. */
|
||||
#define FF_CASE_INSENSITIVE_COMPARISONS 1
|
||||
/* This option ensures that SFNs on disk are compared using case-insensitive routines,
|
||||
* some FAT variants store lower case filenames. dtrg */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
|
||||
BIN
doc/filebrowser.jpg
Normal file
BIN
doc/filebrowser.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
89
doc/filesystem.md
Normal file
89
doc/filesystem.md
Normal file
@@ -0,0 +1,89 @@
|
||||
Direct filesystem access
|
||||
========================
|
||||
|
||||
FluxEngine will let you read and write files directly, in many cases. This will
|
||||
allow you to pull files directly off a disk, or flux image, or disk image,
|
||||
without needing to image it first. You can also write, and make other
|
||||
modifications, including formatting blank disks, if the filesystem supports it.
|
||||
The GUI even has a reasonably competent file manager.
|
||||
|
||||
<div style="text-align: center">
|
||||
<a href="filebrowser.jpg"><img src="filebrowser.jpg" style="width:80%" alt="screenshot of the GUI in action"></a>
|
||||
</div>
|
||||
|
||||
The following file systems are supported so far.
|
||||
|
||||
| Filesystem | Read? | Write? | Notes |
|
||||
|:-----------------------------------------|:-----:|:------:|-------|
|
||||
| Acorn DFS | Y | | |
|
||||
| Amiga FFS | Y | Y | Both OFS and FFS |
|
||||
| Brother 120kB | Y | | |
|
||||
| Commodore CbmFS | Y | | Only 1541 disks so far |
|
||||
| CP/M | Y | | For selected formats |
|
||||
| FatFS (a.k.a. MS-DOS) | Y | Y | FAT12, FAT16, FAT32, but only SFN; not Atari |
|
||||
| Macintosh HFS | Y | Y | Only AppleDouble files may be written |
|
||||
|
||||
Please not that Atari disks do _not_ use standard FatFS, and the library I'm
|
||||
using for the heavy lifting (ChaN's
|
||||
[FatFS](http://elm-chan.org/fsw/ff/00index_e.html) doesn't seem to support it.
|
||||
If you know different, please [get in
|
||||
touch](https://github.com/davidgiven/fluxengine/issues/new).
|
||||
|
||||
Using it
|
||||
--------
|
||||
|
||||
To use, try syntax like this:
|
||||
|
||||
```
|
||||
fluxengine ls ibm180 -f drive:0
|
||||
```
|
||||
|
||||
`ibm180` is the format, which selects the most common filesystem automatically.
|
||||
`-f drive:0` specifies a flux source/sink, in this case a real disk. You may
|
||||
also specify a flux file (read only). Disk images may be specified with `-i
|
||||
disk.img` (read/write).
|
||||
|
||||
Commands which take filename paramaters typically use `-p` to indicate the path
|
||||
on the disk, and `-l` for the local filename. For example:
|
||||
|
||||
```
|
||||
fluxengine putfile ibm180 -f drive:0 -p ondisk.pcx -l z.pcx
|
||||
```
|
||||
|
||||
This will copy the file `z.pcx` onto the disk and call it `ONDISK.PCX`.
|
||||
|
||||
The directory separator character is _always_ `/`, even if you're using
|
||||
Macintosh HFS, where it's usually `:`.
|
||||
|
||||
The command line interface is pretty clunky, and is intended for scripts. For
|
||||
everyday use, I'd suggest using the GUI (see the screenshot above).
|
||||
|
||||
The supported commands are:
|
||||
|
||||
- **ls**: list files (including in a directory)
|
||||
- **getfile**: pull a file off a disk
|
||||
- **putfile**: put a file onto a disk
|
||||
- **format**: create a new filesystem and format a disk
|
||||
- **getfileinfo**: retrieves metadata about a file.
|
||||
- **getdiskinfo**: retrieves metadata about the file system.
|
||||
- **rm**: deletes a file or empty directory
|
||||
- **mv**: renames a file (use `--path` and `--path2` for the old and new paths)
|
||||
- **mkdir**: creates a directory
|
||||
|
||||
There are commands missing here; this is all a work in progress.
|
||||
|
||||
Overriding the filesystem type
|
||||
------------------------------
|
||||
|
||||
Sometimes you might want to use a different filesystem on the disk than the
|
||||
default; for example, Macintosh HFS filesystems are common on 3.5" floppies. You
|
||||
can do this as follows:
|
||||
|
||||
```
|
||||
fluxengine format ibm1440 -f drive:1 --filesystem.machfs=
|
||||
```
|
||||
|
||||
Some filesystems won't work on some disks --- don't try this with Amiga FFS, for
|
||||
example.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "usb/usb.h"
|
||||
#include "fluxsink/fluxsink.h"
|
||||
#include "lib/fluxsink/fluxsink.pb.h"
|
||||
#include "lib/readerwriter.cc"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class HardwareFluxSink : public FluxSink
|
||||
@@ -14,32 +15,8 @@ public:
|
||||
HardwareFluxSink(const HardwareFluxSinkProto& conf):
|
||||
_config(conf)
|
||||
{
|
||||
if (config.drive().has_hard_sector_count())
|
||||
{
|
||||
int retries = 5;
|
||||
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
|
||||
|
||||
nanoseconds_t oneRevolution = config.drive().rotational_period_ms() * 1e6;
|
||||
if (oneRevolution == 0)
|
||||
{
|
||||
Logger() << BeginSpeedOperationLogMessage();
|
||||
|
||||
do {
|
||||
oneRevolution = usbGetRotationalPeriod(config.drive().hard_sector_count());
|
||||
_hardSectorThreshold = oneRevolution * 3 / (4 * config.drive().hard_sector_count());
|
||||
retries--;
|
||||
} while ((oneRevolution == 0) && (retries > 0));
|
||||
config.mutable_drive()->set_rotational_period_ms(oneRevolution / 1e6);
|
||||
|
||||
Logger() << EndSpeedOperationLogMessage { oneRevolution };
|
||||
}
|
||||
|
||||
if (oneRevolution == 0) {
|
||||
Error() << "Failed\nIs a disk in the drive?";
|
||||
}
|
||||
}
|
||||
else
|
||||
_hardSectorThreshold = 0;
|
||||
nanoseconds_t oneRevolution;
|
||||
measureDiskRotation(oneRevolution, _hardSectorThreshold);
|
||||
}
|
||||
|
||||
~HardwareFluxSink()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "usb/usb.h"
|
||||
#include "fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsource/fluxsource.pb.h"
|
||||
#include "lib/readerwriter.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
class HardwareFluxSource : public FluxSource
|
||||
@@ -50,35 +51,7 @@ private:
|
||||
public:
|
||||
HardwareFluxSource(const HardwareFluxSourceProto& conf): _config(conf)
|
||||
{
|
||||
int retries = 5;
|
||||
usbSetDrive(config.drive().drive(), config.drive().high_density(), config.drive().index_mode());
|
||||
Logger() << BeginSpeedOperationLogMessage();
|
||||
|
||||
_oneRevolution = config.drive().rotational_period_ms() * 1e6;
|
||||
_hardSectorThreshold = 0;
|
||||
if (_oneRevolution == 0)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{"Measuring drive rotational speed"};
|
||||
do
|
||||
{
|
||||
_oneRevolution =
|
||||
usbGetRotationalPeriod(config.drive().hard_sector_count());
|
||||
if (config.drive().hard_sector_count() != 0)
|
||||
_hardSectorThreshold =
|
||||
_oneRevolution * 3 / (4 * config.drive().hard_sector_count());
|
||||
else
|
||||
_hardSectorThreshold = 0;
|
||||
|
||||
retries--;
|
||||
} while ((_oneRevolution == 0) && (retries > 0));
|
||||
config.mutable_drive()->set_rotational_period_ms(_oneRevolution / 1e6);
|
||||
Logger() << EndOperationLogMessage{};
|
||||
}
|
||||
|
||||
if (_oneRevolution == 0)
|
||||
Error() << "Failed\nIs a disk in the drive?";
|
||||
|
||||
Logger() << EndSpeedOperationLogMessage{_oneRevolution};
|
||||
measureDiskRotation(_oneRevolution, _hardSectorThreshold);
|
||||
}
|
||||
|
||||
~HardwareFluxSource() {}
|
||||
|
||||
@@ -34,6 +34,8 @@ extern void hexdumpForSrp16(std::ostream& stream, const Bytes& bytes);
|
||||
|
||||
struct ErrorException
|
||||
{
|
||||
ErrorException(const std::string& message): message(message){}
|
||||
|
||||
const std::string message;
|
||||
|
||||
void print() const;
|
||||
|
||||
55
lib/image.cc
55
lib/image.cc
@@ -1,20 +1,41 @@
|
||||
#include "globals.h"
|
||||
#include "sector.h"
|
||||
#include "image.h"
|
||||
#include "layout.h"
|
||||
|
||||
Image::Image()
|
||||
{}
|
||||
Image::Image() {}
|
||||
|
||||
Image::Image(std::set<std::shared_ptr<const Sector>>& sectors)
|
||||
{
|
||||
for (auto& sector : sectors)
|
||||
{
|
||||
key_t key = std::make_tuple(sector->logicalTrack, sector->logicalSide, sector->logicalSector);
|
||||
key_t key = std::make_tuple(
|
||||
sector->logicalTrack, sector->logicalSide, sector->logicalSector);
|
||||
_sectors[key] = sector;
|
||||
}
|
||||
calculateSize();
|
||||
}
|
||||
|
||||
void Image::clear()
|
||||
{
|
||||
_sectors.clear();
|
||||
_geometry = {0, 0, 0};
|
||||
}
|
||||
|
||||
void Image::createBlankImage()
|
||||
{
|
||||
clear();
|
||||
for (const auto& trackAndHead : Layout::getTrackOrdering())
|
||||
{
|
||||
unsigned track = trackAndHead.first;
|
||||
unsigned side = trackAndHead.second;
|
||||
auto layout = Layout::getLayoutOfTrack(track, side);
|
||||
Bytes blank(layout.sector_size());
|
||||
for (unsigned sectorId : Layout::getSectorsInTrack(layout))
|
||||
put(track, side, sectorId)->data = blank;
|
||||
}
|
||||
}
|
||||
|
||||
bool Image::empty() const
|
||||
{
|
||||
return _sectors.empty();
|
||||
@@ -26,7 +47,8 @@ bool Image::contains(unsigned track, unsigned side, unsigned sectorid) const
|
||||
return _sectors.find(key) != _sectors.end();
|
||||
}
|
||||
|
||||
std::shared_ptr<const Sector> Image::get(unsigned track, unsigned side, unsigned sectorid) const
|
||||
std::shared_ptr<const Sector> Image::get(
|
||||
unsigned track, unsigned side, unsigned sectorid) const
|
||||
{
|
||||
static std::shared_ptr<const Sector> NONE;
|
||||
|
||||
@@ -37,7 +59,8 @@ std::shared_ptr<const Sector> Image::get(unsigned track, unsigned side, unsigned
|
||||
return i->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> Image::put(unsigned track, unsigned side, unsigned sectorid)
|
||||
std::shared_ptr<Sector> Image::put(
|
||||
unsigned track, unsigned side, unsigned sectorid)
|
||||
{
|
||||
key_t key = std::make_tuple(track, side, sectorid);
|
||||
std::shared_ptr<Sector> sector = std::make_shared<Sector>();
|
||||
@@ -54,6 +77,15 @@ void Image::erase(unsigned track, unsigned side, unsigned sectorid)
|
||||
_sectors.erase(key);
|
||||
}
|
||||
|
||||
std::set<std::pair<unsigned, unsigned>> Image::tracks() const
|
||||
{
|
||||
std::set<std::pair<unsigned, unsigned>> result;
|
||||
for (const auto& e : _sectors)
|
||||
result.insert(
|
||||
std::make_pair(std::get<0>(e.first), std::get<1>(e.first)));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Image::calculateSize()
|
||||
{
|
||||
_geometry = {};
|
||||
@@ -63,13 +95,16 @@ void Image::calculateSize()
|
||||
const auto& sector = i.second;
|
||||
if (sector)
|
||||
{
|
||||
_geometry.numTracks = std::max(_geometry.numTracks, (unsigned)sector->logicalTrack+1);
|
||||
_geometry.numSides = std::max(_geometry.numSides, (unsigned)sector->logicalSide+1);
|
||||
_geometry.firstSector = std::min(_geometry.firstSector, (unsigned)sector->logicalSector);
|
||||
_geometry.numTracks = std::max(
|
||||
_geometry.numTracks, (unsigned)sector->logicalTrack + 1);
|
||||
_geometry.numSides =
|
||||
std::max(_geometry.numSides, (unsigned)sector->logicalSide + 1);
|
||||
_geometry.firstSector = std::min(
|
||||
_geometry.firstSector, (unsigned)sector->logicalSector);
|
||||
maxSector = std::max(maxSector, (unsigned)sector->logicalSector);
|
||||
_geometry.sectorSize = std::max(_geometry.sectorSize, (unsigned)sector->data.size());
|
||||
_geometry.sectorSize =
|
||||
std::max(_geometry.sectorSize, (unsigned)sector->data.size());
|
||||
}
|
||||
}
|
||||
_geometry.numSectors = maxSector - _geometry.firstSector + 1;
|
||||
}
|
||||
|
||||
|
||||
57
lib/image.h
57
lib/image.h
@@ -23,14 +23,30 @@ public:
|
||||
public:
|
||||
class const_iterator
|
||||
{
|
||||
typedef std::map<key_t, std::shared_ptr<const Sector>>::const_iterator wrapped_iterator_t;
|
||||
typedef std::map<key_t, std::shared_ptr<const Sector>>::const_iterator
|
||||
wrapped_iterator_t;
|
||||
|
||||
public:
|
||||
const_iterator(const wrapped_iterator_t& it): _it(it) {}
|
||||
std::shared_ptr<const Sector> operator* () { return _it->second; }
|
||||
void operator++ () { _it++; }
|
||||
bool operator== (const const_iterator& other) const { return _it == other._it; }
|
||||
bool operator!= (const const_iterator& other) const { return _it != other._it; }
|
||||
std::shared_ptr<const Sector> operator*()
|
||||
{
|
||||
return _it->second;
|
||||
}
|
||||
|
||||
void operator++()
|
||||
{
|
||||
_it++;
|
||||
}
|
||||
|
||||
bool operator==(const const_iterator& other) const
|
||||
{
|
||||
return _it == other._it;
|
||||
}
|
||||
|
||||
bool operator!=(const const_iterator& other) const
|
||||
{
|
||||
return _it != other._it;
|
||||
}
|
||||
|
||||
private:
|
||||
wrapped_iterator_t _it;
|
||||
@@ -39,17 +55,35 @@ public:
|
||||
public:
|
||||
void calculateSize();
|
||||
|
||||
void clear();
|
||||
void createBlankImage();
|
||||
bool empty() const;
|
||||
bool contains(unsigned track, unsigned side, unsigned sectorId) const;
|
||||
std::shared_ptr<const Sector> get(unsigned track, unsigned side, unsigned sectorId) const;
|
||||
std::shared_ptr<Sector> put(unsigned track, unsigned side, unsigned sectorId);
|
||||
std::shared_ptr<const Sector> get(
|
||||
unsigned track, unsigned side, unsigned sectorId) const;
|
||||
std::shared_ptr<Sector> put(
|
||||
unsigned track, unsigned side, unsigned sectorId);
|
||||
void erase(unsigned track, unsigned side, unsigned sectorId);
|
||||
|
||||
const_iterator begin() const { return const_iterator(_sectors.cbegin()); }
|
||||
const_iterator end() const { return const_iterator(_sectors.cend()); }
|
||||
std::set<std::pair<unsigned, unsigned>> tracks() const;
|
||||
|
||||
void setGeometry(Geometry geometry) { _geometry = geometry; }
|
||||
const Geometry& getGeometry() const { return _geometry; }
|
||||
const_iterator begin() const
|
||||
{
|
||||
return const_iterator(_sectors.cbegin());
|
||||
}
|
||||
const_iterator end() const
|
||||
{
|
||||
return const_iterator(_sectors.cend());
|
||||
}
|
||||
|
||||
void setGeometry(Geometry geometry)
|
||||
{
|
||||
_geometry = geometry;
|
||||
}
|
||||
const Geometry& getGeometry() const
|
||||
{
|
||||
return _geometry;
|
||||
}
|
||||
|
||||
private:
|
||||
Geometry _geometry = {0, 0, 0};
|
||||
@@ -57,4 +91,3 @@ private:
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -130,46 +130,32 @@ std::set<Location> Mapper::computeLocations()
|
||||
{
|
||||
std::set<Location> locations;
|
||||
|
||||
unsigned track_step = getTrackStep();
|
||||
for (unsigned logicalTrack : iterate(config.tracks()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
unsigned physicalTrack =
|
||||
config.drive().head_bias() + logicalTrack * track_step;
|
||||
|
||||
locations.insert({.physicalTrack = physicalTrack,
|
||||
.logicalTrack = logicalTrack,
|
||||
.head = head,
|
||||
.groupSize = track_step});
|
||||
}
|
||||
locations.insert(computeLocationFor(logicalTrack, head));
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
Location Mapper::computeLocationFor(unsigned desiredTrack, unsigned desiredHead)
|
||||
Location Mapper::computeLocationFor(unsigned logicalTrack, unsigned logicalHead)
|
||||
{
|
||||
if ((logicalTrack < config.layout().tracks()) &&
|
||||
(logicalHead < config.layout().sides()))
|
||||
{
|
||||
unsigned track_step = getTrackStep();
|
||||
for (unsigned logicalTrack : iterate(config.tracks()))
|
||||
{
|
||||
for (unsigned head : iterate(config.heads()))
|
||||
{
|
||||
if ((logicalTrack == desiredTrack) && (head == desiredHead))
|
||||
{
|
||||
unsigned physicalTrack =
|
||||
config.drive().head_bias() + logicalTrack * track_step;
|
||||
|
||||
return {.physicalTrack = physicalTrack,
|
||||
.logicalTrack = logicalTrack,
|
||||
.head = head,
|
||||
.head = logicalHead,
|
||||
.groupSize = track_step};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Error() << fmt::format(
|
||||
"track {}.{} is not part of the image", desiredTrack, desiredHead);
|
||||
"track {}.{} is not part of the image", logicalTrack, logicalHead);
|
||||
}
|
||||
|
||||
nanoseconds_t Mapper::calculatePhysicalClockPeriod(
|
||||
|
||||
@@ -56,6 +56,46 @@ private:
|
||||
_cache;
|
||||
};
|
||||
|
||||
void measureDiskRotation(
|
||||
nanoseconds_t& oneRevolution, nanoseconds_t& hardSectorThreshold)
|
||||
{
|
||||
Logger() << BeginSpeedOperationLogMessage();
|
||||
|
||||
int retries = 5;
|
||||
usbSetDrive(config.drive().drive(),
|
||||
config.drive().high_density(),
|
||||
config.drive().index_mode());
|
||||
oneRevolution = config.drive().rotational_period_ms() * 1e6;
|
||||
if (config.drive().hard_sector_count() != 0)
|
||||
hardSectorThreshold =
|
||||
oneRevolution * 3 / (4 * config.drive().hard_sector_count());
|
||||
else
|
||||
hardSectorThreshold = 0;
|
||||
|
||||
if (oneRevolution == 0)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{
|
||||
"Measuring drive rotational speed"};
|
||||
do
|
||||
{
|
||||
oneRevolution =
|
||||
usbGetRotationalPeriod(config.drive().hard_sector_count());
|
||||
if (config.drive().hard_sector_count() != 0)
|
||||
hardSectorThreshold = oneRevolution * 3 /
|
||||
(4 * config.drive().hard_sector_count());
|
||||
|
||||
retries--;
|
||||
} while ((oneRevolution == 0) && (retries > 0));
|
||||
config.mutable_drive()->set_rotational_period_ms(oneRevolution / 1e6);
|
||||
Logger() << EndOperationLogMessage{};
|
||||
}
|
||||
|
||||
if (oneRevolution == 0)
|
||||
Error() << "Failed\nIs a disk in the drive?";
|
||||
|
||||
Logger() << EndSpeedOperationLogMessage{oneRevolution};
|
||||
}
|
||||
|
||||
/* Given a set of sectors, deduplicates them sensibly (e.g. if there is a good
|
||||
* and bad version of the same sector, the bad version is dropped). */
|
||||
|
||||
@@ -185,12 +225,11 @@ ReadResult readGroup(FluxSourceIteratorHolder& fluxSourceIteratorHolder,
|
||||
void writeTracks(FluxSink& fluxSink,
|
||||
std::function<std::unique_ptr<const Fluxmap>(const Location& location)>
|
||||
producer,
|
||||
std::function<bool(const Location& location)> verifier)
|
||||
std::function<bool(const Location& location)> verifier,
|
||||
const std::set<Location>& locations)
|
||||
{
|
||||
Logger() << BeginOperationLogMessage{"Encoding and writing to disk"};
|
||||
|
||||
auto locations = Mapper::computeLocations();
|
||||
|
||||
int index = 0;
|
||||
for (const auto& location : locations)
|
||||
{
|
||||
@@ -255,8 +294,10 @@ static bool dontVerify(const Location&)
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeTracks(
|
||||
FluxSink& fluxSink, AbstractEncoder& encoder, const Image& image)
|
||||
void writeTracks(FluxSink& fluxSink,
|
||||
AbstractEncoder& encoder,
|
||||
const Image& image,
|
||||
const std::set<Location>& locations)
|
||||
{
|
||||
writeTracks(
|
||||
fluxSink,
|
||||
@@ -265,14 +306,16 @@ void writeTracks(
|
||||
auto sectors = encoder.collectSectors(location, image);
|
||||
return encoder.encode(location, sectors, image);
|
||||
},
|
||||
dontVerify);
|
||||
dontVerify,
|
||||
locations);
|
||||
}
|
||||
|
||||
void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
const Image& image)
|
||||
const Image& image,
|
||||
const std::set<Location>& locations)
|
||||
{
|
||||
writeTracks(
|
||||
fluxSink,
|
||||
@@ -329,14 +372,16 @@ void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
locations);
|
||||
}
|
||||
|
||||
void writeDiskCommand(const Image& image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder,
|
||||
FluxSource* fluxSource)
|
||||
FluxSource* fluxSource,
|
||||
const std::set<Location>& locations)
|
||||
{
|
||||
const Image* imagep = ℑ
|
||||
std::unique_ptr<const Image> remapped;
|
||||
@@ -348,9 +393,20 @@ void writeDiskCommand(const Image& image,
|
||||
}
|
||||
|
||||
if (fluxSource && decoder)
|
||||
writeTracksAndVerify(fluxSink, encoder, *fluxSource, *decoder, *imagep);
|
||||
writeTracksAndVerify(
|
||||
fluxSink, encoder, *fluxSource, *decoder, *imagep, locations);
|
||||
else
|
||||
writeTracks(fluxSink, encoder, *imagep);
|
||||
writeTracks(fluxSink, encoder, *imagep, locations);
|
||||
}
|
||||
|
||||
void writeDiskCommand(const Image& image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder,
|
||||
FluxSource* fluxSource)
|
||||
{
|
||||
auto locations = Mapper::computeLocations();
|
||||
writeDiskCommand(image, encoder, fluxSink, decoder, fluxSource, locations);
|
||||
}
|
||||
|
||||
void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
|
||||
@@ -362,7 +418,8 @@ void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink)
|
||||
return fluxSource.readFlux(location.physicalTrack, location.head)
|
||||
->next();
|
||||
},
|
||||
dontVerify);
|
||||
dontVerify,
|
||||
Mapper::computeLocations());
|
||||
}
|
||||
|
||||
std::shared_ptr<TrackFlux> readAndDecodeTrack(
|
||||
|
||||
@@ -14,15 +14,33 @@ class ImageWriter;
|
||||
class Location;
|
||||
class TrackFlux;
|
||||
|
||||
extern void measureDiskRotation(
|
||||
nanoseconds_t& oneRevolution, nanoseconds_t& hardSectorThreshold);
|
||||
|
||||
extern void writeTracks(FluxSink& fluxSink,
|
||||
const std::function<std::unique_ptr<const Fluxmap>(
|
||||
const Location& location)> producer);
|
||||
const Location& location)> producer,
|
||||
const std::set<Location>& locations);
|
||||
|
||||
extern void writeTracksAndVerify(FluxSink& fluxSink,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
const Image& image,
|
||||
const std::set<Location>& locations);
|
||||
|
||||
extern void fillBitmapTo(std::vector<bool>& bitmap,
|
||||
unsigned& cursor,
|
||||
unsigned terminateAt,
|
||||
const std::vector<bool>& pattern);
|
||||
|
||||
extern void writeDiskCommand(const Image& image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
AbstractDecoder* decoder,
|
||||
FluxSource* fluxSource,
|
||||
const std::set<Location>& locations);
|
||||
|
||||
extern void writeDiskCommand(const Image& image,
|
||||
AbstractEncoder& encoder,
|
||||
FluxSink& fluxSink,
|
||||
@@ -31,9 +49,8 @@ extern void writeDiskCommand(const Image& image,
|
||||
|
||||
extern void writeRawDiskCommand(FluxSource& fluxSource, FluxSink& fluxSink);
|
||||
|
||||
extern std::shared_ptr<TrackFlux> readAndDecodeTrack(FluxSource& fluxSource,
|
||||
AbstractDecoder& decoder,
|
||||
const Location& location);
|
||||
extern std::shared_ptr<TrackFlux> readAndDecodeTrack(
|
||||
FluxSource& fluxSource, AbstractDecoder& decoder, const Location& location);
|
||||
|
||||
extern std::shared_ptr<const DiskFlux> readDiskCommand(
|
||||
FluxSource& fluxsource, AbstractDecoder& decoder);
|
||||
|
||||
@@ -74,6 +74,14 @@ bool endsWith(const std::string& value, const std::string& ending)
|
||||
std::equal(ending.rbegin(), ending.rend(), lowercase.begin());
|
||||
}
|
||||
|
||||
std::string toUpper(const std::string& value)
|
||||
{
|
||||
std::string s = value;
|
||||
for (char& c : s)
|
||||
c = toupper(c);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string leftTrimWhitespace(std::string value)
|
||||
{
|
||||
value.erase(0, value.find_first_not_of(WHITESPACE));
|
||||
|
||||
@@ -9,6 +9,7 @@ extern std::vector<std::string> split(
|
||||
const std::string& string, char separator);
|
||||
extern bool beginsWith(const std::string& value, const std::string& beginning);
|
||||
extern bool endsWith(const std::string& value, const std::string& ending);
|
||||
extern std::string toUpper(const std::string& value);
|
||||
extern std::string leftTrimWhitespace(std::string value);
|
||||
extern std::string rightTrimWhitespace(std::string value);
|
||||
extern std::string trimWhitespace(const std::string& value);
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
filename = filename.substr(0, filename.find(' '));
|
||||
|
||||
this->inode = inode;
|
||||
path = {filename};
|
||||
start_sector = ((bytes1[6] & 0x03) << 8) | bytes1[7];
|
||||
load_address =
|
||||
((bytes1[6] & 0x0c) << 14) | (bytes1[1] << 8) | bytes1[0];
|
||||
@@ -28,6 +29,18 @@ public:
|
||||
sector_count = (length + 255) / 256;
|
||||
file_type = TYPE_FILE;
|
||||
mode = locked ? "L" : "";
|
||||
|
||||
attributes[Filesystem::FILENAME] = filename;
|
||||
attributes[Filesystem::LENGTH] = fmt::format("{}", length);
|
||||
attributes[Filesystem::FILE_TYPE] = "file";
|
||||
attributes[Filesystem::MODE] = mode;
|
||||
attributes["acorndfs.inode"] = fmt::format("{}", inode);
|
||||
attributes["acorndfs.start_sector"] = fmt::format("{}", start_sector);
|
||||
attributes["acorndfs.load_address"] =
|
||||
fmt::format("0x{:x}", load_address);
|
||||
attributes["acorndfs.exec_address"] =
|
||||
fmt::format("0x{:x}", exec_address);
|
||||
attributes["acorndfs.locked"] = fmt::format("{}", locked);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -101,6 +114,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
{
|
||||
AcornDfsDirectory dir(this);
|
||||
@@ -149,26 +167,10 @@ public:
|
||||
return data;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path)
|
||||
{
|
||||
std::map<std::string, std::string> attributes;
|
||||
|
||||
AcornDfsDirectory dir(this);
|
||||
auto dirent = dir.findFile(path);
|
||||
attributes[FILENAME] = dirent->filename;
|
||||
attributes[LENGTH] = fmt::format("{}", dirent->length);
|
||||
attributes[FILE_TYPE] = "file";
|
||||
attributes[MODE] = dirent->mode;
|
||||
attributes["acorndfs.inode"] = fmt::format("{}", dirent->inode);
|
||||
attributes["acorndfs.start_sector"] =
|
||||
fmt::format("{}", dirent->start_sector);
|
||||
attributes["acorndfs.load_address"] =
|
||||
fmt::format("0x{:x}", dirent->load_address);
|
||||
attributes["acorndfs.exec_address"] =
|
||||
fmt::format("0x{:x}", dirent->exec_address);
|
||||
attributes["acorndfs.locked"] = fmt::format("{}", dirent->locked);
|
||||
|
||||
return attributes;
|
||||
return dir.findFile(path);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/proto.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/logger.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "adflib.h"
|
||||
@@ -56,7 +57,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
|
||||
OP_GETDIRENT | OP_DELETE | OP_MOVE | OP_CREATEDIR;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
AdfMount m(this);
|
||||
auto* vol = m.mount();
|
||||
@@ -72,7 +79,7 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void create(bool quick, const std::string& volumeName)
|
||||
void create(bool quick, const std::string& volumeName) override
|
||||
{
|
||||
if (!quick)
|
||||
eraseEverythingOnDisk();
|
||||
@@ -92,12 +99,12 @@ public:
|
||||
throw CannotWriteException();
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
|
||||
@@ -113,19 +120,13 @@ public:
|
||||
auto* entry = (struct Entry*)cell->content;
|
||||
cell = cell->next;
|
||||
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = entry->name;
|
||||
dirent->length = entry->size;
|
||||
dirent->file_type =
|
||||
(entry->type == ST_FILE) ? TYPE_FILE : TYPE_DIRECTORY;
|
||||
dirent->mode = modeToString(entry->access);
|
||||
results.push_back(dirent);
|
||||
results.push_back(toDirent(entry, path));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -136,29 +137,12 @@ public:
|
||||
|
||||
auto entry = AdfEntry(adfFindEntry(vol, (char*)path.back().c_str()));
|
||||
if (!entry)
|
||||
throw BadPathException();
|
||||
throw FileNotFoundException();
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[FILENAME] = entry->name;
|
||||
attributes[LENGTH] = fmt::format("{}", entry->size);
|
||||
attributes[FILE_TYPE] = (entry->type == ST_FILE) ? "file" : "dir";
|
||||
attributes[MODE] = modeToString(entry->access);
|
||||
attributes["amigaffs.comment"] = entry->comment;
|
||||
attributes["amigaffs.sector"] = entry->sector;
|
||||
|
||||
std::tm tm = {.tm_sec = entry->secs,
|
||||
.tm_min = entry->mins,
|
||||
.tm_hour = entry->hour,
|
||||
.tm_mday = entry->days,
|
||||
.tm_mon = entry->month,
|
||||
.tm_year = entry->year - 1900};
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm, "%FT%T%z");
|
||||
attributes["amigaffs.mtime"] = ss.str();
|
||||
return attributes;
|
||||
return toDirent(entry, path.parent());
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -185,7 +169,7 @@ public:
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void putFile(const Path& path, const Bytes& data)
|
||||
void putFile(const Path& path, const Bytes& data) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -209,7 +193,90 @@ public:
|
||||
adfCloseFile(file);
|
||||
}
|
||||
|
||||
void deleteFile(const Path& path) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
if (path.size() == 0)
|
||||
throw BadPathException();
|
||||
|
||||
auto* vol = m.mount();
|
||||
changeDirButOne(vol, path);
|
||||
|
||||
int res =
|
||||
adfRemoveEntry(vol, vol->curDirPtr, (char*)path.back().c_str());
|
||||
if (res != RC_OK)
|
||||
throw CannotWriteException();
|
||||
}
|
||||
|
||||
void moveFile(const Path& oldPath, const Path& newPath) override
|
||||
{
|
||||
AdfMount m(this);
|
||||
if ((oldPath.size() == 0) || (newPath.size() == 0))
|
||||
throw BadPathException();
|
||||
|
||||
auto* vol = m.mount();
|
||||
|
||||
changeDirButOne(vol, oldPath);
|
||||
auto oldDir = vol->curDirPtr;
|
||||
|
||||
changeDirButOne(vol, newPath);
|
||||
auto newDir = vol->curDirPtr;
|
||||
|
||||
int res = adfRenameEntry(vol,
|
||||
oldDir,
|
||||
(char*)oldPath.back().c_str(),
|
||||
newDir,
|
||||
(char*)newPath.back().c_str());
|
||||
if (res != RC_OK)
|
||||
throw CannotWriteException();
|
||||
}
|
||||
|
||||
void createDirectory(const Path& path)
|
||||
{
|
||||
AdfMount m(this);
|
||||
if (path.empty())
|
||||
throw BadPathException();
|
||||
|
||||
auto* vol = m.mount();
|
||||
changeDirButOne(vol, path);
|
||||
int res = adfCreateDir(vol, vol->curDirPtr, (char*)path.back().c_str());
|
||||
if (res != RC_OK)
|
||||
throw CannotWriteException();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Dirent> toDirent(struct Entry* entry, const Path& container)
|
||||
{
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
|
||||
dirent->path = container;
|
||||
dirent->path.push_back(entry->name);
|
||||
dirent->filename = entry->name;
|
||||
dirent->length = entry->size;
|
||||
dirent->file_type =
|
||||
(entry->type == ST_FILE) ? TYPE_FILE : TYPE_DIRECTORY;
|
||||
dirent->mode = modeToString(entry->access);
|
||||
|
||||
dirent->attributes[FILENAME] = entry->name;
|
||||
dirent->attributes[LENGTH] = fmt::format("{}", entry->size);
|
||||
dirent->attributes[FILE_TYPE] =
|
||||
(entry->type == ST_FILE) ? "file" : "dir";
|
||||
dirent->attributes[MODE] = modeToString(entry->access);
|
||||
dirent->attributes["amigaffs.comment"] = entry->comment;
|
||||
|
||||
std::tm tm = {.tm_sec = entry->secs,
|
||||
.tm_min = entry->mins,
|
||||
.tm_hour = entry->hour,
|
||||
.tm_mday = entry->days,
|
||||
.tm_mon = entry->month,
|
||||
.tm_year = entry->year - 1900};
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm, "%FT%T%z");
|
||||
dirent->attributes["amigaffs.mtime"] = ss.str();
|
||||
|
||||
return dirent;
|
||||
}
|
||||
|
||||
class AdfEntry
|
||||
{
|
||||
public:
|
||||
@@ -274,6 +341,8 @@ private:
|
||||
|
||||
~AdfMount()
|
||||
{
|
||||
if (vol)
|
||||
adfUnMount(vol);
|
||||
if (self->_ffs)
|
||||
adfUnMountDev(self->_ffs);
|
||||
adfEnvCleanUp();
|
||||
@@ -282,11 +351,17 @@ private:
|
||||
struct Volume* mount()
|
||||
{
|
||||
self->_ffs = adfMountDev(nullptr, false);
|
||||
return adfMount(self->_ffs, 0, false);
|
||||
if (!self->_ffs)
|
||||
throw BadFilesystemException();
|
||||
vol = adfMount(self->_ffs, 0, false);
|
||||
if (!vol)
|
||||
throw BadFilesystemException();
|
||||
return vol;
|
||||
}
|
||||
|
||||
private:
|
||||
AmigaFfsFilesystem* self;
|
||||
struct Volume* vol = nullptr;
|
||||
};
|
||||
|
||||
void changeDir(struct Volume* vol, const Path& path)
|
||||
@@ -344,6 +419,16 @@ private:
|
||||
struct Device* _ffs;
|
||||
};
|
||||
|
||||
static void onAdfWarning(char* message)
|
||||
{
|
||||
Logger() << message;
|
||||
}
|
||||
|
||||
static void onAdfError(char* message)
|
||||
{
|
||||
throw FilesystemException(message);
|
||||
}
|
||||
|
||||
void adfInitNativeFct()
|
||||
{
|
||||
auto cbs = (struct nativeFunctions*)adfEnv.nativeFct;
|
||||
@@ -352,6 +437,9 @@ void adfInitNativeFct()
|
||||
cbs->adfNativeWriteSector = ::adfNativeWriteSector;
|
||||
cbs->adfIsDevNative = ::adfIsDevNative;
|
||||
cbs->adfReleaseDevice = ::adfReleaseDevice;
|
||||
|
||||
adfChgEnvProp(PR_WFCT, (void*)onAdfWarning);
|
||||
adfChgEnvProp(PR_EFCT, (void*)onAdfError);
|
||||
}
|
||||
|
||||
static RETCODE adfInitDevice(struct Device* dev, char* name, BOOL ro)
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
ByteReader br(bytes);
|
||||
filename = br.read(8);
|
||||
filename = filename.substr(0, filename.find(' '));
|
||||
path = {filename};
|
||||
|
||||
this->inode = inode;
|
||||
brother_type = br.read_8();
|
||||
@@ -40,6 +41,14 @@ public:
|
||||
length = sector_length * SECTOR_SIZE;
|
||||
file_type = TYPE_FILE;
|
||||
mode = "";
|
||||
|
||||
attributes[Filesystem::FILENAME] = filename;
|
||||
attributes[Filesystem::LENGTH] = fmt::format("{}", length);
|
||||
attributes[Filesystem::FILE_TYPE] = "file";
|
||||
attributes[Filesystem::MODE] = mode;
|
||||
attributes["brother120.inode"] = fmt::format("{}", inode);
|
||||
attributes["brother120.start_sector"] = fmt::format("{}", start_sector);
|
||||
attributes["brother120.type"] = fmt::format("{}", brother_type);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -68,7 +77,7 @@ public:
|
||||
|
||||
auto de = std::make_unique<Brother120Dirent>(inode, buffer);
|
||||
usedSectors += de->sector_length;
|
||||
// dirents.push_back(std::move(de));
|
||||
dirents.push_back(std::move(de));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +121,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
BrotherDirectory dir(this);
|
||||
|
||||
@@ -124,12 +138,12 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
if (!path.empty())
|
||||
throw FileNotFoundException();
|
||||
@@ -143,7 +157,7 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
BrotherDirectory dir(this);
|
||||
auto dirent = dir.findFile(path);
|
||||
@@ -160,22 +174,10 @@ public:
|
||||
return data;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
std::map<std::string, std::string> attributes;
|
||||
|
||||
BrotherDirectory dir(this);
|
||||
auto dirent = dir.findFile(path);
|
||||
attributes[FILENAME] = dirent->filename;
|
||||
attributes[LENGTH] = fmt::format("{}", dirent->length);
|
||||
attributes[FILE_TYPE] = "file";
|
||||
attributes[MODE] = dirent->mode;
|
||||
attributes["brother120.inode"] = fmt::format("{}", dirent->inode);
|
||||
attributes["brother120.start_sector"] =
|
||||
fmt::format("{}", dirent->start_sector);
|
||||
attributes["brother120.type"] = fmt::format("{}", dirent->brother_type);
|
||||
|
||||
return attributes;
|
||||
return dir.findFile(path);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -74,6 +74,7 @@ class CbmfsFilesystem : public Filesystem
|
||||
|
||||
auto filenameBytes = br.read(16).split(0xa0)[0];
|
||||
filename = fromPetscii(filenameBytes);
|
||||
path = {filename};
|
||||
side_track = br.read_8();
|
||||
side_sector = br.read_8();
|
||||
recordlen = br.read_8();
|
||||
@@ -83,6 +84,18 @@ class CbmfsFilesystem : public Filesystem
|
||||
file_type = TYPE_FILE;
|
||||
length = sectors * 254;
|
||||
mode = "";
|
||||
|
||||
attributes[Filesystem::FILENAME] = filename;
|
||||
attributes[Filesystem::LENGTH] = fmt::format("{}", length);
|
||||
attributes[Filesystem::FILE_TYPE] = "file";
|
||||
attributes[Filesystem::MODE] = mode;
|
||||
attributes["cbmfs.type"] = toFileType(cbm_type);
|
||||
attributes["cbmfs.start_track"] = fmt::format("{}", start_track);
|
||||
attributes["cbmfs.start_sector"] = fmt::format("{}", start_sector);
|
||||
attributes["cbmfs.side_track"] = fmt::format("{}", side_track);
|
||||
attributes["cbmfs.side_sector"] = fmt::format("{}", side_sector);
|
||||
attributes["cbmfs.recordlen"] = fmt::format("{}", recordlen);
|
||||
attributes["cbmfs.sectors"] = fmt::format("{}", sectors);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -182,7 +195,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
Directory dir(this);
|
||||
|
||||
@@ -196,12 +214,12 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
if (path.size() != 0)
|
||||
throw BadPathException();
|
||||
@@ -214,29 +232,16 @@ public:
|
||||
return results;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
Directory dir(this);
|
||||
auto de = dir.findFile(unhex(path[0]));
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[FILENAME] = de->filename;
|
||||
attributes[LENGTH] = fmt::format("{}", de->length);
|
||||
attributes[FILE_TYPE] = "file";
|
||||
attributes[MODE] = de->mode;
|
||||
attributes["cbmfs.type"] = toFileType(de->cbm_type);
|
||||
attributes["cbmfs.start_track"] = fmt::format("{}", de->start_track);
|
||||
attributes["cbmfs.start_sector"] = fmt::format("{}", de->start_sector);
|
||||
attributes["cbmfs.side_track"] = fmt::format("{}", de->side_track);
|
||||
attributes["cbmfs.side_sector"] = fmt::format("{}", de->side_sector);
|
||||
attributes["cbmfs.recordlen"] = fmt::format("{}", de->recordlen);
|
||||
attributes["cbmfs.sectors"] = fmt::format("{}", de->sectors);
|
||||
return attributes;
|
||||
Directory dir(this);
|
||||
return dir.findFile(unhex(path[0]));
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
|
||||
@@ -82,7 +82,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_LIST | OP_GETFILE | OP_GETDIRENT;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
mount();
|
||||
|
||||
@@ -108,12 +113,12 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (!path.empty())
|
||||
@@ -130,6 +135,7 @@ public:
|
||||
if (!dirent)
|
||||
{
|
||||
dirent = std::make_unique<Dirent>();
|
||||
dirent->path = {entry->filename};
|
||||
dirent->filename = entry->filename;
|
||||
dirent->mode = entry->mode;
|
||||
dirent->length = 0;
|
||||
@@ -142,50 +148,33 @@ public:
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> result;
|
||||
for (auto& e : map)
|
||||
result.push_back(std::move(e.second));
|
||||
{
|
||||
auto& de = e.second;
|
||||
de->attributes[FILENAME] = de->filename;
|
||||
de->attributes[LENGTH] = fmt::format("{}", de->length);
|
||||
de->attributes[FILE_TYPE] = "file";
|
||||
de->attributes[MODE] = de->mode;
|
||||
result.push_back(std::move(de));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (path.size() != 1)
|
||||
throw BadPathException();
|
||||
|
||||
std::shared_ptr<Dirent> dirent;
|
||||
for (int d = 0; d < _config.dir_entries(); d++)
|
||||
for (const auto& dirent : list(Path()))
|
||||
{
|
||||
auto entry = getEntry(d);
|
||||
if (!entry)
|
||||
continue;
|
||||
if (path[0] != entry->filename)
|
||||
continue;
|
||||
|
||||
if (!dirent)
|
||||
{
|
||||
dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = entry->filename;
|
||||
dirent->mode = entry->mode;
|
||||
dirent->length = 0;
|
||||
dirent->file_type = TYPE_FILE;
|
||||
if (dirent->filename == path.front())
|
||||
return dirent;
|
||||
}
|
||||
|
||||
dirent->length = std::max(
|
||||
dirent->length, entry->extent * 16384 + entry->records * 128);
|
||||
}
|
||||
|
||||
if (!dirent)
|
||||
throw FileNotFoundException();
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[FILENAME] = dirent->filename;
|
||||
attributes[LENGTH] = fmt::format("{}", dirent->length);
|
||||
attributes[FILE_TYPE] = "file";
|
||||
attributes[MODE] = dirent->mode;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
if (path.size() != 1)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
extern "C"
|
||||
@@ -36,7 +37,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
|
||||
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
mount();
|
||||
|
||||
@@ -64,31 +71,34 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void create(bool quick, const std::string& volumeName)
|
||||
void create(bool quick, const std::string& volumeName) override
|
||||
{
|
||||
if (!quick)
|
||||
eraseEverythingOnDisk();
|
||||
|
||||
char buffer[FF_MAX_SS * 2];
|
||||
currentFatFs = this;
|
||||
FRESULT res = f_mkfs("", nullptr, buffer, sizeof(buffer));
|
||||
MKFS_PARM parm = {
|
||||
.fmt = FM_SFD | FM_ANY,
|
||||
};
|
||||
FRESULT res = f_mkfs("", &parm, buffer, sizeof(buffer));
|
||||
throwError(res);
|
||||
|
||||
mount();
|
||||
f_setlabel(volumeName.c_str());
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
|
||||
DIR dir;
|
||||
auto pathstr = path.to_str();
|
||||
auto pathstr = toUpper(path.to_str());
|
||||
FRESULT res = f_opendir(&dir, pathstr.c_str());
|
||||
std::vector<std::shared_ptr<Dirent>> results;
|
||||
|
||||
@@ -101,41 +111,30 @@ public:
|
||||
if (filinfo.fname[0] == 0)
|
||||
break;
|
||||
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = filinfo.fname;
|
||||
dirent->length = filinfo.fsize;
|
||||
dirent->file_type =
|
||||
(filinfo.fattrib & AM_DIR) ? TYPE_DIRECTORY : TYPE_FILE;
|
||||
dirent->mode = modeToString(filinfo.fattrib);
|
||||
results.push_back(dirent);
|
||||
results.push_back(toDirent(filinfo, path));
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
return results;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
std::map<std::string, std::string> attributes;
|
||||
|
||||
mount();
|
||||
auto pathstr = path.to_str();
|
||||
auto pathstr = toUpper(path.to_str());
|
||||
FILINFO filinfo;
|
||||
FRESULT res = f_stat(pathstr.c_str(), &filinfo);
|
||||
throwError(res);
|
||||
|
||||
attributes[FILENAME] = filinfo.fname;
|
||||
attributes[LENGTH] = fmt::format("{}", filinfo.fsize);
|
||||
attributes[FILE_TYPE] = (filinfo.fattrib & AM_DIR) ? "dir" : "file";
|
||||
attributes[MODE] = modeToString(filinfo.fattrib);
|
||||
|
||||
return attributes;
|
||||
return toDirent(filinfo, path.parent());
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
auto pathstr = path.to_str();
|
||||
auto pathstr = toUpper(path.to_str());
|
||||
FIL fil;
|
||||
FRESULT res = f_open(&fil, pathstr.c_str(), FA_READ);
|
||||
throwError(res);
|
||||
@@ -158,10 +157,10 @@ public:
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void putFile(const Path& path, const Bytes& bytes)
|
||||
void putFile(const Path& path, const Bytes& bytes) override
|
||||
{
|
||||
mount();
|
||||
auto pathstr = path.to_str();
|
||||
auto pathstr = toUpper(path.to_str());
|
||||
FIL fil;
|
||||
FRESULT res =
|
||||
f_open(&fil, pathstr.c_str(), FA_WRITE | FA_CREATE_ALWAYS);
|
||||
@@ -182,6 +181,52 @@ public:
|
||||
f_close(&fil);
|
||||
}
|
||||
|
||||
void deleteFile(const Path& path) override
|
||||
{
|
||||
mount();
|
||||
auto pathstr = toUpper(path.to_str());
|
||||
FRESULT res = f_unlink(pathstr.c_str());
|
||||
throwError(res);
|
||||
}
|
||||
|
||||
void moveFile(const Path& oldPath, const Path& newPath) override
|
||||
{
|
||||
mount();
|
||||
auto oldPathStr = toUpper(oldPath.to_str());
|
||||
auto newPathStr = toUpper(newPath.to_str());
|
||||
FRESULT res = f_rename(oldPathStr.c_str(), newPathStr.c_str());
|
||||
throwError(res);
|
||||
}
|
||||
|
||||
void createDirectory(const Path& path)
|
||||
{
|
||||
mount();
|
||||
auto pathStr = path.to_str();
|
||||
FRESULT res = f_mkdir(pathStr.c_str());
|
||||
throwError(res);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Dirent> toDirent(FILINFO& filinfo, const Path& parent)
|
||||
{
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = toUpper(filinfo.fname);
|
||||
dirent->path = parent;
|
||||
dirent->path.push_back(dirent->filename);
|
||||
dirent->length = filinfo.fsize;
|
||||
dirent->file_type =
|
||||
(filinfo.fattrib & AM_DIR) ? TYPE_DIRECTORY : TYPE_FILE;
|
||||
dirent->mode = modeToString(filinfo.fattrib);
|
||||
|
||||
dirent->attributes[Filesystem::FILENAME] = filinfo.fname;
|
||||
dirent->attributes[Filesystem::LENGTH] =
|
||||
fmt::format("{}", filinfo.fsize);
|
||||
dirent->attributes[Filesystem::FILE_TYPE] =
|
||||
(filinfo.fattrib & AM_DIR) ? "dir" : "file";
|
||||
dirent->attributes[Filesystem::MODE] = modeToString(filinfo.fattrib);
|
||||
return dirent;
|
||||
}
|
||||
|
||||
public:
|
||||
DRESULT diskRead(BYTE* buffer, LBA_t sector, UINT count)
|
||||
{
|
||||
|
||||
@@ -24,82 +24,92 @@ public:
|
||||
|
||||
public:
|
||||
std::shared_ptr<const Sector> get(
|
||||
unsigned track, unsigned side, unsigned sectorId)
|
||||
unsigned track, unsigned side, unsigned sectorId) override
|
||||
{
|
||||
auto it = _changedSectors.get(track, side, sectorId);
|
||||
if (it)
|
||||
return it;
|
||||
|
||||
trackid_t trackid(track, side);
|
||||
if (_loadedtracks.find(trackid) == _loadedtracks.end())
|
||||
if (_loadedTracks.find(trackid) == _loadedTracks.end())
|
||||
populateSectors(track, side);
|
||||
|
||||
return _readSectors.get(track, side, sectorId);
|
||||
return _loadedSectors.get(track, side, sectorId);
|
||||
}
|
||||
|
||||
std::shared_ptr<Sector> put(
|
||||
unsigned track, unsigned side, unsigned sectorId)
|
||||
unsigned track, unsigned side, unsigned sectorId) override
|
||||
{
|
||||
trackid_t trackid(track, side);
|
||||
_changedtracks.insert(trackid);
|
||||
_changedTracks.insert(trackid);
|
||||
return _changedSectors.put(track, side, sectorId);
|
||||
}
|
||||
|
||||
void flush()
|
||||
virtual bool isReadOnly()
|
||||
{
|
||||
for (const auto& trackid : _changedtracks)
|
||||
return (_fluxSink == nullptr);
|
||||
}
|
||||
|
||||
bool needsFlushing() override
|
||||
{
|
||||
return !_changedTracks.empty();
|
||||
}
|
||||
|
||||
void flushChanges() override
|
||||
{
|
||||
std::set<Location> locations;
|
||||
|
||||
for (const auto& trackid : _changedTracks)
|
||||
{
|
||||
unsigned track = trackid.first;
|
||||
unsigned side = trackid.second;
|
||||
auto layoutdata = Layout::getLayoutOfTrack(track, side);
|
||||
auto sectors = Layout::getSectorsInTrack(layoutdata);
|
||||
locations.insert(Mapper::computeLocationFor(track, side));
|
||||
|
||||
config.mutable_tracks()->Clear();
|
||||
config.mutable_tracks()->set_start(track);
|
||||
/* If we don't have all the sectors of this track, we may need to
|
||||
* populate any non-changed sectors as we can only write a track at
|
||||
* a time. */
|
||||
|
||||
config.mutable_heads()->Clear();
|
||||
config.mutable_heads()->set_start(side);
|
||||
|
||||
/* Check to see if we have all sectors of this track in the
|
||||
* changesectors image. */
|
||||
|
||||
if (imageContainsAllSectorsOf(
|
||||
if (!imageContainsAllSectorsOf(
|
||||
_changedSectors, track, side, sectors))
|
||||
{
|
||||
/* Just write directly from the changedsectors image. */
|
||||
/* If we don't have any loaded sectors for this track, pre-read
|
||||
* it. */
|
||||
|
||||
writeDiskCommand(_changedSectors,
|
||||
*_encoder,
|
||||
*_fluxSink,
|
||||
&*_decoder,
|
||||
&*_fluxSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Only a few sectors have changed. Do we need to populate the
|
||||
* track? */
|
||||
|
||||
if (_loadedtracks.find(trackid) == _loadedtracks.end())
|
||||
if (_loadedTracks.find(trackid) == _loadedTracks.end())
|
||||
populateSectors(track, side);
|
||||
|
||||
/* Now merge the loaded track with the changed one, and write
|
||||
* the result back. */
|
||||
|
||||
Image image;
|
||||
for (const unsigned sector : sectors)
|
||||
{
|
||||
auto s = image.put(track, side, sector);
|
||||
if (_changedSectors.contains(track, side, sector))
|
||||
s->data =
|
||||
_changedSectors.get(track, side, sector)->data;
|
||||
else
|
||||
s->data = _readSectors.get(track, side, sector)->data;
|
||||
if (!_changedSectors.contains(track, side, sector))
|
||||
_changedSectors.put(track, side, sector)->data =
|
||||
_loadedSectors.get(track, side, sector)->data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeDiskCommand(
|
||||
image, *_encoder, *_fluxSink, &*_decoder, &*_fluxSource);
|
||||
}
|
||||
/* We now have complete tracks which can be written. */
|
||||
|
||||
writeDiskCommand(_changedSectors,
|
||||
*_encoder,
|
||||
*_fluxSink,
|
||||
&*_decoder,
|
||||
&*_fluxSource,
|
||||
locations);
|
||||
|
||||
discardChanges();
|
||||
}
|
||||
|
||||
void discardChanges() override
|
||||
{
|
||||
_loadedTracks.clear();
|
||||
_loadedSectors.clear();
|
||||
_changedTracks.clear();
|
||||
_changedSectors.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -122,8 +132,8 @@ private:
|
||||
auto trackdata = readAndDecodeTrack(*_fluxSource, *_decoder, location);
|
||||
|
||||
for (const auto& sector : trackdata->sectors)
|
||||
*_readSectors.put(track, side, sector->logicalSector) = *sector;
|
||||
_loadedtracks.insert(trackid_t(track, side));
|
||||
*_loadedSectors.put(track, side, sector->logicalSector) = *sector;
|
||||
_loadedTracks.insert(trackid_t(track, side));
|
||||
}
|
||||
|
||||
std::shared_ptr<FluxSource> _fluxSource;
|
||||
@@ -132,10 +142,10 @@ private:
|
||||
std::shared_ptr<AbstractDecoder> _decoder;
|
||||
|
||||
typedef std::pair<unsigned, unsigned> trackid_t;
|
||||
Image _readSectors;
|
||||
Image _loadedSectors;
|
||||
Image _changedSectors;
|
||||
std::set<trackid_t> _loadedtracks;
|
||||
std::set<trackid_t> _changedtracks;
|
||||
std::set<trackid_t> _loadedTracks;
|
||||
std::set<trackid_t> _changedTracks;
|
||||
};
|
||||
|
||||
std::unique_ptr<SectorInterface> SectorInterface::createFluxSectorInterface(
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/vfs/sectorinterface.h"
|
||||
#include "lib/imagereader/imagereader.h"
|
||||
#include "lib/imagewriter/imagewriter.h"
|
||||
#include "lib/image.h"
|
||||
#include "lib/layout.h"
|
||||
#include "lib/sector.h"
|
||||
#include "lib/bytes.h"
|
||||
|
||||
class ImageSectorInterface : public SectorInterface
|
||||
{
|
||||
public:
|
||||
ImageSectorInterface(std::shared_ptr<Image> image): _image(image) {}
|
||||
ImageSectorInterface(std::shared_ptr<ImageReader> reader,
|
||||
std::shared_ptr<ImageWriter> writer):
|
||||
_reader(reader),
|
||||
_writer(writer)
|
||||
{
|
||||
discardChanges();
|
||||
}
|
||||
|
||||
public:
|
||||
std::shared_ptr<const Sector> get(
|
||||
@@ -17,15 +28,47 @@ public:
|
||||
std::shared_ptr<Sector> put(
|
||||
unsigned track, unsigned side, unsigned sectorId)
|
||||
{
|
||||
_changed = true;
|
||||
return _image->put(track, side, sectorId);
|
||||
}
|
||||
|
||||
virtual bool isReadOnly()
|
||||
{
|
||||
return (_writer == nullptr);
|
||||
}
|
||||
|
||||
bool needsFlushing() override
|
||||
{
|
||||
return _changed;
|
||||
}
|
||||
|
||||
void flushChanges() override
|
||||
{
|
||||
_writer->writeImage(*_image);
|
||||
_changed = false;
|
||||
}
|
||||
|
||||
void discardChanges() override
|
||||
{
|
||||
if (_reader)
|
||||
_image = _reader->readImage();
|
||||
else
|
||||
{
|
||||
_image = std::make_shared<Image>();
|
||||
_image->createBlankImage();
|
||||
}
|
||||
_changed = false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Image> _image;
|
||||
std::shared_ptr<ImageReader> _reader;
|
||||
std::shared_ptr<ImageWriter> _writer;
|
||||
bool _changed = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<SectorInterface> SectorInterface::createImageSectorInterface(
|
||||
std::shared_ptr<Image> image)
|
||||
std::shared_ptr<ImageReader> reader, std::shared_ptr<ImageWriter> writer)
|
||||
{
|
||||
return std::make_unique<ImageSectorInterface>(image);
|
||||
return std::make_unique<ImageSectorInterface>(reader, writer);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata()
|
||||
uint32_t capabilities() const
|
||||
{
|
||||
return OP_GETFSDATA | OP_CREATE | OP_LIST | OP_GETFILE | OP_PUTFILE |
|
||||
OP_GETDIRENT | OP_MOVE | OP_CREATEDIR | OP_DELETE;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata() override
|
||||
{
|
||||
HfsMount m(this);
|
||||
|
||||
@@ -42,21 +48,26 @@ public:
|
||||
return attributes;
|
||||
}
|
||||
|
||||
FilesystemStatus check()
|
||||
FilesystemStatus check() override
|
||||
{
|
||||
return FS_OK;
|
||||
}
|
||||
|
||||
void create(bool quick, const std::string& volumeName)
|
||||
void create(bool quick, const std::string& volumeName) override
|
||||
{
|
||||
if (!quick)
|
||||
eraseEverythingOnDisk();
|
||||
|
||||
hfs_format(
|
||||
(const char*)this, 0, HFS_MODE_ANY, volumeName.c_str(), 0, nullptr);
|
||||
if (hfs_format((const char*)this,
|
||||
0,
|
||||
HFS_MODE_ANY,
|
||||
volumeName.c_str(),
|
||||
0,
|
||||
nullptr))
|
||||
throwError();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path)
|
||||
std::vector<std::shared_ptr<Dirent>> list(const Path& path) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
|
||||
@@ -64,7 +75,7 @@ public:
|
||||
auto pathstr = ":" + path.to_str(":");
|
||||
HfsDir dir(hfs_opendir(_vol, pathstr.c_str()));
|
||||
if (!dir)
|
||||
throw FileNotFoundException();
|
||||
throwError();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -73,26 +84,12 @@ public:
|
||||
if (r != 0)
|
||||
break;
|
||||
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = de.name;
|
||||
if (de.flags & HFS_ISDIR)
|
||||
{
|
||||
|
||||
dirent->file_type = TYPE_DIRECTORY;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirent->file_type = TYPE_FILE;
|
||||
dirent->length =
|
||||
de.u.file.dsize + de.u.file.rsize + AppleSingle::OVERHEAD;
|
||||
}
|
||||
dirent->mode = (de.flags & HFS_ISLOCKED) ? "L" : "";
|
||||
results.push_back(dirent);
|
||||
results.push_back(toDirent(de, path));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> getDirent(const Path& path) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -102,46 +99,12 @@ public:
|
||||
auto pathstr = ":" + path.to_str(":");
|
||||
hfsdirent de;
|
||||
if (hfs_stat(_vol, pathstr.c_str(), &de))
|
||||
throw FileNotFoundException();
|
||||
throwError();
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes[FILENAME] = de.name;
|
||||
attributes[LENGTH] = "0";
|
||||
attributes[FILE_TYPE] = (de.flags & HFS_ISDIR) ? "dir" : "file";
|
||||
attributes[MODE] = (de.flags & HFS_ISLOCKED) ? "L" : "";
|
||||
attributes["machfs.ctime"] = toIso8601(de.crdate);
|
||||
attributes["machfs.mtime"] = toIso8601(de.mddate);
|
||||
attributes["machfs.last_backup"] = toIso8601(de.bkdate);
|
||||
attributes["machfs.finder.x"] = fmt::format("{}", de.fdlocation.h);
|
||||
attributes["machfs.finder.y"] = fmt::format("{}", de.fdlocation.v);
|
||||
attributes["machfs.finder.flags"] = fmt::format("0x{:x}", de.fdflags);
|
||||
if (de.flags & HFS_ISDIR)
|
||||
{
|
||||
attributes["machfs.dir.valence"] =
|
||||
fmt::format("{}", de.u.dir.valence);
|
||||
attributes["machfs.dir.x1"] = fmt::format("{}", de.u.dir.rect.left);
|
||||
attributes["machfs.dir.y1"] = fmt::format("{}", de.u.dir.rect.top);
|
||||
attributes["machfs.dir.x2"] =
|
||||
fmt::format("{}", de.u.dir.rect.right);
|
||||
attributes["machfs.dir.y2"] =
|
||||
fmt::format("{}", de.u.dir.rect.bottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
attributes["length"] = fmt::format("{}",
|
||||
de.u.file.dsize + de.u.file.rsize + AppleSingle::OVERHEAD);
|
||||
attributes["machfs.file.dsize"] =
|
||||
fmt::format("{}", de.u.file.dsize);
|
||||
attributes["machfs.file.rsize"] =
|
||||
fmt::format("{}", de.u.file.rsize);
|
||||
attributes["machfs.file.type"] = de.u.file.type;
|
||||
attributes["machfs.file.creator"] = de.u.file.creator;
|
||||
return toDirent(de, path.parent());
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
Bytes getFile(const Path& path)
|
||||
Bytes getFile(const Path& path) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -151,7 +114,7 @@ public:
|
||||
auto pathstr = ":" + path.to_str(":");
|
||||
HfsFile file(hfs_open(_vol, pathstr.c_str()));
|
||||
if (!file)
|
||||
throw FileNotFoundException();
|
||||
throwError();
|
||||
|
||||
AppleSingle a;
|
||||
|
||||
@@ -168,7 +131,7 @@ public:
|
||||
return a.render();
|
||||
}
|
||||
|
||||
void putFile(const Path& path, const Bytes& bytes)
|
||||
void putFile(const Path& path, const Bytes& bytes) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
if (path.size() == 0)
|
||||
@@ -192,7 +155,7 @@ public:
|
||||
(const char*)a.type.cbegin(),
|
||||
(const char*)a.creator.cbegin()));
|
||||
if (!file)
|
||||
throw CannotWriteException();
|
||||
throwError();
|
||||
|
||||
hfs_setfork(file, 0);
|
||||
writeBytes(file, a.data);
|
||||
@@ -200,6 +163,146 @@ public:
|
||||
writeBytes(file, a.rsrc);
|
||||
}
|
||||
|
||||
void deleteFile(const Path& path) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
if (path.size() == 0)
|
||||
throw BadPathException();
|
||||
|
||||
auto pathstr = ":" + path.to_str(":");
|
||||
if (hfs_delete(_vol, pathstr.c_str()))
|
||||
throwError();
|
||||
}
|
||||
|
||||
void moveFile(const Path& oldPath, const Path& newPath) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
if (oldPath.empty() || newPath.empty())
|
||||
throw BadPathException();
|
||||
|
||||
auto oldPathStr = ":" + oldPath.to_str(":");
|
||||
auto newPathStr = ":" + newPath.to_str(":");
|
||||
if (hfs_rename(_vol, oldPathStr.c_str(), newPathStr.c_str()))
|
||||
throwError();
|
||||
}
|
||||
|
||||
void createDirectory(const Path& path) override
|
||||
{
|
||||
HfsMount m(this);
|
||||
|
||||
auto pathStr = ":" + path.to_str(":");
|
||||
if (hfs_mkdir(_vol, pathStr.c_str()))
|
||||
throwError();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Dirent> toDirent(hfsdirent& de, const Path& parent)
|
||||
{
|
||||
auto dirent = std::make_shared<Dirent>();
|
||||
dirent->filename = de.name;
|
||||
dirent->path = parent;
|
||||
dirent->path.push_back(de.name);
|
||||
if (de.flags & HFS_ISDIR)
|
||||
dirent->file_type = TYPE_DIRECTORY;
|
||||
else
|
||||
{
|
||||
dirent->file_type = TYPE_FILE;
|
||||
dirent->length =
|
||||
de.u.file.dsize + de.u.file.rsize + AppleSingle::OVERHEAD;
|
||||
}
|
||||
dirent->mode = (de.flags & HFS_ISLOCKED) ? "L" : "";
|
||||
|
||||
dirent->attributes[FILENAME] = de.name;
|
||||
dirent->attributes[LENGTH] = "0";
|
||||
dirent->attributes[FILE_TYPE] = (de.flags & HFS_ISDIR) ? "dir" : "file";
|
||||
dirent->attributes[MODE] = (de.flags & HFS_ISLOCKED) ? "L" : "";
|
||||
dirent->attributes["machfs.ctime"] = toIso8601(de.crdate);
|
||||
dirent->attributes["machfs.mtime"] = toIso8601(de.mddate);
|
||||
dirent->attributes["machfs.last_backup"] = toIso8601(de.bkdate);
|
||||
dirent->attributes["machfs.finder.x"] =
|
||||
fmt::format("{}", de.fdlocation.h);
|
||||
dirent->attributes["machfs.finder.y"] =
|
||||
fmt::format("{}", de.fdlocation.v);
|
||||
dirent->attributes["machfs.finder.flags"] =
|
||||
fmt::format("0x{:x}", de.fdflags);
|
||||
if (de.flags & HFS_ISDIR)
|
||||
{
|
||||
dirent->attributes["machfs.dir.valence"] =
|
||||
fmt::format("{}", de.u.dir.valence);
|
||||
dirent->attributes["machfs.dir.x1"] =
|
||||
fmt::format("{}", de.u.dir.rect.left);
|
||||
dirent->attributes["machfs.dir.y1"] =
|
||||
fmt::format("{}", de.u.dir.rect.top);
|
||||
dirent->attributes["machfs.dir.x2"] =
|
||||
fmt::format("{}", de.u.dir.rect.right);
|
||||
dirent->attributes["machfs.dir.y2"] =
|
||||
fmt::format("{}", de.u.dir.rect.bottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
dirent->attributes["length"] = fmt::format("{}",
|
||||
de.u.file.dsize + de.u.file.rsize + AppleSingle::OVERHEAD);
|
||||
dirent->attributes["machfs.file.dsize"] =
|
||||
fmt::format("{}", de.u.file.dsize);
|
||||
dirent->attributes["machfs.file.rsize"] =
|
||||
fmt::format("{}", de.u.file.rsize);
|
||||
dirent->attributes["machfs.file.type"] = de.u.file.type;
|
||||
dirent->attributes["machfs.file.creator"] = de.u.file.creator;
|
||||
}
|
||||
|
||||
return dirent;
|
||||
}
|
||||
|
||||
void throwError()
|
||||
{
|
||||
auto message =
|
||||
fmt::format("HFS error: {}", hfs_error ? hfs_error : "unknown");
|
||||
switch (errno)
|
||||
{
|
||||
case ENOTDIR:
|
||||
case ENAMETOOLONG:
|
||||
if (hfs_error)
|
||||
throw BadPathException(message);
|
||||
else
|
||||
throw BadPathException();
|
||||
|
||||
case ENOENT:
|
||||
if (hfs_error)
|
||||
throw FileNotFoundException(message);
|
||||
else
|
||||
throw FileNotFoundException();
|
||||
|
||||
case EEXIST:
|
||||
throw BadPathException("That already exists");
|
||||
|
||||
case ENOTEMPTY:
|
||||
throw BadPathException("Directory is not empty");
|
||||
|
||||
case EISDIR:
|
||||
throw BadPathException("That's a directory");
|
||||
|
||||
case EIO:
|
||||
case EINVAL:
|
||||
if (hfs_error)
|
||||
throw BadFilesystemException(message);
|
||||
else
|
||||
throw BadFilesystemException();
|
||||
|
||||
case ENOSPC:
|
||||
case ENOMEM:
|
||||
if (hfs_error)
|
||||
throw DiskFullException(message);
|
||||
else
|
||||
throw DiskFullException();
|
||||
|
||||
case EROFS:
|
||||
if (hfs_error)
|
||||
throw ReadOnlyFilesystemException(message);
|
||||
else
|
||||
throw ReadOnlyFilesystemException();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Bytes readBytes(hfsfile* file)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#ifndef SECTORINTERFACE_H
|
||||
#define SECTORINTERFACE_H
|
||||
|
||||
class Image;
|
||||
class ImageReader;
|
||||
class ImageWriter;
|
||||
class Sector;
|
||||
class FluxSource;
|
||||
class FluxSink;
|
||||
@@ -16,11 +17,24 @@ public:
|
||||
virtual std::shared_ptr<Sector> put(
|
||||
unsigned track, unsigned side, unsigned sectorId) = 0;
|
||||
|
||||
virtual void flush() {}
|
||||
virtual bool isReadOnly()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool needsFlushing()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void flushChanges() {}
|
||||
|
||||
virtual void discardChanges() {}
|
||||
|
||||
public:
|
||||
static std::unique_ptr<SectorInterface> createImageSectorInterface(
|
||||
std::shared_ptr<Image> image);
|
||||
std::shared_ptr<ImageReader> reader,
|
||||
std::shared_ptr<ImageWriter> writer);
|
||||
static std::unique_ptr<SectorInterface> createFluxSectorInterface(
|
||||
std::shared_ptr<FluxSource> fluxSource,
|
||||
std::shared_ptr<FluxSink> fluxSink,
|
||||
|
||||
101
lib/vfs/vfs.cc
101
lib/vfs/vfs.cc
@@ -6,9 +6,20 @@
|
||||
#include "lib/image.h"
|
||||
#include "lib/sector.h"
|
||||
#include "lib/vfs/sectorinterface.h"
|
||||
#include "lib/imagereader/imagereader.h"
|
||||
#include "lib/imagewriter/imagewriter.h"
|
||||
#include "lib/fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsink/fluxsink.h"
|
||||
#include "lib/decoders/decoders.h"
|
||||
#include "lib/encoders/encoders.h"
|
||||
#include "lib/config.pb.h"
|
||||
#include "lib/utils.h"
|
||||
|
||||
Path::Path(const std::vector<std::string> other):
|
||||
std::vector<std::string>(other)
|
||||
{
|
||||
}
|
||||
|
||||
Path::Path(const std::string& path)
|
||||
{
|
||||
if (path == "")
|
||||
@@ -28,11 +39,34 @@ Path::Path(const std::string& path)
|
||||
}
|
||||
}
|
||||
|
||||
Path Path::parent() const
|
||||
{
|
||||
Path p;
|
||||
if (!empty())
|
||||
{
|
||||
for (int i = 0; i < (size() - 1); i++)
|
||||
p.push_back((*this)[i]);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
Path Path::concat(const std::string& s) const
|
||||
{
|
||||
Path p(*this);
|
||||
p.push_back(s);
|
||||
return p;
|
||||
}
|
||||
|
||||
std::string Path::to_str(const std::string sep) const
|
||||
{
|
||||
return join(*this, sep);
|
||||
}
|
||||
|
||||
uint32_t Filesystem::capabilities() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Filesystem::create(bool quick, const std::string& volumeName)
|
||||
{
|
||||
throw UnimplementedFilesystemException();
|
||||
@@ -68,7 +102,7 @@ void Filesystem::putFile(const Path& path, const Bytes& data)
|
||||
throw UnimplementedFilesystemException();
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> Filesystem::getMetadata(const Path& path)
|
||||
std::shared_ptr<Dirent> Filesystem::getDirent(const Path& path)
|
||||
{
|
||||
throw UnimplementedFilesystemException();
|
||||
}
|
||||
@@ -89,9 +123,29 @@ void Filesystem::deleteFile(const Path& path)
|
||||
throw UnimplementedFilesystemException();
|
||||
}
|
||||
|
||||
void Filesystem::flush()
|
||||
void Filesystem::moveFile(const Path& oldName, const Path& newName)
|
||||
{
|
||||
_sectors->flush();
|
||||
throw UnimplementedFilesystemException();
|
||||
}
|
||||
|
||||
bool Filesystem::isReadOnly()
|
||||
{
|
||||
return _sectors->isReadOnly();
|
||||
}
|
||||
|
||||
bool Filesystem::needsFlushing()
|
||||
{
|
||||
return _sectors->needsFlushing();
|
||||
}
|
||||
|
||||
void Filesystem::flushChanges()
|
||||
{
|
||||
_sectors->flushChanges();
|
||||
}
|
||||
|
||||
void Filesystem::discardChanges()
|
||||
{
|
||||
_sectors->discardChanges();
|
||||
}
|
||||
|
||||
Filesystem::Filesystem(std::shared_ptr<SectorInterface> sectors):
|
||||
@@ -153,6 +207,47 @@ std::unique_ptr<Filesystem> Filesystem::createFilesystem(
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Filesystem> Filesystem::createFilesystemFromConfig()
|
||||
{
|
||||
std::shared_ptr<SectorInterface> sectorInterface;
|
||||
if (config.has_flux_source() || config.has_flux_sink())
|
||||
{
|
||||
std::shared_ptr<FluxSource> fluxSource;
|
||||
std::shared_ptr<AbstractDecoder> decoder;
|
||||
std::shared_ptr<FluxSink> fluxSink;
|
||||
std::shared_ptr<AbstractEncoder> encoder;
|
||||
if (config.flux_source().source_case() !=
|
||||
FluxSourceProto::SOURCE_NOT_SET)
|
||||
{
|
||||
fluxSource = FluxSource::create(config.flux_source());
|
||||
decoder = AbstractDecoder::create(config.decoder());
|
||||
}
|
||||
if (config.flux_sink().has_drive())
|
||||
{
|
||||
fluxSink = FluxSink::create(config.flux_sink());
|
||||
encoder = AbstractEncoder::create(config.encoder());
|
||||
}
|
||||
sectorInterface = SectorInterface::createFluxSectorInterface(
|
||||
fluxSource, fluxSink, encoder, decoder);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::shared_ptr<ImageReader> reader;
|
||||
std::shared_ptr<ImageWriter> writer;
|
||||
if (config.image_reader().format_case() !=
|
||||
ImageReaderProto::FORMAT_NOT_SET)
|
||||
reader = ImageReader::create(config.image_reader());
|
||||
if (config.image_writer().format_case() !=
|
||||
ImageWriterProto::FORMAT_NOT_SET)
|
||||
writer = ImageWriter::create(config.image_writer());
|
||||
|
||||
sectorInterface =
|
||||
SectorInterface::createImageSectorInterface(reader, writer);
|
||||
}
|
||||
|
||||
return createFilesystem(config.filesystem(), sectorInterface);
|
||||
}
|
||||
|
||||
Bytes Filesystem::getSector(unsigned track, unsigned side, unsigned sector)
|
||||
{
|
||||
auto s = _sectors->get(track, side, sector);
|
||||
|
||||
@@ -10,18 +10,34 @@ class DfsProto;
|
||||
class FilesystemProto;
|
||||
class SectorInterface;
|
||||
|
||||
class Path : public std::vector<std::string>
|
||||
{
|
||||
public:
|
||||
Path() {}
|
||||
Path(const std::vector<std::string> other);
|
||||
Path(const std::string& text);
|
||||
|
||||
public:
|
||||
Path parent() const;
|
||||
Path concat(const std::string& s) const;
|
||||
std::string to_str(const std::string sep = "/") const;
|
||||
};
|
||||
|
||||
enum FileType
|
||||
{
|
||||
TYPE__INVALID,
|
||||
TYPE_FILE,
|
||||
TYPE_DIRECTORY
|
||||
};
|
||||
|
||||
struct Dirent
|
||||
{
|
||||
Path path;
|
||||
std::string filename;
|
||||
FileType file_type;
|
||||
uint32_t length;
|
||||
std::string mode;
|
||||
std::map<std::string, std::string> attributes;
|
||||
};
|
||||
|
||||
enum FilesystemStatus
|
||||
@@ -33,37 +49,50 @@ enum FilesystemStatus
|
||||
FS_BAD
|
||||
};
|
||||
|
||||
class FilesystemException
|
||||
class FilesystemException : public ErrorException
|
||||
{
|
||||
public:
|
||||
FilesystemException(const std::string& message): message(message) {}
|
||||
|
||||
public:
|
||||
std::string message;
|
||||
FilesystemException(const std::string& message): ErrorException(message) {}
|
||||
};
|
||||
|
||||
class BadPathException : public FilesystemException
|
||||
{
|
||||
public:
|
||||
BadPathException(): FilesystemException("Bad path") {}
|
||||
|
||||
BadPathException(const std::string& msg): FilesystemException(msg) {}
|
||||
};
|
||||
|
||||
class FileNotFoundException : public FilesystemException
|
||||
{
|
||||
public:
|
||||
FileNotFoundException(): FilesystemException("File not found") {}
|
||||
|
||||
FileNotFoundException(const std::string& msg): FilesystemException(msg) {}
|
||||
};
|
||||
|
||||
class BadFilesystemException : public FilesystemException
|
||||
{
|
||||
public:
|
||||
BadFilesystemException(): FilesystemException("Invalid filesystem") {}
|
||||
|
||||
BadFilesystemException(const std::string& msg): FilesystemException(msg) {}
|
||||
};
|
||||
|
||||
class CannotWriteException : public FilesystemException
|
||||
{
|
||||
public:
|
||||
CannotWriteException(): FilesystemException("Cannot write file") {}
|
||||
|
||||
CannotWriteException(const std::string& msg): FilesystemException(msg) {}
|
||||
};
|
||||
|
||||
class DiskFullException : public CannotWriteException
|
||||
{
|
||||
public:
|
||||
DiskFullException(): CannotWriteException("Disk is full") {}
|
||||
|
||||
DiskFullException(const std::string& msg): CannotWriteException(msg) {}
|
||||
};
|
||||
|
||||
class ReadOnlyFilesystemException : public FilesystemException
|
||||
@@ -72,6 +101,11 @@ public:
|
||||
ReadOnlyFilesystemException(): FilesystemException("Read only filesystem")
|
||||
{
|
||||
}
|
||||
|
||||
ReadOnlyFilesystemException(const std::string& msg):
|
||||
FilesystemException(msg)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class UnimplementedFilesystemException : public FilesystemException
|
||||
@@ -88,16 +122,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class Path : public std::vector<std::string>
|
||||
{
|
||||
public:
|
||||
Path() {}
|
||||
Path(const std::string& text);
|
||||
|
||||
public:
|
||||
std::string to_str(const std::string sep = "/") const;
|
||||
};
|
||||
|
||||
class Filesystem
|
||||
{
|
||||
public:
|
||||
@@ -111,9 +135,28 @@ public:
|
||||
static constexpr const char* USED_BLOCKS = "used_blocks";
|
||||
static constexpr const char* BLOCK_SIZE = "block_size";
|
||||
|
||||
enum
|
||||
{
|
||||
OP_CREATE = 0b0000000000000001,
|
||||
OP_CHECK = 0b0000000000000010,
|
||||
OP_LIST = 0b0000000000000100,
|
||||
OP_GETFILE = 0b0000000000001000,
|
||||
OP_PUTFILE = 0b0000000000010000,
|
||||
OP_GETDIRENT = 0b0000000000100000,
|
||||
OP_CREATEDIR = 0b0000000001000000,
|
||||
OP_DELETE = 0b0000000010000000,
|
||||
OP_GETFSDATA = 0b0000000100000000,
|
||||
OP_PUTFSDATA = 0b0000001000000000,
|
||||
OP_PUTATTRS = 0b0000010000000000,
|
||||
OP_MOVE = 0b0000100000000000,
|
||||
};
|
||||
|
||||
public:
|
||||
/* Retrieve capability information. */
|
||||
virtual uint32_t capabilities() const;
|
||||
|
||||
/* Create a filesystem on the disk. */
|
||||
virtual void create(bool quick, const std::string& volmeName);
|
||||
virtual void create(bool quick, const std::string& volumeName);
|
||||
|
||||
/* Are all sectors on the filesystem present and good? (Does not check
|
||||
* filesystem consistency.) */
|
||||
@@ -135,8 +178,8 @@ public:
|
||||
/* Write a file. */
|
||||
virtual void putFile(const Path& path, const Bytes& data);
|
||||
|
||||
/* Get file metadata. */
|
||||
virtual std::map<std::string, std::string> getMetadata(const Path& path);
|
||||
/* Get a single file dirent. */
|
||||
virtual std::shared_ptr<Dirent> getDirent(const Path& path);
|
||||
|
||||
/* Update file metadata. */
|
||||
virtual void putMetadata(
|
||||
@@ -148,8 +191,20 @@ public:
|
||||
/* Deletes a file or non-empty directory. */
|
||||
virtual void deleteFile(const Path& path);
|
||||
|
||||
/* Moves a file (including renaming it). */
|
||||
virtual void moveFile(const Path& oldName, const Path& newName);
|
||||
|
||||
/* Is this filesystem's backing store read-only? */
|
||||
bool isReadOnly();
|
||||
|
||||
/* Does this filesystem need flushing? */
|
||||
bool needsFlushing();
|
||||
|
||||
/* Flushes any changes back to the disk. */
|
||||
void flush();
|
||||
void flushChanges();
|
||||
|
||||
/* Discards any pending changes. */
|
||||
void discardChanges();
|
||||
|
||||
public:
|
||||
Filesystem(std::shared_ptr<SectorInterface> sectors);
|
||||
@@ -188,6 +243,7 @@ public:
|
||||
|
||||
static std::unique_ptr<Filesystem> createFilesystem(
|
||||
const FilesystemProto& config, std::shared_ptr<SectorInterface> image);
|
||||
static std::unique_ptr<Filesystem> createFilesystemFromConfig();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,9 @@ FLUXENGINE_SRCS = \
|
||||
src/fe-getfileinfo.cc \
|
||||
src/fe-inspect.cc \
|
||||
src/fe-ls.cc \
|
||||
src/fe-mkdir.cc \
|
||||
src/fe-mv.cc \
|
||||
src/fe-rm.cc \
|
||||
src/fe-putfile.cc \
|
||||
src/fe-rawread.cc \
|
||||
src/fe-rawwrite.cc \
|
||||
|
||||
@@ -30,9 +30,9 @@ int mainFormat(int argc, const char* argv[])
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
filesystem->create(quick, volumeName);
|
||||
filesystem->flush();
|
||||
filesystem->flushChanges();
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ int mainGetDiskInfo(int argc, const char* argv[])
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
auto attributes = filesystem->getMetadata();
|
||||
|
||||
for (const auto& e : attributes)
|
||||
|
||||
@@ -37,7 +37,7 @@ int mainGetFile(int argc, const char* argv[])
|
||||
if (outputFilename.empty())
|
||||
outputFilename = inputFilename.back();
|
||||
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
auto data = filesystem->getFile(inputFilename);
|
||||
data.writeToFile(outputFilename);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ int mainGetFileInfo(int argc, const char* argv[])
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto attributes = filesystem->getMetadata(Path(directory));
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
auto dirent = filesystem->getDirent(Path(directory));
|
||||
|
||||
for (const auto& e : attributes)
|
||||
for (const auto& e : dirent->attributes)
|
||||
fmt::print("{}={}\n", e.first, quote(e.second));
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
|
||||
@@ -43,7 +43,7 @@ int mainLs(int argc, const char* argv[])
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
auto files = filesystem->list(Path(directory));
|
||||
|
||||
int maxlen = 0;
|
||||
|
||||
39
src/fe-mkdir.cc
Normal file
39
src/fe-mkdir.cc
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/utils.h"
|
||||
#include "src/fileutils.h"
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags({&fileFlags});
|
||||
|
||||
static StringFlag filename({"-p", "--path"}, "directory to create", "");
|
||||
|
||||
int mainMkDir(int argc, const char* argv[])
|
||||
{
|
||||
if (argc == 1)
|
||||
showProfiles("mkdir", formats);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
|
||||
Path path(filename);
|
||||
if (path.size() == 0)
|
||||
Error() << "filename missing";
|
||||
|
||||
filesystem->createDirectory(path);
|
||||
filesystem->flushChanges();
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
{
|
||||
Error() << e.message;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
44
src/fe-mv.cc
Normal file
44
src/fe-mv.cc
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/utils.h"
|
||||
#include "src/fileutils.h"
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags({&fileFlags});
|
||||
|
||||
static StringFlag oldFilename({"--path1"}, "old filename", "");
|
||||
static StringFlag newFilename({"--path2"}, "new filename", "");
|
||||
|
||||
int mainMv(int argc, const char* argv[])
|
||||
{
|
||||
if (argc == 1)
|
||||
showProfiles("mv", formats);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
|
||||
Path oldPath(oldFilename);
|
||||
if (oldPath.size() == 0)
|
||||
Error() << "old filename missing";
|
||||
|
||||
Path newPath(newFilename);
|
||||
if (newPath.size() == 0)
|
||||
Error() << "new filename missing";
|
||||
|
||||
filesystem->moveFile(oldPath, newPath);
|
||||
filesystem->flushChanges();
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
{
|
||||
Error() << e.message;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -38,9 +38,9 @@ int mainPutFile(int argc, const char* argv[])
|
||||
Error() << "you must supply a destination path to write to";
|
||||
|
||||
auto data = Bytes::readFromFile(inputFilename);
|
||||
auto filesystem = createFilesystemFromConfig();
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
filesystem->putFile(outputFilename, data);
|
||||
filesystem->flush();
|
||||
filesystem->flushChanges();
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
{
|
||||
|
||||
39
src/fe-rm.cc
Normal file
39
src/fe-rm.cc
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "globals.h"
|
||||
#include "flags.h"
|
||||
#include "proto.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include "lib/utils.h"
|
||||
#include "src/fileutils.h"
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <fstream>
|
||||
|
||||
static FlagGroup flags({&fileFlags});
|
||||
|
||||
static StringFlag filename({"-p", "--path"}, "filename to remove", "");
|
||||
|
||||
int mainRm(int argc, const char* argv[])
|
||||
{
|
||||
if (argc == 1)
|
||||
showProfiles("rm", formats);
|
||||
flags.parseFlagsWithConfigFiles(argc, argv, formats);
|
||||
|
||||
try
|
||||
{
|
||||
auto filesystem = Filesystem::createFilesystemFromConfig();
|
||||
|
||||
Path path(filename);
|
||||
if (path.size() == 0)
|
||||
Error() << "filename missing";
|
||||
|
||||
filesystem->deleteFile(path);
|
||||
filesystem->flushChanges();
|
||||
}
|
||||
catch (const FilesystemException& e)
|
||||
{
|
||||
Error() << e.message;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -4,11 +4,10 @@
|
||||
#include "sector.h"
|
||||
#include "proto.h"
|
||||
#include "readerwriter.h"
|
||||
#include "lib/decoders/decoders.h"
|
||||
#include "lib/encoders/encoders.h"
|
||||
#include "lib/fluxsource/fluxsource.h"
|
||||
#include "lib/fluxsink/fluxsink.h"
|
||||
#include "lib/imagereader/imagereader.h"
|
||||
#include "lib/imagewriter/imagewriter.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fluxengine.h"
|
||||
#include "lib/vfs/sectorinterface.h"
|
||||
@@ -25,6 +24,8 @@ static StringFlag image({"-i", "--image"},
|
||||
{
|
||||
ImageReader::updateConfigForFilename(
|
||||
config.mutable_image_reader(), value);
|
||||
ImageWriter::updateConfigForFilename(
|
||||
config.mutable_image_writer(), value);
|
||||
});
|
||||
|
||||
static StringFlag flux({"-f", "--flux"},
|
||||
@@ -38,24 +39,3 @@ static StringFlag flux({"-f", "--flux"},
|
||||
config.mutable_flux_sink(), value);
|
||||
});
|
||||
|
||||
std::unique_ptr<Filesystem> createFilesystemFromConfig()
|
||||
{
|
||||
std::shared_ptr<SectorInterface> sectorInterface;
|
||||
if (config.has_flux_source())
|
||||
{
|
||||
std::shared_ptr<FluxSource> fluxSource(FluxSource::create(config.flux_source()));
|
||||
std::shared_ptr<FluxSink> fluxSink(FluxSink::create(config.flux_sink()));
|
||||
std::shared_ptr<AbstractEncoder> encoder(AbstractEncoder::create(config.encoder()));
|
||||
std::shared_ptr<AbstractDecoder> decoder(AbstractDecoder::create(config.decoder()));
|
||||
sectorInterface = SectorInterface::createFluxSectorInterface(fluxSource, fluxSink, encoder, decoder);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reader = ImageReader::create(config.image_reader());
|
||||
std::shared_ptr<Image> image(std::move(reader->readImage()));
|
||||
sectorInterface = SectorInterface::createImageSectorInterface(image);
|
||||
}
|
||||
|
||||
return Filesystem::createFilesystem(config.filesystem(), sectorInterface);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,5 @@
|
||||
|
||||
extern FlagGroup fileFlags;
|
||||
|
||||
extern std::unique_ptr<Filesystem> createFilesystemFromConfig();
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -12,10 +12,13 @@ extern command_cb mainGetFile;
|
||||
extern command_cb mainGetFileInfo;
|
||||
extern command_cb mainInspect;
|
||||
extern command_cb mainLs;
|
||||
extern command_cb mainMkDir;
|
||||
extern command_cb mainMv;
|
||||
extern command_cb mainPutFile;
|
||||
extern command_cb mainRawRead;
|
||||
extern command_cb mainRawWrite;
|
||||
extern command_cb mainRead;
|
||||
extern command_cb mainRm;
|
||||
extern command_cb mainRpm;
|
||||
extern command_cb mainSeek;
|
||||
extern command_cb mainTestBandwidth;
|
||||
@@ -43,9 +46,12 @@ static std::vector<Command> commands =
|
||||
{ "rawwrite", mainRawWrite, "Writes a flux file to a disk. Warning: you can't use this to copy disks.", },
|
||||
{ "getdiskinfo", mainGetDiskInfo, "Read volume metadata off a disk (or image).", },
|
||||
{ "ls", mainLs, "Show files on disk (or image).", },
|
||||
{ "mv", mainMv, "Rename a file on a disk (or image).", },
|
||||
{ "rm", mainRm, "Deletes a file (or directory) off a disk (or image).", },
|
||||
{ "getfile", mainGetFile, "Read a file off a disk (or image).", },
|
||||
{ "getfileinfo", mainGetFileInfo, "Read file metadata off a disk (or image).", },
|
||||
{ "putfile", mainPutFile, "Write a file to disk (or image).", },
|
||||
{ "mkdir", mainMkDir, "Create a directory on disk (or image).", },
|
||||
{ "rpm", mainRpm, "Measures the disk rotational speed.", },
|
||||
{ "seek", mainSeek, "Moves the disk head.", },
|
||||
{ "test", mainTest, "Various testing commands.", },
|
||||
@@ -133,7 +139,7 @@ void showProfiles(const std::string& command, const std::map<std::string, std::s
|
||||
ConfigProto config;
|
||||
if (!config.ParseFromString(it.second))
|
||||
Error() << "couldn't load config proto";
|
||||
if (config.is_extension())
|
||||
if (config.is_extension() && (it.first[0] != '_'))
|
||||
std::cout << fmt::format(" {}: {}\n", it.first, config.comment());
|
||||
}
|
||||
|
||||
|
||||
3
src/formats/_atari.textpb
Normal file
3
src/formats/_atari.textpb
Normal file
@@ -0,0 +1,3 @@
|
||||
comment: 'Common Atari definitions'
|
||||
is_extension: true
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 360kB 3.5" 80-track 9-sector SSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist360.st"
|
||||
img {}
|
||||
@@ -53,7 +55,3 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 370kB 3.5" 82-track 9-sector SSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist370.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 400kB 3.5" 80-track 10-sector SSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist400.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 410kB 3.5" 82-track 10-sector SSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist410.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 0
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 720kB 3.5" 80-track 9-sector DSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist720.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 1
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 740kB 3.5" 82-track 9-sector DSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist740.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 1
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 800kB 3.5" 80-track 10-sector DSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist800.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 1
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
comment: 'Atari ST 820kB 3.5" 82-track 10-sector DSDD'
|
||||
|
||||
include: '_atari'
|
||||
|
||||
image_reader {
|
||||
filename: "atarist820.st"
|
||||
img {}
|
||||
@@ -53,8 +55,4 @@ heads {
|
||||
end: 1
|
||||
}
|
||||
|
||||
filesystem {
|
||||
fatfs {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FORMATS = \
|
||||
_atari \
|
||||
_micropolis \
|
||||
_northstar \
|
||||
_mx \
|
||||
@@ -72,7 +73,7 @@ $(OBJDIR)/src/formats/format_%.cc: $(OBJDIR)/protoencode_ConfigProto.exe src/for
|
||||
|
||||
OBJS += $(patsubst %, $(OBJDIR)/src/formats/format_%.o, $(FORMATS))
|
||||
|
||||
$(OBJDIR)/src/formats/table.cc: scripts/mktable.sh Makefile
|
||||
$(OBJDIR)/src/formats/table.cc: scripts/mktable.sh src/formats/build.mk
|
||||
@mkdir -p $(dir $@)
|
||||
@echo MKTABLE $@
|
||||
@scripts/mktable.sh formats $(FORMATS) > $@
|
||||
|
||||
@@ -2,6 +2,7 @@ ifneq ($(shell $(WX_CONFIG) --version),)
|
||||
|
||||
FLUXENGINE_GUI_SRCS = \
|
||||
src/gui/customstatusbar.cc \
|
||||
src/gui/filesystemmodel.cc \
|
||||
src/gui/fluxviewercontrol.cc \
|
||||
src/gui/fluxviewerwindow.cc \
|
||||
src/gui/layout.cpp \
|
||||
@@ -9,6 +10,7 @@ FLUXENGINE_GUI_SRCS = \
|
||||
src/gui/mainwindow.cc \
|
||||
src/gui/texteditorwindow.cc \
|
||||
src/gui/textviewerwindow.cc \
|
||||
src/gui/fileviewerwindow.cc \
|
||||
src/gui/visualisationcontrol.cc \
|
||||
|
||||
FLUXENGINE_GUI_OBJS = \
|
||||
@@ -17,9 +19,9 @@ FLUXENGINE_GUI_OBJS = \
|
||||
)
|
||||
OBJS += $(FLUXENGINE_GUI_OBJS)
|
||||
$(FLUXENGINE_GUI_SRCS): | $(PROTO_HDRS)
|
||||
$(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv)
|
||||
$(FLUXENGINE_GUI_OBJS): CFLAGS += $(shell $(WX_CONFIG) --cxxflags core base adv aui)
|
||||
FLUXENGINE_GUI_BIN = $(OBJDIR)/fluxengine-gui.exe
|
||||
$(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv)
|
||||
$(FLUXENGINE_GUI_BIN): LDFLAGS += $(shell $(WX_CONFIG) --libs core base adv aui)
|
||||
$(FLUXENGINE_GUI_BIN): $(FLUXENGINE_GUI_OBJS)
|
||||
|
||||
$(call use-pkgconfig, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), fmt)
|
||||
@@ -28,6 +30,9 @@ $(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), LIBFLUXENGINE
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), LIBFORMATS)
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), LIBUSBP)
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), PROTO)
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), FATFS)
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), ADFLIB)
|
||||
$(call use-library, $(FLUXENGINE_GUI_BIN), $(FLUXENGINE_GUI_OBJS), HFSUTILS)
|
||||
|
||||
binaries: fluxengine-gui$(EXT)
|
||||
|
||||
|
||||
299
src/gui/filesystemmodel.cc
Normal file
299
src/gui/filesystemmodel.cc
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "lib/globals.h"
|
||||
#include "gui.h"
|
||||
#include "filesystemmodel.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include <wx/artprov.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
static uintptr_t nodeCount = 0;
|
||||
FilesystemNode::FilesystemNode(std::shared_ptr<Dirent> dirent):
|
||||
dirent(dirent),
|
||||
item((void*)nodeCount++)
|
||||
{
|
||||
}
|
||||
|
||||
class FilesystemModelImpl : public FilesystemModel
|
||||
{
|
||||
public:
|
||||
FilesystemModelImpl():
|
||||
_fileIcon(wxArtProvider::GetIcon(wxART_NORMAL_FILE, wxART_BUTTON)),
|
||||
_folderOpenIcon(
|
||||
wxArtProvider::GetIcon(wxART_FOLDER_OPEN, wxART_BUTTON)),
|
||||
_folderClosedIcon(wxArtProvider::GetIcon(wxART_FOLDER, wxART_BUTTON))
|
||||
{
|
||||
_root = std::make_shared<FilesystemNode>(std::make_shared<Dirent>());
|
||||
_root->dirent->file_type = TYPE_DIRECTORY;
|
||||
|
||||
_byItem[_root->item] = _root;
|
||||
|
||||
ItemAdded(wxDataViewItem(), _root->item);
|
||||
}
|
||||
|
||||
/* --- DataViewModel API --------------------------------------------- */
|
||||
|
||||
unsigned int GetColumnCount() const override
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
wxString GetColumnType(unsigned int column) const override
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
return "wxDataViewIconText";
|
||||
|
||||
case 1:
|
||||
case 2:
|
||||
return "string";
|
||||
|
||||
default:
|
||||
wxFAIL;
|
||||
return "<bad>";
|
||||
}
|
||||
}
|
||||
|
||||
bool IsContainer(const wxDataViewItem& item) const override
|
||||
{
|
||||
auto node = Find(item);
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
return node->dirent->file_type == TYPE_DIRECTORY;
|
||||
}
|
||||
|
||||
wxDataViewItem GetParent(const wxDataViewItem& item) const override
|
||||
{
|
||||
auto node = Find(item);
|
||||
if (!node || (node == _root))
|
||||
return wxDataViewItem();
|
||||
|
||||
return Find(node->dirent->path.parent())->item;
|
||||
}
|
||||
|
||||
void GetValue(wxVariant& value,
|
||||
const wxDataViewItem& item,
|
||||
unsigned column) const override
|
||||
{
|
||||
auto node = Find(item);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
if (node->stub)
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
value << wxDataViewIconText("...loading...");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
value = "";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
value = "";
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
value << wxDataViewIconText(node->dirent->filename,
|
||||
(node->dirent->file_type == TYPE_DIRECTORY)
|
||||
? _folderClosedIcon
|
||||
: _fileIcon);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
value = std::to_string(node->dirent->length);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
value = node->dirent->mode;
|
||||
break;
|
||||
|
||||
default:
|
||||
wxFAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SetValue(const wxVariant& value,
|
||||
const wxDataViewItem& item,
|
||||
unsigned column) override
|
||||
{
|
||||
auto node = Find(item);
|
||||
if (!node || node->stub)
|
||||
return false;
|
||||
|
||||
if ((column == 0) && (value.GetType() == "wxDataViewIconText"))
|
||||
{
|
||||
wxDataViewIconText dvit;
|
||||
dvit << value;
|
||||
node->newname = dvit.GetText();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned GetChildren(const wxDataViewItem& item,
|
||||
wxDataViewItemArray& children) const override
|
||||
{
|
||||
auto node = Find(item);
|
||||
if (!node)
|
||||
return 0;
|
||||
|
||||
for (auto& e : node->children)
|
||||
children.Add(e.second->item);
|
||||
return node->children.size();
|
||||
}
|
||||
|
||||
/* --- Mutation API -------------------------------------------------- */
|
||||
|
||||
void Clear(const Path& path) override
|
||||
{
|
||||
auto top = Find(path);
|
||||
if (!top)
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto it = top->children.begin();
|
||||
if (it == top->children.end())
|
||||
break;
|
||||
|
||||
auto child = it->second;
|
||||
if (!child->stub)
|
||||
{
|
||||
Clear(child->dirent->path);
|
||||
_byItem.erase(child->item);
|
||||
}
|
||||
top->children.erase(it);
|
||||
ItemDeleted(top->item, child->item);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FilesystemNode> Find(const Path& path) const override
|
||||
{
|
||||
if (path.empty())
|
||||
return _root;
|
||||
|
||||
auto node = _root;
|
||||
for (const auto& element : path)
|
||||
{
|
||||
try
|
||||
{
|
||||
node = node->children.at(element);
|
||||
}
|
||||
catch (std::out_of_range& e)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<FilesystemNode> Find(
|
||||
const wxDataViewItem& item) const override
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return _root;
|
||||
|
||||
auto it = _byItem.find(item);
|
||||
if (it == _byItem.end())
|
||||
return nullptr;
|
||||
|
||||
auto node = it->second.lock();
|
||||
if (node)
|
||||
return node;
|
||||
|
||||
/* This node is stale; clean it out of the weak reference map. */
|
||||
|
||||
_byItem.erase(item);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Delete(const Path& path) override
|
||||
{
|
||||
auto parent = Find(path.parent());
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
auto child = Find(path);
|
||||
if (!child)
|
||||
return;
|
||||
|
||||
Clear(path);
|
||||
_byItem.erase(child->item);
|
||||
parent->children.erase(child->dirent->filename);
|
||||
ItemDeleted(parent->item, child->item);
|
||||
}
|
||||
|
||||
void RemoveStub(const Path& path) override
|
||||
{
|
||||
auto node = Find(path);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
/* If the only item in the directory is a stub, remove it. */
|
||||
|
||||
if ((node->children.size() == 1) &&
|
||||
node->children.begin()->second->stub)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void Add(std::shared_ptr<Dirent> dirent) override
|
||||
{
|
||||
auto parent = Find(dirent->path.parent());
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
/* Add the actual item (the easy bit). */
|
||||
|
||||
auto node = std::make_shared<FilesystemNode>(dirent);
|
||||
_byItem[node->item] = node;
|
||||
parent->children[dirent->filename] = node;
|
||||
ItemAdded(parent->item, node->item);
|
||||
|
||||
/* If this is a new directory, add the stub item to it. */
|
||||
|
||||
if (dirent->file_type == TYPE_DIRECTORY)
|
||||
{
|
||||
auto stub =
|
||||
std::make_shared<FilesystemNode>(std::make_shared<Dirent>());
|
||||
_byItem[stub->item] = stub;
|
||||
node->children[""] = stub;
|
||||
stub->stub = true;
|
||||
stub->dirent->path = dirent->path;
|
||||
stub->dirent->path.push_back("");
|
||||
ItemAdded(node->item, stub->item);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<FilesystemNode> _root;
|
||||
mutable std::map<wxDataViewItem, std::weak_ptr<FilesystemNode>> _byItem;
|
||||
wxIcon _fileIcon;
|
||||
wxIcon _folderOpenIcon;
|
||||
wxIcon _folderClosedIcon;
|
||||
};
|
||||
|
||||
FilesystemModel* FilesystemModel::Associate(wxDataViewCtrl* control)
|
||||
{
|
||||
auto model = new FilesystemModelImpl();
|
||||
control->AssociateModel(nullptr);
|
||||
control->AssociateModel(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
// vim: sw=4 ts=4 et
|
||||
38
src/gui/filesystemmodel.h
Normal file
38
src/gui/filesystemmodel.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef FILESYSTEMMODEL_H
|
||||
#define FILESYSTEMMODEL_H
|
||||
|
||||
#include <wx/dataview.h>
|
||||
class Dirent;
|
||||
class Path;
|
||||
|
||||
class FilesystemNode
|
||||
{
|
||||
public:
|
||||
FilesystemNode(std::shared_ptr<Dirent> dirent);
|
||||
|
||||
wxDataViewItem item;
|
||||
bool populated = false;
|
||||
bool populating = false;
|
||||
bool stub = false;
|
||||
std::shared_ptr<Dirent> dirent;
|
||||
std::map<std::string, std::shared_ptr<FilesystemNode>> children;
|
||||
|
||||
std::string newname; /* used for inline renames */
|
||||
};
|
||||
|
||||
class FilesystemModel : public wxDataViewModel
|
||||
{
|
||||
public:
|
||||
virtual void Clear(const Path& path) = 0;
|
||||
virtual std::shared_ptr<FilesystemNode> Find(const Path& path) const = 0;
|
||||
virtual std::shared_ptr<FilesystemNode> Find(
|
||||
const wxDataViewItem& item) const = 0;
|
||||
virtual void Delete(const Path& path) = 0;
|
||||
virtual void RemoveStub(const Path& path) = 0;
|
||||
virtual void Add(std::shared_ptr<Dirent> dirent) = 0;
|
||||
|
||||
public:
|
||||
static FilesystemModel* Associate(wxDataViewCtrl* control);
|
||||
};
|
||||
|
||||
#endif
|
||||
50
src/gui/fileviewerwindow.cc
Normal file
50
src/gui/fileviewerwindow.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "lib/globals.h"
|
||||
#include "lib/utils.h"
|
||||
#include "lib/bytes.h"
|
||||
#include "gui.h"
|
||||
#include "layout.h"
|
||||
#include "fileviewerwindow.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
FileViewerWindow::FileViewerWindow(
|
||||
wxWindow* parent, const std::string& title, const Bytes& data):
|
||||
FileViewerWindowGen(parent)
|
||||
{
|
||||
auto size = hexControl->GetTextExtent("M");
|
||||
SetSize(size.Scale(85, 25));
|
||||
SetTitle(title);
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
hexdump(ss, data);
|
||||
hexControl->SetValue(ss.str());
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
bool nl = false;
|
||||
for (uint8_t c : data)
|
||||
{
|
||||
if ((c == '\r') && nl)
|
||||
{
|
||||
nl = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c == '\n') || ((c >= 32) && (c <= 126)))
|
||||
ss << (char)c;
|
||||
else if (c == '\r')
|
||||
ss << '\n';
|
||||
else
|
||||
ss << fmt::format("\\x{:02x}", c);
|
||||
|
||||
nl = (c == '\n');
|
||||
}
|
||||
textControl->SetValue(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
void FileViewerWindow::OnClose(wxCloseEvent& event)
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
16
src/gui/fileviewerwindow.h
Normal file
16
src/gui/fileviewerwindow.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef FILEVIEWERWINDOW_H
|
||||
#define FILEVIEWERWINDOW_H
|
||||
|
||||
#include "layout.h"
|
||||
|
||||
class FileViewerWindow : public FileViewerWindowGen
|
||||
{
|
||||
public:
|
||||
FileViewerWindow(
|
||||
wxWindow* parent, const std::string& title, const Bytes& data);
|
||||
|
||||
private:
|
||||
void OnClose(wxCloseEvent& event);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
|
||||
// C++ code generated with wxFormBuilder (version 3.10.1-234-gd93c9fc0-dirty)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
@@ -186,11 +186,15 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
browseButton = new wxButton( idlePanel, wxID_ANY, wxT("Browse disk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
|
||||
browseButton->SetBitmap( wxArtProvider::GetBitmap( wxART_FOLDER_OPEN, wxART_TOOLBAR ) );
|
||||
browseButton->Enable( false );
|
||||
browseButton->SetToolTip( wxT("Access the files on the disk directly without needing to image it.") );
|
||||
|
||||
gSizer9->Add( browseButton, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
formatButton = new wxButton( idlePanel, wxID_ANY, wxT("Format disk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
|
||||
formatButton->SetBitmap( wxArtProvider::GetBitmap( wxART_DELETE, wxART_BUTTON ) );
|
||||
gSizer9->Add( formatButton, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
fgSizer8->Add( gSizer9, 1, wxEXPAND, 5 );
|
||||
|
||||
@@ -206,7 +210,7 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
wxBoxSizer* bSizer41;
|
||||
bSizer41 = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
imagerToolbar = new wxToolBar( imagePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_FLAT|wxTB_HORIZONTAL|wxTB_TEXT );
|
||||
imagerToolbar = new wxAuiToolBar( imagePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_TB_HORZ_LAYOUT|wxAUI_TB_TEXT );
|
||||
imagerBackTool = imagerToolbar->AddTool( wxID_ANY, wxT("Back"), wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
imagerToolbar->Realize();
|
||||
@@ -272,30 +276,68 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
fgSizer23->SetFlexibleDirection( wxBOTH );
|
||||
fgSizer23->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
browserToolbar = new wxToolBar( browsePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_FLAT|wxTB_HORIZONTAL|wxTB_TEXT );
|
||||
browserToolbar = new wxAuiToolBar( browsePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxAUI_TB_HORZ_LAYOUT|wxAUI_TB_TEXT );
|
||||
browserBackTool = browserToolbar->AddTool( wxID_ANY, wxT("Back"), wxArtProvider::GetBitmap( wxART_GO_BACK, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
browserToolbar->AddSeparator();
|
||||
|
||||
browserInfoTool = browserToolbar->AddTool( wxID_ANY, wxT("Info"), wxArtProvider::GetBitmap( wxART_INFORMATION, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
browserViewTool = browserToolbar->AddTool( wxID_ANY, wxT("View"), wxArtProvider::GetBitmap( wxART_FILE_OPEN, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
browserSaveTool = browserToolbar->AddTool( wxID_ANY, wxT("Save"), wxArtProvider::GetBitmap( wxART_FILE_SAVE, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
browserMoreMenuButton = browserToolbar->AddTool( wxID_ANY, wxT("More"), wxArtProvider::GetBitmap( wxART_PLUS, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
browserToolbar->SetToolDropDown( browserMoreMenuButton->GetId(), true );
|
||||
|
||||
browserMoreMenu = new wxMenu();
|
||||
browserAddMenuItem = new wxMenuItem( browserMoreMenu, wxID_ANY, wxString( wxT("Add file") ) , wxEmptyString, wxITEM_NORMAL );
|
||||
browserMoreMenu->Append( browserAddMenuItem );
|
||||
|
||||
browserNewDirectoryMenuItem = new wxMenuItem( browserMoreMenu, wxID_ANY, wxString( wxT("New directory") ) , wxEmptyString, wxITEM_NORMAL );
|
||||
#ifdef __WXMSW__
|
||||
browserNewDirectoryMenuItem->SetBitmaps( wxNullBitmap );
|
||||
#elif (defined( __WXGTK__ ) || defined( __WXOSX__ ))
|
||||
browserNewDirectoryMenuItem->SetBitmap( wxNullBitmap );
|
||||
#endif
|
||||
browserMoreMenu->Append( browserNewDirectoryMenuItem );
|
||||
|
||||
browserRenameMenuItem = new wxMenuItem( browserMoreMenu, wxID_ANY, wxString( wxT("Move file") ) , wxEmptyString, wxITEM_NORMAL );
|
||||
#ifdef __WXMSW__
|
||||
browserRenameMenuItem->SetBitmaps( wxNullBitmap );
|
||||
#elif (defined( __WXGTK__ ) || defined( __WXOSX__ ))
|
||||
browserRenameMenuItem->SetBitmap( wxNullBitmap );
|
||||
#endif
|
||||
browserMoreMenu->Append( browserRenameMenuItem );
|
||||
|
||||
browserDeleteMenuItem = new wxMenuItem( browserMoreMenu, wxID_ANY, wxString( wxT("Delete file") ) , wxEmptyString, wxITEM_NORMAL );
|
||||
#ifdef __WXMSW__
|
||||
browserDeleteMenuItem->SetBitmaps( wxNullBitmap );
|
||||
#elif (defined( __WXGTK__ ) || defined( __WXOSX__ ))
|
||||
browserDeleteMenuItem->SetBitmap( wxNullBitmap );
|
||||
#endif
|
||||
browserMoreMenu->Append( browserDeleteMenuItem );
|
||||
|
||||
browserToolbar->Connect( browserMoreMenuButton->GetId(), wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, wxAuiToolBarEventHandler( MainWindowGen::browserMoreMenuButtonOnDropDownMenu ), NULL, this );
|
||||
|
||||
|
||||
browserToolbar->AddSeparator();
|
||||
|
||||
browserFormatTool = browserToolbar->AddTool( wxID_ANY, wxT("Format"), wxArtProvider::GetBitmap( wxART_DELETE, wxART_TOOLBAR ), wxNullBitmap, wxITEM_NORMAL, wxEmptyString, wxEmptyString, NULL );
|
||||
|
||||
browserToolbar->Realize();
|
||||
|
||||
fgSizer23->Add( browserToolbar, 0, wxEXPAND, 5 );
|
||||
|
||||
m_scrolledWindow1 = new wxScrolledWindow( browsePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL );
|
||||
m_scrolledWindow1->SetScrollRate( 5, 5 );
|
||||
wxGridSizer* gSizer13;
|
||||
gSizer13 = new wxGridSizer( 1, 1, 0, 0 );
|
||||
browserTree = new wxDataViewCtrl( browsePanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_SINGLE );
|
||||
m_dataViewColumn1 = browserTree->AppendIconTextColumn( wxT("Filename"), 0, wxDATAVIEW_CELL_EDITABLE, 250, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE|wxDATAVIEW_COL_SORTABLE );
|
||||
m_dataViewColumn2 = browserTree->AppendTextColumn( wxT("Size"), 1, wxDATAVIEW_CELL_INERT, 100, static_cast<wxAlignment>(wxALIGN_RIGHT), wxDATAVIEW_COL_RESIZABLE );
|
||||
m_dataViewColumn3 = browserTree->AppendTextColumn( wxT("Mode"), 2, wxDATAVIEW_CELL_INERT, -1, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE );
|
||||
fgSizer23->Add( browserTree, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
browserView = new wxDataViewCtrl( m_scrolledWindow1, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
|
||||
browserFilenameColumn = browserView->AppendTextColumn( wxT("Filename"), 0, wxDATAVIEW_CELL_INERT, -1, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE );
|
||||
browserModeColumn = browserView->AppendTextColumn( wxT("Mode"), 1, wxDATAVIEW_CELL_INERT, -1, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE );
|
||||
browserLengthColumn = browserView->AppendTextColumn( wxT("Length"), 2, wxDATAVIEW_CELL_INERT, -1, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE );
|
||||
browserExtraColumn = browserView->AppendTextColumn( wxT("Additional properties"), 0, wxDATAVIEW_CELL_INERT, -1, static_cast<wxAlignment>(wxALIGN_LEFT), wxDATAVIEW_COL_RESIZABLE );
|
||||
gSizer13->Add( browserView, 0, wxEXPAND, 5 );
|
||||
|
||||
|
||||
m_scrolledWindow1->SetSizer( gSizer13 );
|
||||
m_scrolledWindow1->Layout();
|
||||
gSizer13->Fit( m_scrolledWindow1 );
|
||||
fgSizer23->Add( m_scrolledWindow1, 1, wxEXPAND | wxALL, 5 );
|
||||
diskSpaceGauge = new wxGauge( browsePanel, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL|wxGA_SMOOTH );
|
||||
diskSpaceGauge->SetValue( 0 );
|
||||
fgSizer23->Add( diskSpaceGauge, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
wxGridSizer* gSizer12;
|
||||
gSizer12 = new wxGridSizer( 0, 2, 0, 0 );
|
||||
@@ -313,6 +355,10 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
|
||||
fgSizer23->Add( gSizer12, 1, wxEXPAND, 5 );
|
||||
|
||||
m_staticText12 = new wxStaticText( browsePanel, wxID_ANY, wxT("No changes will be written until the 'commit' button is pressed."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
|
||||
m_staticText12->Wrap( -1 );
|
||||
fgSizer23->Add( m_staticText12, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
browsePanel->SetSizer( fgSizer23 );
|
||||
browsePanel->Layout();
|
||||
@@ -345,11 +391,29 @@ MainWindowGen::MainWindowGen( wxWindow* parent, wxWindowID id, const wxString& t
|
||||
customConfigurationButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnCustomConfigurationButton ), NULL, this );
|
||||
readButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnReadButton ), NULL, this );
|
||||
writeButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
|
||||
browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
|
||||
formatButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
|
||||
this->Connect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
imagerSaveImageButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
|
||||
imagerSaveFluxButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
|
||||
imagerGoAgainButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnImagerGoAgainButton ), NULL, this );
|
||||
this->Connect( browserBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
this->Connect( browserInfoTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserInfoButton ) );
|
||||
this->Connect( browserViewTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserViewButton ) );
|
||||
this->Connect( browserSaveTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserSaveButton ) );
|
||||
browserMoreMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnBrowserAddMenuItem ), this, browserAddMenuItem->GetId());
|
||||
browserMoreMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnBrowserNewDirectoryMenuItem ), this, browserNewDirectoryMenuItem->GetId());
|
||||
browserMoreMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnBrowserRenameMenuItem ), this, browserRenameMenuItem->GetId());
|
||||
browserMoreMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainWindowGen::OnBrowserDeleteMenuItem ), this, browserDeleteMenuItem->GetId());
|
||||
this->Connect( browserFormatTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserFormatButton ) );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG, wxDataViewEventHandler( MainWindowGen::OnBrowserBeginDrag ), NULL, this );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_DROP, wxDataViewEventHandler( MainWindowGen::OnBrowserDrop ), NULL, this );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, wxDataViewEventHandler( MainWindowGen::OnBrowserDropPossible ), NULL, this );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, wxDataViewEventHandler( MainWindowGen::OnBrowserFilenameChanged ), NULL, this );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING, wxDataViewEventHandler( MainWindowGen::OnBrowserDirectoryExpanding ), NULL, this );
|
||||
browserTree->Connect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
|
||||
browserDiscardButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
|
||||
browserCommitButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
|
||||
}
|
||||
|
||||
MainWindowGen::~MainWindowGen()
|
||||
@@ -368,12 +432,27 @@ MainWindowGen::~MainWindowGen()
|
||||
customConfigurationButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnCustomConfigurationButton ), NULL, this );
|
||||
readButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnReadButton ), NULL, this );
|
||||
writeButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnWriteButton ), NULL, this );
|
||||
browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowseButton ), NULL, this );
|
||||
formatButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnFormatButton ), NULL, this );
|
||||
this->Disconnect( imagerBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
imagerSaveImageButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveImageButton ), NULL, this );
|
||||
imagerSaveFluxButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnSaveFluxButton ), NULL, this );
|
||||
imagerGoAgainButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnImagerGoAgainButton ), NULL, this );
|
||||
this->Disconnect( browserBackTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBackButton ) );
|
||||
this->Disconnect( browserInfoTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserInfoButton ) );
|
||||
this->Disconnect( browserViewTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserViewButton ) );
|
||||
this->Disconnect( browserSaveTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserSaveButton ) );
|
||||
this->Disconnect( browserFormatTool->GetId(), wxEVT_COMMAND_TOOL_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserFormatButton ) );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG, wxDataViewEventHandler( MainWindowGen::OnBrowserBeginDrag ), NULL, this );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_DROP, wxDataViewEventHandler( MainWindowGen::OnBrowserDrop ), NULL, this );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, wxDataViewEventHandler( MainWindowGen::OnBrowserDropPossible ), NULL, this );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, wxDataViewEventHandler( MainWindowGen::OnBrowserFilenameChanged ), NULL, this );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING, wxDataViewEventHandler( MainWindowGen::OnBrowserDirectoryExpanding ), NULL, this );
|
||||
browserTree->Disconnect( wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, wxDataViewEventHandler( MainWindowGen::OnBrowserSelectionChanged ), NULL, this );
|
||||
browserDiscardButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserDiscardButton ), NULL, this );
|
||||
browserCommitButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainWindowGen::OnBrowserCommitButton ), NULL, this );
|
||||
|
||||
delete browserMoreMenu;
|
||||
}
|
||||
|
||||
TextViewerWindowGen::TextViewerWindowGen( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
@@ -507,3 +586,301 @@ TextEditorWindowGen::~TextEditorWindowGen()
|
||||
m_sdbSizer2Save->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( TextEditorWindowGen::OnSave ), NULL, this );
|
||||
|
||||
}
|
||||
|
||||
FileViewerWindowGen::FileViewerWindowGen( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxFlexGridSizer* fgSizer8;
|
||||
fgSizer8 = new wxFlexGridSizer( 0, 1, 0, 0 );
|
||||
fgSizer8->AddGrowableCol( 0 );
|
||||
fgSizer8->AddGrowableRow( 0 );
|
||||
fgSizer8->SetFlexibleDirection( wxBOTH );
|
||||
fgSizer8->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
|
||||
|
||||
m_notebook1 = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_panel8 = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
wxGridSizer* gSizer101;
|
||||
gSizer101 = new wxGridSizer( 1, 1, 0, 0 );
|
||||
|
||||
textControl = new wxTextCtrl( m_panel8, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY );
|
||||
textControl->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
|
||||
|
||||
gSizer101->Add( textControl, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
m_panel8->SetSizer( gSizer101 );
|
||||
m_panel8->Layout();
|
||||
gSizer101->Fit( m_panel8 );
|
||||
m_notebook1->AddPage( m_panel8, wxT("Text"), false );
|
||||
m_panel7 = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
wxGridSizer* gSizer10;
|
||||
gSizer10 = new wxGridSizer( 1, 1, 0, 0 );
|
||||
|
||||
hexControl = new wxTextCtrl( m_panel7, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY );
|
||||
hexControl->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
|
||||
|
||||
gSizer10->Add( hexControl, 0, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
m_panel7->SetSizer( gSizer10 );
|
||||
m_panel7->Layout();
|
||||
gSizer10->Fit( m_panel7 );
|
||||
m_notebook1->AddPage( m_panel7, wxT("Hex"), false );
|
||||
|
||||
fgSizer8->Add( m_notebook1, 1, wxEXPAND | wxALL, 5 );
|
||||
|
||||
m_sdbSizer2 = new wxStdDialogButtonSizer();
|
||||
m_sdbSizer2OK = new wxButton( this, wxID_OK );
|
||||
m_sdbSizer2->AddButton( m_sdbSizer2OK );
|
||||
m_sdbSizer2->Realize();
|
||||
|
||||
fgSizer8->Add( m_sdbSizer2, 1, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( fgSizer8 );
|
||||
this->Layout();
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
|
||||
// Connect Events
|
||||
m_sdbSizer2OK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FileViewerWindowGen::OnClose ), NULL, this );
|
||||
}
|
||||
|
||||
FileViewerWindowGen::~FileViewerWindowGen()
|
||||
{
|
||||
// Disconnect Events
|
||||
m_sdbSizer2OK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FileViewerWindowGen::OnClose ), NULL, this );
|
||||
|
||||
}
|
||||
|
||||
GetfileDialog::GetfileDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxGridBagSizer* gbSizer1;
|
||||
gbSizer1 = new wxGridBagSizer( 0, 0 );
|
||||
gbSizer1->SetFlexibleDirection( wxBOTH );
|
||||
gbSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText7 = new wxStaticText( this, wxID_ANY, wxT("File on disk:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText7->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText7, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
filenameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), wxTE_READONLY );
|
||||
filenameText->Enable( false );
|
||||
|
||||
gbSizer1->Add( filenameText, wxGBPosition( 0, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
m_staticText9 = new wxStaticText( this, wxID_ANY, wxT("File to save as:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText9->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText9, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
targetFilePicker = new wxFilePickerCtrl( this, wxID_ANY, wxEmptyString, wxT("Select a file"), wxT("*.*"), wxDefaultPosition, wxDefaultSize, wxFLP_OVERWRITE_PROMPT|wxFLP_SAVE|wxFLP_USE_TEXTCTRL );
|
||||
gbSizer1->Add( targetFilePicker, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
buttons_ = new wxStdDialogButtonSizer();
|
||||
buttons_OK = new wxButton( this, wxID_OK );
|
||||
buttons_->AddButton( buttons_OK );
|
||||
buttons_Cancel = new wxButton( this, wxID_CANCEL );
|
||||
buttons_->AddButton( buttons_Cancel );
|
||||
buttons_->Realize();
|
||||
|
||||
gbSizer1->Add( buttons_, wxGBPosition( 2, 0 ), wxGBSpan( 1, 2 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( gbSizer1 );
|
||||
this->Layout();
|
||||
gbSizer1->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
}
|
||||
|
||||
GetfileDialog::~GetfileDialog()
|
||||
{
|
||||
}
|
||||
|
||||
FileConflictDialog::FileConflictDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxGridBagSizer* gbSizer1;
|
||||
gbSizer1 = new wxGridBagSizer( 0, 0 );
|
||||
gbSizer1->SetFlexibleDirection( wxBOTH );
|
||||
gbSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText91 = new wxStaticText( this, wxID_ANY, wxT("That name is already in use.\nPlease specify another:"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
|
||||
m_staticText91->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText91, wxGBPosition( 0, 0 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
|
||||
|
||||
m_staticText7 = new wxStaticText( this, wxID_ANY, wxT("Old name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText7->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText7, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
newNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), 0 );
|
||||
gbSizer1->Add( newNameText, wxGBPosition( 2, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
m_staticText9 = new wxStaticText( this, wxID_ANY, wxT("New name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText9->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText9, wxGBPosition( 2, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
oldNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY );
|
||||
oldNameText->Enable( false );
|
||||
|
||||
gbSizer1->Add( oldNameText, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
buttons_ = new wxStdDialogButtonSizer();
|
||||
buttons_OK = new wxButton( this, wxID_OK );
|
||||
buttons_->AddButton( buttons_OK );
|
||||
buttons_Cancel = new wxButton( this, wxID_CANCEL );
|
||||
buttons_->AddButton( buttons_Cancel );
|
||||
buttons_->Realize();
|
||||
|
||||
gbSizer1->Add( buttons_, wxGBPosition( 3, 0 ), wxGBSpan( 1, 2 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( gbSizer1 );
|
||||
this->Layout();
|
||||
gbSizer1->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
}
|
||||
|
||||
FileConflictDialog::~FileConflictDialog()
|
||||
{
|
||||
}
|
||||
|
||||
FileRenameDialog::FileRenameDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxGridBagSizer* gbSizer1;
|
||||
gbSizer1 = new wxGridBagSizer( 0, 0 );
|
||||
gbSizer1->SetFlexibleDirection( wxBOTH );
|
||||
gbSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText91 = new wxStaticText( this, wxID_ANY, wxT("Please specify the new name\n(and path) of the file."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
|
||||
m_staticText91->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText91, wxGBPosition( 0, 0 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
|
||||
|
||||
m_staticText7 = new wxStaticText( this, wxID_ANY, wxT("Old name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText7->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText7, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
newNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), 0 );
|
||||
gbSizer1->Add( newNameText, wxGBPosition( 2, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
m_staticText9 = new wxStaticText( this, wxID_ANY, wxT("New name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText9->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText9, wxGBPosition( 2, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
oldNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY );
|
||||
oldNameText->Enable( false );
|
||||
|
||||
gbSizer1->Add( oldNameText, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
buttons_ = new wxStdDialogButtonSizer();
|
||||
buttons_OK = new wxButton( this, wxID_OK );
|
||||
buttons_->AddButton( buttons_OK );
|
||||
buttons_Cancel = new wxButton( this, wxID_CANCEL );
|
||||
buttons_->AddButton( buttons_Cancel );
|
||||
buttons_->Realize();
|
||||
|
||||
gbSizer1->Add( buttons_, wxGBPosition( 3, 0 ), wxGBSpan( 1, 2 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( gbSizer1 );
|
||||
this->Layout();
|
||||
gbSizer1->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
}
|
||||
|
||||
FileRenameDialog::~FileRenameDialog()
|
||||
{
|
||||
}
|
||||
|
||||
CreateDirectoryDialog::CreateDirectoryDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxGridBagSizer* gbSizer1;
|
||||
gbSizer1 = new wxGridBagSizer( 0, 0 );
|
||||
gbSizer1->SetFlexibleDirection( wxBOTH );
|
||||
gbSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText91 = new wxStaticText( this, wxID_ANY, wxT("Please specify the name (and path)\nof the new directory."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
|
||||
m_staticText91->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText91, wxGBPosition( 0, 0 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
|
||||
|
||||
newNameText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 300,-1 ), 0 );
|
||||
gbSizer1->Add( newNameText, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
m_staticText9 = new wxStaticText( this, wxID_ANY, wxT("Name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText9->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText9, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
buttons_ = new wxStdDialogButtonSizer();
|
||||
buttons_OK = new wxButton( this, wxID_OK );
|
||||
buttons_->AddButton( buttons_OK );
|
||||
buttons_Cancel = new wxButton( this, wxID_CANCEL );
|
||||
buttons_->AddButton( buttons_Cancel );
|
||||
buttons_->Realize();
|
||||
|
||||
gbSizer1->Add( buttons_, wxGBPosition( 2, 0 ), wxGBSpan( 1, 2 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( gbSizer1 );
|
||||
this->Layout();
|
||||
gbSizer1->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
}
|
||||
|
||||
CreateDirectoryDialog::~CreateDirectoryDialog()
|
||||
{
|
||||
}
|
||||
|
||||
FormatDialog::FormatDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
|
||||
{
|
||||
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
|
||||
|
||||
wxGridBagSizer* gbSizer1;
|
||||
gbSizer1 = new wxGridBagSizer( 0, 0 );
|
||||
gbSizer1->SetFlexibleDirection( wxBOTH );
|
||||
gbSizer1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_staticText91 = new wxStaticText( this, wxID_ANY, wxT("This will erase the entire disk. (But remember that\nyou can always undo changes until you press the 'commit changes' button.)"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
|
||||
m_staticText91->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText91, wxGBPosition( 0, 0 ), wxGBSpan( 1, 2 ), wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
|
||||
|
||||
m_staticText7 = new wxStaticText( this, wxID_ANY, wxT("Volume name:"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_staticText7->Wrap( -1 );
|
||||
gbSizer1->Add( m_staticText7, wxGBPosition( 1, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxALL, 5 );
|
||||
|
||||
volumeNameText = new wxTextCtrl( this, wxID_ANY, wxT("FluxEngineDisk"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
gbSizer1->Add( volumeNameText, wxGBPosition( 1, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
quickFormatCheckBox = new wxCheckBox( this, wxID_ANY, wxT("Quick format: if the disk is not already correctly formatted,\nvery bad things will happen!"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
gbSizer1->Add( quickFormatCheckBox, wxGBPosition( 2, 1 ), wxGBSpan( 1, 1 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
buttons_ = new wxStdDialogButtonSizer();
|
||||
buttons_OK = new wxButton( this, wxID_OK );
|
||||
buttons_->AddButton( buttons_OK );
|
||||
buttons_Cancel = new wxButton( this, wxID_CANCEL );
|
||||
buttons_->AddButton( buttons_Cancel );
|
||||
buttons_->Realize();
|
||||
|
||||
gbSizer1->Add( buttons_, wxGBPosition( 3, 0 ), wxGBSpan( 1, 2 ), wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
||||
this->SetSizer( gbSizer1 );
|
||||
this->Layout();
|
||||
gbSizer1->Fit( this );
|
||||
|
||||
this->Centre( wxBOTH );
|
||||
}
|
||||
|
||||
FormatDialog::~FormatDialog()
|
||||
{
|
||||
}
|
||||
|
||||
2602
src/gui/layout.fbp
2602
src/gui/layout.fbp
File diff suppressed because it is too large
Load Diff
227
src/gui/layout.h
227
src/gui/layout.h
@@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
|
||||
// C++ code generated with wxFormBuilder (version 3.10.1-234-gd93c9fc0-dirty)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
@@ -28,15 +28,19 @@
|
||||
#include <wx/filepicker.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/toolbar.h>
|
||||
#include <wx/aui/aui.h>
|
||||
#include <wx/aui/auibar.h>
|
||||
#include "visualisationcontrol.h"
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/frame.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/dialog.h>
|
||||
#include "fluxviewercontrol.h"
|
||||
#include <wx/scrolbar.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/gbsizer.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -73,25 +77,36 @@ class MainWindowGen : public wxFrame
|
||||
wxButton* readButton;
|
||||
wxButton* writeButton;
|
||||
wxButton* browseButton;
|
||||
wxButton* formatButton;
|
||||
wxPanel* imagePanel;
|
||||
wxToolBar* imagerToolbar;
|
||||
wxToolBarToolBase* imagerBackTool;
|
||||
wxAuiToolBar* imagerToolbar;
|
||||
wxAuiToolBarItem* imagerBackTool;
|
||||
VisualisationControl* visualiser;
|
||||
wxButton* imagerSaveImageButton;
|
||||
wxButton* imagerSaveFluxButton;
|
||||
wxStaticText* m_staticText4;
|
||||
wxButton* imagerGoAgainButton;
|
||||
wxPanel* browsePanel;
|
||||
wxToolBar* browserToolbar;
|
||||
wxToolBarToolBase* browserBackTool;
|
||||
wxScrolledWindow* m_scrolledWindow1;
|
||||
wxDataViewCtrl* browserView;
|
||||
wxDataViewColumn* browserFilenameColumn;
|
||||
wxDataViewColumn* browserModeColumn;
|
||||
wxDataViewColumn* browserLengthColumn;
|
||||
wxDataViewColumn* browserExtraColumn;
|
||||
wxAuiToolBar* browserToolbar;
|
||||
wxAuiToolBarItem* browserBackTool;
|
||||
wxAuiToolBarItem* browserInfoTool;
|
||||
wxAuiToolBarItem* browserViewTool;
|
||||
wxAuiToolBarItem* browserSaveTool;
|
||||
wxAuiToolBarItem* browserMoreMenuButton;
|
||||
wxMenu* browserMoreMenu;
|
||||
wxMenuItem* browserAddMenuItem;
|
||||
wxMenuItem* browserNewDirectoryMenuItem;
|
||||
wxMenuItem* browserRenameMenuItem;
|
||||
wxMenuItem* browserDeleteMenuItem;
|
||||
wxAuiToolBarItem* browserFormatTool;
|
||||
wxDataViewCtrl* browserTree;
|
||||
wxDataViewColumn* m_dataViewColumn1;
|
||||
wxDataViewColumn* m_dataViewColumn2;
|
||||
wxDataViewColumn* m_dataViewColumn3;
|
||||
wxGauge* diskSpaceGauge;
|
||||
wxButton* browserDiscardButton;
|
||||
wxButton* browserCommitButton;
|
||||
wxStaticText* m_staticText12;
|
||||
|
||||
// Virtual event handlers, override them in your derived class
|
||||
virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
|
||||
@@ -105,18 +120,49 @@ class MainWindowGen : public wxFrame
|
||||
virtual void OnCustomConfigurationButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnReadButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnWriteButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowseButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnFormatButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBackButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnSaveImageButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnSaveFluxButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnImagerGoAgainButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserInfoButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserViewButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserSaveButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserAddMenuItem( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserNewDirectoryMenuItem( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserRenameMenuItem( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserDeleteMenuItem( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserFormatButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserBeginDrag( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserDrop( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserDropPossible( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserFilenameChanged( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserDirectoryExpanding( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserSelectionChanged( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserDiscardButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnBrowserCommitButton( wxCommandEvent& event ) { event.Skip(); }
|
||||
|
||||
|
||||
public:
|
||||
|
||||
MainWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("FluxEngine"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 819,607 ), long style = wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER|wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL );
|
||||
MainWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("FluxEngine"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 616,607 ), long style = wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER|wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL );
|
||||
|
||||
~MainWindowGen();
|
||||
|
||||
void browserMoreMenuButtonOnDropDownMenu( wxAuiToolBarEvent &event )
|
||||
{
|
||||
if ( event.IsDropDownClicked() )
|
||||
{
|
||||
browserToolbar->SetToolSticky( event.GetId(), true );
|
||||
wxRect rect = browserToolbar->GetToolRect( event.GetId() );
|
||||
wxPoint pt = browserToolbar->ClientToScreen( rect.GetBottomLeft() );
|
||||
pt = ScreenToClient( pt );
|
||||
browserToolbar->PopupMenu( browserMoreMenu, pt );
|
||||
browserToolbar->SetToolSticky( event.GetId(), false );
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -138,7 +184,7 @@ class TextViewerWindowGen : public wxDialog
|
||||
|
||||
public:
|
||||
|
||||
TextViewerWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 208,143 ), long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
|
||||
TextViewerWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 208,143 ), long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
|
||||
|
||||
~TextViewerWindowGen();
|
||||
|
||||
@@ -164,7 +210,7 @@ class FluxViewerWindowGen : public wxDialog
|
||||
|
||||
public:
|
||||
|
||||
FluxViewerWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,200 ), long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
|
||||
FluxViewerWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 400,200 ), long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
|
||||
|
||||
~FluxViewerWindowGen();
|
||||
|
||||
@@ -191,9 +237,158 @@ class TextEditorWindowGen : public wxDialog
|
||||
|
||||
public:
|
||||
|
||||
TextEditorWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
|
||||
TextEditorWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxCLOSE_BOX|wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
|
||||
|
||||
~TextEditorWindowGen();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class FileViewerWindowGen
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class FileViewerWindowGen : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxNotebook* m_notebook1;
|
||||
wxPanel* m_panel8;
|
||||
wxTextCtrl* textControl;
|
||||
wxPanel* m_panel7;
|
||||
wxTextCtrl* hexControl;
|
||||
wxStdDialogButtonSizer* m_sdbSizer2;
|
||||
wxButton* m_sdbSizer2OK;
|
||||
|
||||
// Virtual event handlers, override them in your derived class
|
||||
virtual void OnClose( wxCommandEvent& event ) { event.Skip(); }
|
||||
|
||||
|
||||
public:
|
||||
|
||||
FileViewerWindowGen( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 408,269 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER );
|
||||
|
||||
~FileViewerWindowGen();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class GetfileDialog
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class GetfileDialog : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxStaticText* m_staticText7;
|
||||
wxStaticText* m_staticText9;
|
||||
|
||||
public:
|
||||
wxTextCtrl* filenameText;
|
||||
wxFilePickerCtrl* targetFilePicker;
|
||||
wxStdDialogButtonSizer* buttons_;
|
||||
wxButton* buttons_OK;
|
||||
wxButton* buttons_Cancel;
|
||||
|
||||
GetfileDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Copy file off disk"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
|
||||
|
||||
~GetfileDialog();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class FileConflictDialog
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class FileConflictDialog : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxStaticText* m_staticText91;
|
||||
wxStaticText* m_staticText7;
|
||||
wxStaticText* m_staticText9;
|
||||
|
||||
public:
|
||||
wxTextCtrl* newNameText;
|
||||
wxTextCtrl* oldNameText;
|
||||
wxStdDialogButtonSizer* buttons_;
|
||||
wxButton* buttons_OK;
|
||||
wxButton* buttons_Cancel;
|
||||
|
||||
FileConflictDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Filename conflict"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
|
||||
|
||||
~FileConflictDialog();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class FileRenameDialog
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class FileRenameDialog : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxStaticText* m_staticText91;
|
||||
wxStaticText* m_staticText7;
|
||||
wxStaticText* m_staticText9;
|
||||
|
||||
public:
|
||||
wxTextCtrl* newNameText;
|
||||
wxTextCtrl* oldNameText;
|
||||
wxStdDialogButtonSizer* buttons_;
|
||||
wxButton* buttons_OK;
|
||||
wxButton* buttons_Cancel;
|
||||
|
||||
FileRenameDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Rename or move file"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
|
||||
|
||||
~FileRenameDialog();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class CreateDirectoryDialog
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class CreateDirectoryDialog : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxStaticText* m_staticText91;
|
||||
wxStaticText* m_staticText9;
|
||||
|
||||
public:
|
||||
wxTextCtrl* newNameText;
|
||||
wxStdDialogButtonSizer* buttons_;
|
||||
wxButton* buttons_OK;
|
||||
wxButton* buttons_Cancel;
|
||||
|
||||
CreateDirectoryDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Create new directory"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
|
||||
|
||||
~CreateDirectoryDialog();
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class FormatDialog
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class FormatDialog : public wxDialog
|
||||
{
|
||||
private:
|
||||
|
||||
protected:
|
||||
wxStaticText* m_staticText91;
|
||||
wxStaticText* m_staticText7;
|
||||
|
||||
public:
|
||||
wxTextCtrl* volumeNameText;
|
||||
wxCheckBox* quickFormatCheckBox;
|
||||
wxStdDialogButtonSizer* buttons_;
|
||||
wxButton* buttons_OK;
|
||||
wxButton* buttons_Cancel;
|
||||
|
||||
FormatDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Format disk"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
|
||||
|
||||
~FormatDialog();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -15,11 +15,15 @@
|
||||
#include "utils.h"
|
||||
#include "fluxviewerwindow.h"
|
||||
#include "textviewerwindow.h"
|
||||
#include "fileviewerwindow.h"
|
||||
#include "texteditorwindow.h"
|
||||
#include "filesystemmodel.h"
|
||||
#include "customstatusbar.h"
|
||||
#include "lib/vfs/vfs.h"
|
||||
#include <google/protobuf/text_format.h>
|
||||
#include <wx/config.h>
|
||||
#include <wx/aboutdlg.h>
|
||||
#include <deque>
|
||||
|
||||
extern const std::map<std::string, std::string> formats;
|
||||
|
||||
@@ -38,10 +42,25 @@ const std::string DEFAULT_EXTRA_CONFIGURATION =
|
||||
"# or the name of a built-in configuration, or the filename\n"
|
||||
"# of a text proto file. Or a comment, of course.\n\n";
|
||||
|
||||
const std::string DND_TYPE = "fluxengine.files";
|
||||
|
||||
class CancelException
|
||||
{
|
||||
};
|
||||
|
||||
class MainWindow : public MainWindowGen
|
||||
{
|
||||
private:
|
||||
class FilesystemOperation;
|
||||
|
||||
public:
|
||||
MainWindow(): MainWindowGen(nullptr), _config("FluxEngine")
|
||||
MainWindow():
|
||||
MainWindowGen(nullptr),
|
||||
/* This is wrong. Apparently the wxDataViewCtrl doesn't work properly
|
||||
* with DnD unless the format is wxDF_UNICODETEXT. It should be a custom
|
||||
* value. */
|
||||
_dndFormat(wxDF_UNICODETEXT),
|
||||
_config("FluxEngine")
|
||||
{
|
||||
Logger::setLogger(
|
||||
[&](std::shared_ptr<const AnyLogMessage> message)
|
||||
@@ -96,6 +115,26 @@ public:
|
||||
emergencyStop = true;
|
||||
});
|
||||
|
||||
_filesystemModel = FilesystemModel::Associate(browserTree);
|
||||
|
||||
/* This is a bug workaround for an issue in wxformbuilder's generated
|
||||
* code; see https://github.com/wxFormBuilder/wxFormBuilder/pull/758.
|
||||
* The default handler for the submenu doesn't allow events to fire on
|
||||
* the button itself, so we have to override it with our own version. */
|
||||
|
||||
browserToolbar->Connect(browserMoreMenuButton->GetId(),
|
||||
wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN,
|
||||
wxAuiToolBarEventHandler(MainWindow::OnBrowserMoreMenuButton),
|
||||
NULL,
|
||||
this);
|
||||
|
||||
/* This is a bug workaround for an issue where the calculation of the
|
||||
* item being dropped on is wrong due to the header not being taken into
|
||||
* account. See https://forums.wxwidgets.org/viewtopic.php?t=44752. */
|
||||
|
||||
browserTree->EnableDragSource(_dndFormat);
|
||||
browserTree->EnableDropTarget(_dndFormat);
|
||||
|
||||
/* I have no idea why this is necessary, but on Windows things aren't
|
||||
* laid out correctly without it. */
|
||||
|
||||
@@ -193,8 +232,8 @@ public:
|
||||
|
||||
void OnControlsChanged(wxFileDirPickerEvent& event)
|
||||
{
|
||||
SaveConfig();
|
||||
UpdateState();
|
||||
wxCommandEvent e;
|
||||
OnControlsChanged(e);
|
||||
}
|
||||
|
||||
void OnReadButton(wxCommandEvent&)
|
||||
@@ -257,6 +296,8 @@ public:
|
||||
|
||||
ImageReader::updateConfigForFilename(
|
||||
config.mutable_image_reader(), filename.ToStdString());
|
||||
ImageWriter::updateConfigForFilename(
|
||||
config.mutable_image_writer(), filename.ToStdString());
|
||||
visualiser->Clear();
|
||||
_currentDisk = nullptr;
|
||||
|
||||
@@ -394,6 +435,605 @@ public:
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
/* --- Browser ---------------------------------------------------------- */
|
||||
|
||||
void OnBrowseButton(wxCommandEvent& event) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PrepareConfig();
|
||||
|
||||
visualiser->Clear();
|
||||
_filesystemModel->Clear(Path());
|
||||
_currentDisk = nullptr;
|
||||
|
||||
_state = STATE_BROWSING_WORKING;
|
||||
UpdateState();
|
||||
ShowConfig();
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this]()
|
||||
{
|
||||
_filesystem = Filesystem::createFilesystemFromConfig();
|
||||
_filesystemCapabilities = _filesystem->capabilities();
|
||||
_filesystemIsReadOnly = _filesystem->isReadOnly();
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
RepopulateBrowser();
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (const ErrorException& e)
|
||||
{
|
||||
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void OnFormatButton(wxCommandEvent& event) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PrepareConfig();
|
||||
|
||||
visualiser->Clear();
|
||||
_filesystemModel->Clear(Path());
|
||||
_currentDisk = nullptr;
|
||||
|
||||
_state = STATE_BROWSING_WORKING;
|
||||
UpdateState();
|
||||
ShowConfig();
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this]()
|
||||
{
|
||||
_filesystem = Filesystem::createFilesystemFromConfig();
|
||||
_filesystemCapabilities = _filesystem->capabilities();
|
||||
_filesystemIsReadOnly = _filesystem->isReadOnly();
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
wxCommandEvent e;
|
||||
OnBrowserFormatButton(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (const ErrorException& e)
|
||||
{
|
||||
wxMessageBox(e.message, "Error", wxOK | wxICON_ERROR);
|
||||
_state = STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void OnBrowserMoreMenuButton(wxAuiToolBarEvent& event)
|
||||
{
|
||||
browserToolbar->SetToolSticky(event.GetId(), true);
|
||||
wxRect rect = browserToolbar->GetToolRect(event.GetId());
|
||||
wxPoint pt = browserToolbar->ClientToScreen(rect.GetBottomLeft());
|
||||
pt = ScreenToClient(pt);
|
||||
browserToolbar->PopupMenu(browserMoreMenu, pt);
|
||||
browserToolbar->SetToolSticky(event.GetId(), false);
|
||||
}
|
||||
|
||||
void RepopulateBrowser(Path path = Path())
|
||||
{
|
||||
QueueBrowserOperation(
|
||||
[this, path]()
|
||||
{
|
||||
auto files = _filesystem->list(path);
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Clear(path);
|
||||
for (auto& f : files)
|
||||
_filesystemModel->Add(f);
|
||||
|
||||
auto node = _filesystemModel->Find(path);
|
||||
if (node)
|
||||
browserTree->Expand(node->item);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateFilesystemData()
|
||||
{
|
||||
QueueBrowserOperation(
|
||||
[this]()
|
||||
{
|
||||
auto metadata = _filesystem->getMetadata();
|
||||
_filesystemNeedsFlushing = _filesystem->needsFlushing();
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
try
|
||||
{
|
||||
uint32_t blockSize =
|
||||
std::stoul(metadata.at(Filesystem::BLOCK_SIZE));
|
||||
uint32_t totalBlocks = std::stoul(
|
||||
metadata.at(Filesystem::TOTAL_BLOCKS));
|
||||
uint32_t usedBlocks = std::stoul(
|
||||
metadata.at(Filesystem::USED_BLOCKS));
|
||||
|
||||
diskSpaceGauge->Enable();
|
||||
diskSpaceGauge->SetRange(totalBlocks * blockSize);
|
||||
diskSpaceGauge->SetValue(usedBlocks * blockSize);
|
||||
}
|
||||
catch (const std::out_of_range& e)
|
||||
{
|
||||
diskSpaceGauge->Disable();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserDirectoryExpanding(wxDataViewEvent& event) override
|
||||
{
|
||||
auto node = _filesystemModel->Find(event.GetItem());
|
||||
if (node && !node->populated && !node->populating)
|
||||
{
|
||||
node->populating = true;
|
||||
RepopulateBrowser(node->dirent->path);
|
||||
}
|
||||
}
|
||||
|
||||
void OnBrowserInfoButton(wxCommandEvent&) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = _filesystemModel->Find(item);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "File attributes for " << node->dirent->path.to_str() << ":\n\n";
|
||||
for (const auto& e : node->dirent->attributes)
|
||||
ss << e.first << "=" << quote(e.second) << "\n";
|
||||
|
||||
TextViewerWindow::Create(
|
||||
this, node->dirent->path.to_str(), ss.str(), true)
|
||||
->Show();
|
||||
}
|
||||
|
||||
void OnBrowserViewButton(wxCommandEvent&) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = _filesystemModel->Find(item);
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this, node]()
|
||||
{
|
||||
auto bytes = _filesystem->getFile(node->dirent->path);
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
(new FileViewerWindow(
|
||||
this, node->dirent->path.to_str(), bytes))
|
||||
->Show();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserSaveButton(wxCommandEvent&) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = _filesystemModel->Find(item);
|
||||
|
||||
GetfileDialog d(this, wxID_ANY);
|
||||
d.filenameText->SetValue(node->dirent->path.to_str());
|
||||
d.targetFilePicker->SetFileName(wxFileName(node->dirent->filename));
|
||||
d.targetFilePicker->SetFocus();
|
||||
d.buttons_OK->SetDefault();
|
||||
if (d.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
auto localPath = d.targetFilePicker->GetPath().ToStdString();
|
||||
QueueBrowserOperation(
|
||||
[this, node, localPath]()
|
||||
{
|
||||
auto bytes = _filesystem->getFile(node->dirent->path);
|
||||
bytes.writeToFile(localPath);
|
||||
});
|
||||
}
|
||||
|
||||
/* Called from worker thread only! */
|
||||
Path ResolveFileConflicts_WT(Path path)
|
||||
{
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
_filesystem->getDirent(path);
|
||||
}
|
||||
catch (const FileNotFoundException& e)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
FileConflictDialog d(this, wxID_ANY);
|
||||
d.oldNameText->SetValue(path.to_str());
|
||||
d.newNameText->SetValue(path.to_str());
|
||||
d.newNameText->SetFocus();
|
||||
d.buttons_OK->SetDefault();
|
||||
if (d.ShowModal() == wxID_OK)
|
||||
path = Path(d.newNameText->GetValue().ToStdString());
|
||||
else
|
||||
path = Path("");
|
||||
});
|
||||
} while (!path.empty());
|
||||
return path;
|
||||
}
|
||||
|
||||
std::shared_ptr<FilesystemNode> GetTargetDirectoryNode(wxDataViewItem& item)
|
||||
{
|
||||
Path path;
|
||||
if (item.IsOk())
|
||||
{
|
||||
auto node = _filesystemModel->Find(item);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
path = node->dirent->path;
|
||||
}
|
||||
|
||||
auto node = _filesystemModel->Find(path);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
if (node->dirent->file_type != TYPE_DIRECTORY)
|
||||
return _filesystemModel->Find(path.parent());
|
||||
return node;
|
||||
}
|
||||
|
||||
void OnBrowserAddMenuItem(wxCommandEvent&) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto dirNode = GetTargetDirectoryNode(item);
|
||||
if (!dirNode)
|
||||
return;
|
||||
|
||||
auto localPath = wxFileSelector("Choose the name of the file to add",
|
||||
/* default_path= */ wxEmptyString,
|
||||
/* default_filename= */ wxEmptyString,
|
||||
/* default_extension= */ wxEmptyString,
|
||||
/* wildcard= */ wxEmptyString,
|
||||
/* flags= */ wxFD_OPEN | wxFD_FILE_MUST_EXIST)
|
||||
.ToStdString();
|
||||
if (localPath.empty())
|
||||
return;
|
||||
auto path = dirNode->dirent->path.concat(
|
||||
wxFileName(localPath).GetFullName().ToStdString());
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this, path, localPath]() mutable
|
||||
{
|
||||
path = ResolveFileConflicts_WT(path);
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
auto bytes = Bytes::readFromFile(localPath);
|
||||
_filesystem->putFile(path, bytes);
|
||||
|
||||
auto dirent = _filesystem->getDirent(path);
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Add(dirent);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserDeleteMenuItem(wxCommandEvent&) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = _filesystemModel->Find(item);
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this, node]()
|
||||
{
|
||||
_filesystem->deleteFile(node->dirent->path);
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Delete(node->dirent->path);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserFormatButton(wxCommandEvent&) override
|
||||
{
|
||||
FormatDialog d(this, wxID_ANY);
|
||||
d.volumeNameText->SetFocus();
|
||||
d.buttons_OK->SetDefault();
|
||||
if (d.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
auto volumeName = d.volumeNameText->GetValue().ToStdString();
|
||||
auto quickFormat = d.quickFormatCheckBox->GetValue();
|
||||
QueueBrowserOperation(
|
||||
[this, volumeName, quickFormat]()
|
||||
{
|
||||
_filesystem->discardChanges();
|
||||
_filesystem->create(quickFormat, volumeName);
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
RepopulateBrowser();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserFilenameChanged(wxDataViewEvent& event)
|
||||
{
|
||||
if (!(_filesystem->capabilities() & Filesystem::OP_MOVE))
|
||||
return;
|
||||
|
||||
auto node = _filesystemModel->Find(event.GetItem());
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
if (node->newname.empty())
|
||||
return;
|
||||
if (node->newname == node->dirent->filename)
|
||||
return;
|
||||
|
||||
QueueBrowserOperation(
|
||||
[this, node]() mutable
|
||||
{
|
||||
auto oldPath = node->dirent->path;
|
||||
auto newPath = oldPath.parent().concat(node->newname);
|
||||
|
||||
newPath = ResolveFileConflicts_WT(newPath);
|
||||
if (newPath.empty())
|
||||
return;
|
||||
|
||||
_filesystem->moveFile(oldPath, newPath);
|
||||
|
||||
auto dirent = _filesystem->getDirent(newPath);
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Delete(oldPath);
|
||||
_filesystemModel->Add(dirent);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserRenameMenuItem(wxCommandEvent& event)
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = _filesystemModel->Find(item);
|
||||
|
||||
FileRenameDialog d(this, wxID_ANY);
|
||||
d.oldNameText->SetValue(node->dirent->path.to_str());
|
||||
d.newNameText->SetValue(node->dirent->path.to_str());
|
||||
d.newNameText->SetFocus();
|
||||
d.buttons_OK->SetDefault();
|
||||
if (d.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
ActuallyMoveFile(
|
||||
node->dirent->path, Path(d.newNameText->GetValue().ToStdString()));
|
||||
}
|
||||
|
||||
void ActuallyMoveFile(const Path& oldPath, Path newPath)
|
||||
{
|
||||
QueueBrowserOperation(
|
||||
[this, oldPath, newPath]() mutable
|
||||
{
|
||||
newPath = ResolveFileConflicts_WT(newPath);
|
||||
if (newPath.empty())
|
||||
return;
|
||||
|
||||
_filesystem->moveFile(oldPath, newPath);
|
||||
|
||||
auto dirent = _filesystem->getDirent(newPath);
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Delete(oldPath);
|
||||
_filesystemModel->Add(dirent);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserNewDirectoryMenuItem(wxCommandEvent& event)
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
auto node = GetTargetDirectoryNode(item);
|
||||
if (!node)
|
||||
return;
|
||||
auto path = node->dirent->path;
|
||||
|
||||
CreateDirectoryDialog d(this, wxID_ANY);
|
||||
d.newNameText->SetValue(path.to_str() + "/");
|
||||
d.newNameText->SetFocus();
|
||||
d.buttons_OK->SetDefault();
|
||||
if (d.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
auto newPath = Path(d.newNameText->GetValue().ToStdString());
|
||||
QueueBrowserOperation(
|
||||
[this, newPath]() mutable
|
||||
{
|
||||
newPath = ResolveFileConflicts_WT(newPath);
|
||||
_filesystem->createDirectory(newPath);
|
||||
|
||||
auto dirent = _filesystem->getDirent(newPath);
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_filesystemModel->Add(dirent);
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserBeginDrag(wxDataViewEvent& event) override
|
||||
{
|
||||
auto item = browserTree->GetSelection();
|
||||
if (!item.IsOk())
|
||||
{
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
|
||||
auto node = _filesystemModel->Find(item);
|
||||
if (!node)
|
||||
{
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
|
||||
wxTextDataObject* obj = new wxTextDataObject();
|
||||
obj->SetText(node->dirent->path.to_str());
|
||||
event.SetDataObject(obj);
|
||||
event.SetDataFormat(_dndFormat);
|
||||
}
|
||||
|
||||
void OnBrowserDropPossible(wxDataViewEvent& event) override
|
||||
{
|
||||
if (event.GetDataFormat() != _dndFormat)
|
||||
{
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OnBrowserDrop(wxDataViewEvent& event) override
|
||||
{
|
||||
try
|
||||
{
|
||||
if (event.GetDataFormat() != _dndFormat)
|
||||
throw CancelException();
|
||||
|
||||
/* wxWidgets 3.0 data view DnD is borked. See
|
||||
* https://forums.wxwidgets.org/viewtopic.php?t=44752. The hit
|
||||
* detection is done against the wrong object, resulting in the
|
||||
* header size not being taken into account, so we have to manually
|
||||
* do hit detection correctly. */
|
||||
|
||||
auto* window = browserTree->GetMainWindow();
|
||||
auto screenPos = wxGetMousePosition();
|
||||
auto relPos = screenPos - window->GetScreenPosition();
|
||||
|
||||
wxDataViewItem item;
|
||||
wxDataViewColumn* column;
|
||||
browserTree->HitTest(relPos, item, column);
|
||||
if (!item.IsOk())
|
||||
throw CancelException();
|
||||
|
||||
auto destDirNode = GetTargetDirectoryNode(item);
|
||||
if (!destDirNode)
|
||||
throw CancelException();
|
||||
auto destDirPath = destDirNode->dirent->path;
|
||||
|
||||
wxTextDataObject obj;
|
||||
obj.SetData(_dndFormat, event.GetDataSize(), event.GetDataBuffer());
|
||||
auto srcPath = Path(obj.GetText().ToStdString());
|
||||
if (srcPath.empty())
|
||||
throw CancelException();
|
||||
|
||||
ActuallyMoveFile(srcPath, destDirPath.concat(srcPath.back()));
|
||||
}
|
||||
catch (const CancelException& e)
|
||||
{
|
||||
event.Veto();
|
||||
}
|
||||
}
|
||||
|
||||
void OnBrowserCommitButton(wxCommandEvent&) override
|
||||
{
|
||||
QueueBrowserOperation(
|
||||
[this]()
|
||||
{
|
||||
_filesystem->flushChanges();
|
||||
UpdateFilesystemData();
|
||||
});
|
||||
}
|
||||
|
||||
void OnBrowserDiscardButton(wxCommandEvent&) override
|
||||
{
|
||||
QueueBrowserOperation(
|
||||
[this]()
|
||||
{
|
||||
_filesystem->discardChanges();
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
RepopulateBrowser();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void QueueBrowserOperation(std::function<void()> f)
|
||||
{
|
||||
_filesystemQueue.push_back(f);
|
||||
KickBrowserQueue();
|
||||
}
|
||||
|
||||
void KickBrowserQueue()
|
||||
{
|
||||
bool running = wxGetApp().IsWorkerThreadRunning();
|
||||
if (!running)
|
||||
{
|
||||
_state = STATE_BROWSING_WORKING;
|
||||
_errorState = STATE_BROWSING_IDLE;
|
||||
UpdateState();
|
||||
runOnWorkerThread(
|
||||
[this]()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::function<void()> f;
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
UpdateState();
|
||||
if (!_filesystemQueue.empty())
|
||||
{
|
||||
f = _filesystemQueue.front();
|
||||
_filesystemQueue.pop_front();
|
||||
}
|
||||
});
|
||||
if (!f)
|
||||
break;
|
||||
|
||||
f();
|
||||
}
|
||||
|
||||
runOnUiThread(
|
||||
[&]()
|
||||
{
|
||||
_state = STATE_BROWSING_IDLE;
|
||||
UpdateState();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OnBrowserSelectionChanged(wxDataViewEvent& event) override
|
||||
{
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
/* --- Config management
|
||||
* ------------------------------------------------ */
|
||||
|
||||
/* This sets the *global* config object. That's safe provided the worker
|
||||
* thread isn't running, otherwise you'll get a race. */
|
||||
void PrepareConfig()
|
||||
@@ -508,6 +1148,7 @@ public:
|
||||
_statusBar->HideProgressBar();
|
||||
_statusBar->SetRightLabel("");
|
||||
_state = _errorState;
|
||||
_filesystemQueue.clear();
|
||||
UpdateState();
|
||||
},
|
||||
|
||||
@@ -690,8 +1331,6 @@ public:
|
||||
|
||||
void UpdateState()
|
||||
{
|
||||
bool running = wxGetApp().IsWorkerThreadRunning();
|
||||
|
||||
if (_state < STATE_IDLE__LAST)
|
||||
{
|
||||
dataNotebook->SetSelection(0);
|
||||
@@ -724,6 +1363,36 @@ public:
|
||||
else if (_state < STATE_BROWSING__LAST)
|
||||
{
|
||||
dataNotebook->SetSelection(2);
|
||||
|
||||
bool running = !_filesystemQueue.empty();
|
||||
bool selection = browserTree->GetSelection().IsOk();
|
||||
|
||||
browserToolbar->EnableTool(
|
||||
browserBackTool->GetId(), _state == STATE_BROWSING_IDLE);
|
||||
|
||||
uint32_t c = _filesystemCapabilities;
|
||||
bool ro = _filesystemIsReadOnly;
|
||||
bool needsFlushing = _filesystemNeedsFlushing;
|
||||
|
||||
browserToolbar->EnableTool(browserInfoTool->GetId(),
|
||||
!running && (c & Filesystem::OP_GETDIRENT) && selection);
|
||||
browserToolbar->EnableTool(browserViewTool->GetId(),
|
||||
!running && (c & Filesystem::OP_GETFILE) && selection);
|
||||
browserToolbar->EnableTool(browserSaveTool->GetId(),
|
||||
!running && (c & Filesystem::OP_GETFILE) && selection);
|
||||
browserMoreMenu->Enable(browserAddMenuItem->GetId(),
|
||||
!running && !ro && (c & Filesystem::OP_PUTFILE));
|
||||
browserMoreMenu->Enable(browserNewDirectoryMenuItem->GetId(),
|
||||
!running && !ro && (c & Filesystem::OP_CREATEDIR));
|
||||
browserMoreMenu->Enable(browserRenameMenuItem->GetId(),
|
||||
!running && !ro && (c & Filesystem::OP_MOVE) && selection);
|
||||
browserMoreMenu->Enable(browserDeleteMenuItem->GetId(),
|
||||
!running && !ro && (c & Filesystem::OP_DELETE) && selection);
|
||||
browserToolbar->EnableTool(browserFormatTool->GetId(),
|
||||
!running && !ro && (c & Filesystem::OP_CREATE));
|
||||
|
||||
browserDiscardButton->Enable(!running && needsFlushing);
|
||||
browserCommitButton->Enable(!running && needsFlushing);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
@@ -785,11 +1454,66 @@ private:
|
||||
STATE_BROWSING__LAST
|
||||
};
|
||||
|
||||
class FilesystemOperation
|
||||
{
|
||||
public:
|
||||
FilesystemOperation(
|
||||
int operation, const Path& path, const wxDataViewItem& item):
|
||||
operation(operation),
|
||||
path(path),
|
||||
item(item)
|
||||
{
|
||||
}
|
||||
|
||||
FilesystemOperation(int operation, const Path& path):
|
||||
operation(operation),
|
||||
path(path)
|
||||
{
|
||||
}
|
||||
|
||||
FilesystemOperation(
|
||||
int operation, const Path& path, const std::string& local):
|
||||
operation(operation),
|
||||
path(path),
|
||||
local(local)
|
||||
{
|
||||
}
|
||||
|
||||
FilesystemOperation(int operation,
|
||||
const Path& path,
|
||||
const wxDataViewItem& item,
|
||||
wxString& local):
|
||||
operation(operation),
|
||||
path(path),
|
||||
item(item),
|
||||
local(local)
|
||||
{
|
||||
}
|
||||
|
||||
FilesystemOperation(int operation, const Path& path, wxString local):
|
||||
operation(operation),
|
||||
path(path),
|
||||
local(local)
|
||||
{
|
||||
}
|
||||
|
||||
FilesystemOperation(int operation): operation(operation) {}
|
||||
|
||||
int operation;
|
||||
Path path;
|
||||
wxDataViewItem item;
|
||||
std::string local;
|
||||
|
||||
std::string volumeName;
|
||||
bool quickFormat;
|
||||
};
|
||||
|
||||
wxConfig _config;
|
||||
wxDataFormat _dndFormat;
|
||||
std::vector<std::pair<std::string, std::unique_ptr<const ConfigProto>>>
|
||||
_formats;
|
||||
std::vector<std::unique_ptr<const CandidateDevice>> _devices;
|
||||
int _state;
|
||||
int _state = STATE_IDLE;
|
||||
int _errorState;
|
||||
int _selectedSource;
|
||||
bool _dontSaveConfig = false;
|
||||
@@ -799,6 +1523,12 @@ private:
|
||||
std::unique_ptr<TextViewerWindow> _logWindow;
|
||||
std::unique_ptr<TextViewerWindow> _configWindow;
|
||||
std::string _extraConfiguration;
|
||||
std::unique_ptr<Filesystem> _filesystem;
|
||||
uint32_t _filesystemCapabilities;
|
||||
bool _filesystemIsReadOnly;
|
||||
bool _filesystemNeedsFlushing;
|
||||
FilesystemModel* _filesystemModel;
|
||||
std::deque<std::function<void()>> _filesystemQueue;
|
||||
};
|
||||
|
||||
wxWindow* FluxEngineApp::CreateMainWindow()
|
||||
|
||||
@@ -17,6 +17,7 @@ $(call use-library, $(OBJDIR)/tests/$1.exe, $(OBJDIR)/tests/$1.o, PROTO)
|
||||
$(call use-library, $(OBJDIR)/tests/$1.exe, $(OBJDIR)/tests/$1.o, FATFS)
|
||||
$(call use-library, $(OBJDIR)/tests/$1.exe, $(OBJDIR)/tests/$1.o, ADFLIB)
|
||||
$(call use-library, $(OBJDIR)/tests/$1.exe, $(OBJDIR)/tests/$1.o, HFSUTILS)
|
||||
$(call use-library, $(OBJDIR)/tests/$1.exe, $(OBJDIR)/tests/$1.o, LIBUSBP)
|
||||
|
||||
endef
|
||||
|
||||
|
||||
20
tests/vfs.cc
20
tests/vfs.cc
@@ -8,15 +8,25 @@ static void testPathParsing()
|
||||
{
|
||||
AssertThat(Path(""), Equals(std::vector<std::string>{}));
|
||||
AssertThat(Path("/"), Equals(std::vector<std::string>{}));
|
||||
AssertThat(Path("one"), Equals(std::vector<std::string>{ "one" }));
|
||||
AssertThat(Path("one/two"), Equals(std::vector<std::string>{ "one", "two" }));
|
||||
AssertThat(Path("/one/two"), Equals(std::vector<std::string>{ "one", "two" }));
|
||||
AssertThat(Path("one"), Equals(std::vector<std::string>{"one"}));
|
||||
AssertThat(Path("one/two"), Equals(std::vector<std::string>{"one", "two"}));
|
||||
AssertThat(
|
||||
Path("/one/two"), Equals(std::vector<std::string>{"one", "two"}));
|
||||
}
|
||||
|
||||
static void testPathParenthood()
|
||||
{
|
||||
AssertThat(Path("").parent(), Equals(std::vector<std::string>{}));
|
||||
AssertThat(Path("one").parent(), Equals(std::vector<std::string>{}));
|
||||
AssertThat(
|
||||
Path("one/two").parent(), Equals(std::vector<std::string>{"one"}));
|
||||
AssertThat(Path("one/two/three").parent(),
|
||||
Equals(std::vector<std::string>{"one", "two"}));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
testPathParsing();
|
||||
testPathParenthood();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user