Improve HACKING.md
This commit is contained in:
parent
a4c8995299
commit
8348f34a4a
@ -1,25 +1,23 @@
|
||||
HACKING
|
||||
=======
|
||||
# HACKING
|
||||
|
||||
Check out the [issue tracker](https://github.com/hasufell/hsfm/issues)
|
||||
if you don't know yet what you want to hack on.
|
||||
|
||||
Coding style
|
||||
------------
|
||||
## Coding style
|
||||
|
||||
- match the sorroundings
|
||||
- no overcomplicated pointfree style
|
||||
- normal indenting 2 whitespaces
|
||||
- just make things pretty and readable
|
||||
- use the provided [hsimport.hs](hsimport.hs)
|
||||
- you can use the provided [hsimport.hs](hsimport.hs)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
## Documentation
|
||||
|
||||
__Everything__ must be documented. :)
|
||||
Don't assume people know what you mean. Type signatures are not sufficient
|
||||
documentation.
|
||||
|
||||
Hacking Guide
|
||||
-------------
|
||||
## Hacking Overview
|
||||
|
||||
The main data structure for the IO related File type is in
|
||||
[HSFM.FileSystem.FileType](./../src/HSFM/FileSystem/FileType.hs#L93), which
|
||||
@ -54,3 +52,73 @@ following files:
|
||||
* [HSFM.GUI.Gtk.MyGUI](./../src/HSFM/GUI/Gtk/MyGUI.hs): add initializers for the GUI buttons to be fetched from the GTK builder.xml file
|
||||
* [HSFM.GUI.Gtk.Callbacks](./../src/HSFM/GUI/Gtk/Callbacks.hs): define the callbacks and the actual functionality here
|
||||
|
||||
## Concepts
|
||||
|
||||
### Path safety
|
||||
|
||||
Paths are usually represented in haskell libraries as `type FilePath = String`.
|
||||
This is bad, because of a number of reasons:
|
||||
* encoding issues, since the low-level representation of filepaths is in fact an array of C chars
|
||||
* weak typing... we could pass arbitrary invalid/malicious filepaths or other random strings
|
||||
* no information about any property at type level (e.g. is it an absolute path?)
|
||||
* no filepath constructors that do sanity checks and proper parsing
|
||||
* no guarantee whether the filepath is normalised or not or even valid
|
||||
|
||||
Because of that, the solution is:
|
||||
* use `ByteString` under the hood
|
||||
* wrap it inside `Path t` where `t` can be either `Abs` (for absolute), `Rel` (for relative) or `Fn` (for filename)
|
||||
* construct filepaths via smart constructors only that reject certain paths (like `.` or `..`) and normalise the path
|
||||
|
||||
This leads to the following benefits:
|
||||
* we have guarantees about whether a path is absolute or not, which is important for runtime safety in general, predictable behavior and thread safety
|
||||
* we don't mess with the filepath representation we get from low-level posix functions, so encoding issues are pretty much out
|
||||
* we can reason about filepaths and rely on them to be valid (don't confuse that with "they exist")
|
||||
* filepath functions like `(</>)` are now predictable and safe in contrast to the version from the `filepath` package
|
||||
|
||||
The only problem with this approach is that most libraries are still String
|
||||
based. Some provide dedicated `Foo.ByteString` modules though, but it
|
||||
might be necessary to fork libraries.
|
||||
We also need to keep track of the [Abstract FilePath proposal](https://ghc.haskell.org/trac/ghc/wiki/Proposal/AbstractFilePath).
|
||||
|
||||
Almost all paths in HSFM are only allowed to be absolute (`Path Abs`), unless
|
||||
they are filenames (`Path Fn`) and processed for GUI purposes. This is as
|
||||
already mentioned for the purpose of runtime safety, predictability and
|
||||
thread safety.
|
||||
|
||||
### File IO safety
|
||||
|
||||
This is a pretty difficult problem. One thing to ensure safety on IO level
|
||||
is simply the strong haskell type system, since we push everything
|
||||
into our `File a` type and can then pattern match easily against the different
|
||||
types of files.
|
||||
|
||||
The only problem with this approach is that we are examining a file at point
|
||||
`a` in time, safe the information and then use that information further down
|
||||
the call stack at point `b` in time, when the file information in memory
|
||||
could already be out of date. There are two approaches to make this less
|
||||
sucky:
|
||||
* use the hinotify library on GUI level to refresh the view whenever the contents of a directory changes
|
||||
* when we stuff something into the copy buffer, it is not saved as type `File a`, but as `Path Abs`... when the operation is finalized via `runFileOp`, then the file at the given path is read and the copy/move/whatnot function carried out immediately
|
||||
|
||||
This means we should only interact with the `HSFM.FileSystem.FileOperation`
|
||||
module via the operation data types `FileOperation`, `Copy` and `Move` and
|
||||
the `runFileOp` function. This doesn't completely solve the problem, but for
|
||||
the rest we have to trust the posix functions to throw the proper exceptions.
|
||||
|
||||
In addition, we don't use the `directory` package, which is dangerous
|
||||
and broken. Instead, we implement our own low-level wrappers around
|
||||
the posix functions, so we have proper control over the internals
|
||||
and know the possible exceptions.
|
||||
|
||||
### Exception handling
|
||||
|
||||
Exceptions are good. We don't want to wrap everything in Maybe/Either types
|
||||
unless we want to handle failure immediately. Otherwise we need to make
|
||||
sure that at least at some point IOExceptions are caught and visualized
|
||||
to the user. This is often done via e.g. `withErrorDialog` which catches
|
||||
`IOException` and `FmIOException`.
|
||||
|
||||
It's also important to clean up stuff like filedescriptors via
|
||||
functions like `bracket` directly in our low-level code in case
|
||||
something goes wrong.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user