\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}