feat: project 3
This commit is contained in:
parent
8f4463782c
commit
c04f363e57
15 changed files with 5425 additions and 15 deletions
237
project3/report/report.tex
Normal file
237
project3/report/report.tex
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
\documentclass{article}
|
||||
|
||||
\usepackage{graphicx}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{minted}
|
||||
\usepackage{hyperref}
|
||||
|
||||
\title{CS3502 Project 3: File Manager}
|
||||
\author{Kiana Sheibani}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
\newpage
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
Circumstance compels us to program a GUI file manager from scratch, as it so
|
||||
often does.
|
||||
|
||||
\subsection{Objective}
|
||||
|
||||
A file manager is a tool for reading and manipulating a file-system, typically
|
||||
the primary file-system of the machine. It allows the user to perform various
|
||||
operations on the files and directories (\emph{entries} in aggregate) of the
|
||||
file-system, such as to:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Create} new entries;
|
||||
\item \textbf{Read} and \textbf{Update} the contents of entries;
|
||||
\item \textbf{Delete} entries.
|
||||
\end{itemize}
|
||||
|
||||
Commonly, file managers are designed to allow this manipulation by having the
|
||||
user traverse the filesystem one directory at a time, moving from a single
|
||||
working directory to its sub- or parent directories.
|
||||
|
||||
\subsection{Resources}
|
||||
|
||||
Implementing a file manager is not a trivial task. Properly navigating a
|
||||
file-system requires quite a bit of engineering around complex systems,
|
||||
unintuitive edge cases and strange errors, and some languages and frameworks are
|
||||
better equipped to handle this than others.
|
||||
|
||||
After considering this fact, I decided on \href{https://rust-lang.org/}{Rust} as
|
||||
my language of choice. Not only do I have significant prior experience with it,
|
||||
but its low-level nature and near-slavish dedication to correctness make it
|
||||
ideal for working with file-systems.
|
||||
|
||||
Once I had my language picked, I searched for GUI libraries I could use for the
|
||||
front-end. It was not long before I found
|
||||
\href{https://crates.io/crates/xilem/}{Xilem}, an experimental cross-platform UI
|
||||
toolkit. It was still somewhat unstable, but I decided it would be perfectly
|
||||
fine for my purposes.%
|
||||
\footnote{This assumption turned out to be rather short-sighted, but I made it
|
||||
work as best I could.}
|
||||
|
||||
\section{Design and Architecture}
|
||||
|
||||
\subsection{Front-End Design}
|
||||
|
||||
\begin{figure}[tbh]
|
||||
\includegraphics[width=\linewidth]{assets/gui.png}
|
||||
\caption{The UI of the file manager.}
|
||||
\label{fig:gui}
|
||||
\end{figure}
|
||||
|
||||
The UI design of the file manager is somewhat inspired by
|
||||
\href{https://github.com/ranger/ranger}{{\texttt{ranger}}}, though heavily
|
||||
simplified. The current directory path is displayed at the top, and its entries
|
||||
are listed in the main panel below it. When a file is opened, its contents
|
||||
appear on a panel to the right, and remain there until another file is opened.
|
||||
|
||||
The file-system can be navigated by either double-clicking on a subdirectory, or
|
||||
selecting it and then clicking the \emph{Open} button at the bottom. To move up
|
||||
the directory tree, a component of the topmost path display can be clicked on.
|
||||
There are also several other action buttons along the bottom; these operate on
|
||||
the currently selected entry (aside from the \emph{New File} and \emph{New Dir}
|
||||
actions) and using them pops up a dialog box to confirm the action. The dialog
|
||||
box popup is also used to display errors whenever they occur.
|
||||
|
||||
\begin{figure}[tbh]
|
||||
\includegraphics[width=\linewidth]{assets/dialog.png}
|
||||
\caption{The file manager showing a file renaming dialog.}
|
||||
\label{fig:dialog}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Program Architecture}
|
||||
|
||||
The structure of the program can be conceptually divided into two halves: the
|
||||
file-system management subroutines, and the GUI logic.
|
||||
|
||||
\subsubsection{File-System Management}
|
||||
|
||||
Internally, an entry in the current directory is stored as an \texttt{EntryData}
|
||||
object (Listing~\ref{listing:EntryData}), which holds the entry's name and other
|
||||
metadata. The basic file-system operations, such as renaming and deleting
|
||||
entries, all operate on this data structure.
|
||||
|
||||
\begin{listing}[!ht]
|
||||
\inputminted[
|
||||
firstline=7, lastline=17,
|
||||
fontsize=\footnotesize,
|
||||
linenos,
|
||||
frame=lines,
|
||||
framesep=2mm,
|
||||
baselinestretch=1.1,
|
||||
]{rust}{../src/files.rs}
|
||||
\vspace*{-\baselineskip}
|
||||
\caption{\texttt{files.rs} --- Definition of \texttt{EntryData}}
|
||||
\label{listing:EntryData}
|
||||
\end{listing}
|
||||
|
||||
Reading and editing a file, on the other hand, requires a different structure to
|
||||
store and manipulate the file handle. This is defined as \texttt{FileData}
|
||||
(Listing~\ref{listing:FileData}). When a file is opened, the file handle is
|
||||
retained for the entire duration the file is viewed, only being closed when the
|
||||
file is exited. The \texttt{files} module of the program implements an
|
||||
\texttt{open} function to create this data structure and a \texttt{save}
|
||||
function to write to the file handle that it holds.
|
||||
|
||||
\begin{listing}[!ht]
|
||||
\inputminted[
|
||||
firstline=72, lastline=85,
|
||||
fontsize=\footnotesize,
|
||||
linenos,
|
||||
frame=lines,
|
||||
framesep=2mm,
|
||||
baselinestretch=1.1,
|
||||
]{rust}{../src/files.rs}
|
||||
\vspace*{-\baselineskip}
|
||||
\caption{\texttt{files.rs} --- Definition of \texttt{FileData}}
|
||||
\label{listing:FileData}
|
||||
\end{listing}
|
||||
|
||||
\subsubsection{GUI \& Error Handling}
|
||||
|
||||
Xilem is a reactive GUI framework based on composable widgets, similar to Qt.
|
||||
The system is based around a data structure representing the app's state; in the
|
||||
case of this program, the \texttt{AppState} type includes the current directory,
|
||||
selected entry, and state for any open panels, such as the \texttt{FileData} for
|
||||
the open file.
|
||||
|
||||
\texttt{AppState} also includes the \texttt{DialogState}, which is an enum with
|
||||
one value per type of dialog that can be opened: \texttt{NewFile},
|
||||
\texttt{NewDir}, \texttt{Rename}, \texttt{Delete}, and \texttt{Error}, where the
|
||||
last value also includes an error message to display. Determining when to show
|
||||
these errors is simple: thanks to Rust's aforementioned obsession with
|
||||
correctness, every file-system routine it provides that could ever conceivably
|
||||
result in any sort of error wraps its return type in a \texttt{std::io::Result}
|
||||
data structure, which forces the programmer to handle the error case before the
|
||||
actual output can be retrieved. This is why I chose Rust for this project, and
|
||||
it led to very simple and easy-to-understand file management code.
|
||||
|
||||
\section{Testing \& Edge Cases}
|
||||
|
||||
In order to ensure the robustness of my work, I regularly tested it manually to
|
||||
make sure that it functioned properly and handled any of the weird edge cases
|
||||
file-system code is prone to. Some of these issues I was able to fully
|
||||
eliminate, while others were a bit more stubborn.
|
||||
|
||||
\subsection{File-System Race Conditions}
|
||||
|
||||
The file manager is only one of many processes that can access the file-system,
|
||||
which can lead to issues if two processes are trying to modify the same
|
||||
directory at the same time. For example, if another process deletes a file in
|
||||
the current directory of the file manager, an orphan entry may be left over that
|
||||
no longer points to an existing file. While the file manager will not
|
||||
automatically detect that this has occurred, it will throw an error if the user
|
||||
tries to access an entry that no longer exists.
|
||||
|
||||
\subsection{Uncommon Entry Types}
|
||||
|
||||
So far, the only two entry types mentioned have been files and subdirectories,
|
||||
the two most common types. There are others, however; symbolic links, or
|
||||
``symlinks'', and more esoteric file types like named pipes, device files, etc.
|
||||
Symlinks are of particular concern, as they violate the file-system's otherwise
|
||||
perfect tree structure.
|
||||
|
||||
To avoid hard-to-debug issues down the line, I decided on a policy for handling
|
||||
symlinks from the start:
|
||||
|
||||
\begin{itemize}
|
||||
\item In the main panel, symlinks are not followed and are displayed
|
||||
distinctly from the entries that they point to, including their
|
||||
metadata. This is done for performance reasons.
|
||||
\item Opening a symlink follows it to its target, with the caveat that
|
||||
if a symlink is followed into a subdirectory, the displayed path is
|
||||
canonicalized. This simplifies the navigation.
|
||||
\end{itemize}
|
||||
|
||||
All other entry types simply throw an error when the user tries to open them, as
|
||||
do any files that do not contain UTF-8 text.
|
||||
|
||||
\subsection{Unusual Entry Names}
|
||||
|
||||
While most entry names are simple Latin letters and numbers, depending on the
|
||||
operating system there are many other possibilities. Unix-based systems
|
||||
typically have UTF-8 entry names, though they technically allow any string of
|
||||
bytes except for \texttt{0x00} (NUL) or \texttt{0x2F} (/). Windows, on the other
|
||||
hand, has historically used UTF-16 for its entry names, as it predated the wide
|
||||
adoption of the superior UTF-8.
|
||||
|
||||
To allow cross-platform programs to navigate this confusion, the Rust standard
|
||||
library provides the \texttt{OsString} type, which represents a string in the
|
||||
format the target OS prefers. Due to this and some careful programming, the file
|
||||
manager will never crash due to unsupported entry names (at least on Linux; I
|
||||
have not personally tested Windows). Unfortunately, while non-Unicode entry
|
||||
names can be read, they cannot be entered into dialog text fields. This is an
|
||||
inherent limitation of Xilem that is unlikely to change.
|
||||
|
||||
\subsection{Large File Sizes}
|
||||
|
||||
This is the largest issue that still remains in the file manager. Due to the
|
||||
fact that a full buffer of an open file's contents must be maintained, the file
|
||||
editor is exceedingly slow with files over a few kilobytes, and trying to open
|
||||
anything larger than a megabyte has a chance of causing the application to
|
||||
crash. Ideally, the program would use some sort of line buffering scheme to
|
||||
avoid having to update the file all at once, but I was not able to implement
|
||||
anything that worked well.
|
||||
|
||||
\section{Conclusion}
|
||||
|
||||
After roughly a week's effort, a basic file manager has been achieved. Though
|
||||
the visual aesthetic and the file editing feature may be improved, this
|
||||
serves as a strong foundation for a well-programmed application.
|
||||
|
||||
\subsection{Learning Outcomes}
|
||||
|
||||
While I most likely started from a more knowledgeable place on this subject than
|
||||
most in this class, I did learn some new things from this process. I was not
|
||||
nearly as knowledgeable on the minutiae of OS file-systems when I started this
|
||||
project as I am now, nor was I as experienced in writing GUI applications that
|
||||
properly interface with that OS minutia. At the end of it all, I am glad that
|
||||
something of some use came out of this.
|
||||
|
||||
\end{document}
|
||||
Loading…
Add table
Add a link
Reference in a new issue