mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -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; | ||||
|   | ||||
							
								
								
									
										123
									
								
								lib/image.cc
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								lib/image.cc
									
									
									
									
									
								
							| @@ -1,75 +1,110 @@ | ||||
| #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); | ||||
| 		_sectors[key] = sector; | ||||
| 	} | ||||
| 	calculateSize(); | ||||
|     for (auto& sector : sectors) | ||||
|     { | ||||
|         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(); | ||||
|     return _sectors.empty(); | ||||
| } | ||||
|  | ||||
| bool Image::contains(unsigned track, unsigned side, unsigned sectorid) const | ||||
| { | ||||
| 	key_t key = std::make_tuple(track, side, sectorid); | ||||
| 	return _sectors.find(key) != _sectors.end(); | ||||
|     key_t key = std::make_tuple(track, side, sectorid); | ||||
|     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; | ||||
|     static std::shared_ptr<const Sector> NONE; | ||||
|  | ||||
| 	key_t key = std::make_tuple(track, side, sectorid); | ||||
| 	auto i = _sectors.find(key); | ||||
| 	if (i == _sectors.end()) | ||||
| 		return NONE; | ||||
| 	return i->second; | ||||
|     key_t key = std::make_tuple(track, side, sectorid); | ||||
|     auto i = _sectors.find(key); | ||||
|     if (i == _sectors.end()) | ||||
|         return NONE; | ||||
|     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>(); | ||||
| 	sector->logicalTrack = track; | ||||
| 	sector->logicalSide = side; | ||||
| 	sector->logicalSector = sectorid; | ||||
| 	_sectors[key] = sector; | ||||
| 	return sector; | ||||
|     key_t key = std::make_tuple(track, side, sectorid); | ||||
|     std::shared_ptr<Sector> sector = std::make_shared<Sector>(); | ||||
|     sector->logicalTrack = track; | ||||
|     sector->logicalSide = side; | ||||
|     sector->logicalSector = sectorid; | ||||
|     _sectors[key] = sector; | ||||
|     return sector; | ||||
| } | ||||
|  | ||||
| void Image::erase(unsigned track, unsigned side, unsigned sectorid) | ||||
| { | ||||
| 	key_t key = std::make_tuple(track, side, sectorid); | ||||
| 	_sectors.erase(key); | ||||
|     key_t key = std::make_tuple(track, side, 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 = {}; | ||||
| 	unsigned maxSector = 0; | ||||
| 	for (const auto& i : _sectors) | ||||
| 	{ | ||||
| 		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); | ||||
| 			maxSector = std::max(maxSector, (unsigned)sector->logicalSector); | ||||
| 			_geometry.sectorSize = std::max(_geometry.sectorSize, (unsigned)sector->data.size()); | ||||
| 		} | ||||
| 	} | ||||
| 	_geometry.numSectors = maxSector - _geometry.firstSector + 1; | ||||
|     _geometry = {}; | ||||
|     unsigned maxSector = 0; | ||||
|     for (const auto& i : _sectors) | ||||
|     { | ||||
|         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); | ||||
|             maxSector = std::max(maxSector, (unsigned)sector->logicalSector); | ||||
|             _geometry.sectorSize = | ||||
|                 std::max(_geometry.sectorSize, (unsigned)sector->data.size()); | ||||
|         } | ||||
|     } | ||||
|     _geometry.numSectors = maxSector - _geometry.firstSector + 1; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										101
									
								
								lib/image.h
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								lib/image.h
									
									
									
									
									
								
							| @@ -3,58 +3,91 @@ | ||||
