hsfm/hacking/HACKING.md

125 lines
6.4 KiB
Markdown
Raw Normal View History

2016-04-21 00:45:23 +00:00
# HACKING
2015-12-19 23:57:34 +00:00
2016-04-16 23:31:13 +00:00
Check out the [issue tracker](https://github.com/hasufell/hsfm/issues)
if you don't know yet what you want to hack on.
2016-04-21 00:45:23 +00:00
## Coding style
2015-12-19 23:57:34 +00:00
- match the sorroundings
- no overcomplicated pointfree style
- normal indenting 2 whitespaces
- just make things pretty and readable
2016-04-21 00:45:23 +00:00
- you can use the provided [hsimport.hs](hsimport.hs)
2015-12-19 23:57:34 +00:00
2016-04-21 00:45:23 +00:00
## Documentation
2015-12-19 23:57:34 +00:00
__Everything__ must be documented. :)
2016-04-21 00:45:23 +00:00
Don't assume people know what you mean. Type signatures are not sufficient
documentation.
2015-12-19 23:57:34 +00:00
2016-04-21 00:45:23 +00:00
## Hacking Overview
2015-12-19 23:57:34 +00:00
2016-04-16 23:31:13 +00:00
The main data structure for the IO related File type is in
2016-04-16 23:37:29 +00:00
[HSFM.FileSystem.FileType](./../src/HSFM/FileSystem/FileType.hs#L93), which
2016-04-16 23:31:13 +00:00
should be seen as a library. This is the entry point where
2016-04-16 23:37:29 +00:00
[directory contents are read](./../src/HSFM/FileSystem/FileType.hs#L465)
and the File type in general [is constructed](./../src/HSFM/FileSystem/FileType.hs#L302).
2016-04-16 23:31:13 +00:00
The File type uses a safe Path type under the hood instead of Strings,
utilizing the [hpath](https://github.com/hasufell/hpath) library.
2016-04-16 23:44:53 +00:00
Note that mostly only absolute paths are allowed on type level to improve
path and thread safety.
2015-12-19 23:57:34 +00:00
File operations (like copy, delete etc) are defined at
2016-04-16 23:37:29 +00:00
[HSFM.FileSystem.FileOperation](./../src/HSFM/FileSystem/FileOperations.hs)
2016-04-16 23:31:13 +00:00
which use this File type.
Only a GTK GUI is currently implemented, the entry point being
2016-04-16 23:37:29 +00:00
[HSFM.GUI.Gtk](./../src/HSFM/GUI/Gtk.hs). From there it flows down
to creating a [MyGUI object](./../src/HSFM/GUI/Gtk/Data.hs#L51) in
[HSFM.GUI.Gtk.MyGUI](./../src/HSFM/GUI/Gtk/MyGUI.hs), which is sort of
2016-04-16 23:31:13 +00:00
a global object for the whole window. Inside this object are
2016-04-16 23:37:29 +00:00
theoretically multiple [MyView objects](./../src/HSFM/GUI/Gtk/Data.hs#L101)
2016-04-16 23:31:13 +00:00
allowed which represent the actual view on the filesystem and related
widgets, which are constructed in
2016-04-16 23:37:29 +00:00
[HSFM.GUI.Gtk.MyView](./../src/HSFM/GUI/Gtk/MyView.hs). Both MyGUI and MyView
2016-04-16 23:31:13 +00:00
are more or less accessible throughout the whole GTK callstack, expclicitly
passed as parameters.
For adding new GTK widgets with functionality you mostly have to touch the
following files:
2016-04-16 23:37:29 +00:00
* [builder.xml](./../data/Gtk/builder.xml): this defines the main GUI widgets which are static, use the [glade editor](http://glade.gnome.org) to add stuff
* [HSFM.GUI.Gtk.Data](./../src/HSFM/GUI/Gtk/Data.hs): add the widget to e.g. the MyGUI type so we can access it throughout the GTK call stack
* [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
2015-12-19 23:57:34 +00:00
2016-04-21 00:45:23 +00:00
## 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:
2016-04-21 00:50:10 +00:00
* use the hinotify library on GUI level to refresh the view (and the File representation in memory) whenever the contents of a directory changes
2016-04-21 00:45:23 +00:00
* 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.