|  | ||||
| struct Geometry | ||||
| { | ||||
| 	unsigned numTracks = 0; | ||||
| 	unsigned numSides = 0; | ||||
| 	unsigned firstSector = UINT_MAX; | ||||
| 	unsigned numSectors = 0; | ||||
| 	unsigned sectorSize = 0; | ||||
| 	bool irregular = false; | ||||
|     unsigned numTracks = 0; | ||||
|     unsigned numSides = 0; | ||||
|     unsigned firstSector = UINT_MAX; | ||||
|     unsigned numSectors = 0; | ||||
|     unsigned sectorSize = 0; | ||||
|     bool irregular = false; | ||||
| }; | ||||
|  | ||||
| class Image | ||||
| { | ||||
| private: | ||||
| 	typedef std::tuple<unsigned, unsigned, unsigned> key_t; | ||||
|     typedef std::tuple<unsigned, unsigned, unsigned> key_t; | ||||
|  | ||||
| public: | ||||
| 	Image(); | ||||
| 	Image(std::set<std::shared_ptr<const Sector>>& sectors); | ||||
|     Image(); | ||||
|     Image(std::set<std::shared_ptr<const Sector>>& sectors); | ||||
|  | ||||
| public: | ||||
| 	class const_iterator | ||||
| 	{ | ||||
| 		typedef std::map<key_t, std::shared_ptr<const Sector>>::const_iterator wrapped_iterator_t; | ||||
|     class const_iterator | ||||
|     { | ||||
|         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; } | ||||
|     public: | ||||
|         const_iterator(const wrapped_iterator_t& it): _it(it) {} | ||||
|         std::shared_ptr<const Sector> operator*() | ||||
|         { | ||||
|             return _it->second; | ||||
|         } | ||||
|  | ||||
| 	private: | ||||
| 		wrapped_iterator_t _it; | ||||
| 	}; | ||||
|         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; | ||||
|     }; | ||||
|  | ||||
| public: | ||||
| 	void calculateSize(); | ||||
|     void calculateSize(); | ||||
|  | ||||
| 	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); | ||||
| 	void erase(unsigned track, unsigned side, unsigned sectorId); | ||||
|     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); | ||||
|     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}; | ||||
| 	std::map<key_t, std::shared_ptr<const Sector>> _sectors; | ||||
|     Geometry _geometry = {0, 0, 0}; | ||||
|     std::map<key_t, std::shared_ptr<const Sector>> _sectors; | ||||
| }; | ||||
|  | ||||
| #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) | ||||
| { | ||||
|     unsigned track_step = getTrackStep(); | ||||
|     for (unsigned logicalTrack : iterate(config.tracks())) | ||||
|     if ((logicalTrack < config.layout().tracks()) && | ||||
|         (logicalHead < config.layout().sides())) | ||||
|     { | ||||
|         for (unsigned head : iterate(config.heads())) | ||||
|         { | ||||
|             if ((logicalTrack == desiredTrack) && (head == desiredHead)) | ||||
|             { | ||||
|                 unsigned physicalTrack = | ||||
|                     config.drive().head_bias() + logicalTrack * track_step; | ||||
|         unsigned track_step = getTrackStep(); | ||||
|         unsigned physicalTrack = | ||||
|             config.drive().head_bias() + logicalTrack * track_step; | ||||
|  | ||||
|                 return {.physicalTrack = physicalTrack, | ||||
|                     .logicalTrack = logicalTrack, | ||||
|                     .head = head, | ||||
|                     .groupSize = track_step}; | ||||
|             } | ||||
|         } | ||||
|         return {.physicalTrack = physicalTrack, | ||||
|             .logicalTrack = logicalTrack, | ||||
|             .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; | ||||
|             } | ||||
|  | ||||
|             dirent->length = std::max( | ||||
|                 dirent->length, entry->extent * 16384 + entry->records * 128); | ||||
|             if (dirent->filename == path.front()) | ||||
|                 return dirent; | ||||
|         } | ||||
|  | ||||
|         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; | ||||
|         throw FileNotFoundException(); | ||||
|     } | ||||
|  | ||||
|     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 attributes; | ||||
|         return toDirent(de, path.parent()); | ||||
|     } | ||||
|  | ||||
|     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() | ||||
| { | ||||
| } | ||||
|   | ||||
							
								
								
									
										2604
									
								
								src/gui/layout.fbp
									
									
									
									
									
								
							
							
						
						
									
										2604
									
								
								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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										28
									
								
								tests/vfs.cc
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								tests/vfs.cc
									
									
									
									
									
								
							| @@ -6,17 +6,27 @@ using namespace snowhouse; | ||||
|  | ||||
| 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(""), 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"})); | ||||
| } | ||||
|  | ||||
| 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(); | ||||
| 	return 0; | ||||
|     testPathParsing(); | ||||
|     testPathParenthood(); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user