Object Oriented Design Course : Exercise 3
Exercise 3 - Kraken - The File System Manager
Deadline June 1, 2004 at Midnight
Description Kraken In this exercise you will:
  • Design and build a file system manager - Kraken
  • Produce a set of UML class and sequence diagrams to document your design
  • Experience design patterns in actual C++ or Java code
  • Write a covering set of unit tests

Requirements

In this exercise you will develop kraken - a simple shell for manipulating files. kraken command line is:
    kraken <root directory>
The shell will start in the given root directory. Its prompt is: kraken:DIR> where DIR is the same with unix - / for the root, /a/b/c for directory c under directory b under directory a under the root directory, and so on. The file system (and its types) are the same used in exercise 1. One change from exercise 1 is that you have to support all the mime types specified in the given list. If the file extension is missing from the list (or if it has no extension) then write "unknown" as MIME type.

Commands

  • Display:
    • cd <path> - sets the current directory to path. cd .. sets the current directory to the parent directory, except when the current directory is the root directory (same as unix cd command works). The / character specifies the root directory, as given when starting kraken.
    • list [-n] [-d] [-r] - prints the structure of the current directory in xml format. The format and behaviour are the same as in exercise 1. The -n and -d parameters are the same as nameonly an dironly (respectively), and -r means the list should be recursive - descend into directories (as was the default in exercise 1).
  • File Manipulation:
    • copy [-r] <file|dir> <destination> - copies the file to the destination location (same as the unix cp command works). The -r parameter means recursive copy - if the copied file is actually a directory, then all the files and directories that it contains (recursively) will be copied as well.
    • move <file|dir> <destination> - moves the file/directory to the destination location. Just like the unix mv command works, this command can also be used to either move a file, rename it or do both things at once.
    • delete <file|dir> - deletes the file. Only an empty directory can be deleted.
  • Action Manipulation:
    • commit - performs the copies/moves/deletes done since the last commit or rollback to the actual file system. Commited actions cannot be undone (using undo). The commit action is the only one that causes changes to the real file system. All other actions happen only in memory, and execute in batch mode if and when commit is called. The cd is not executed on the real file system.
    • rollback - undoes all the actions done from the last commit or rollback. This is equal to calling undo for all the actions done since the last commit/rollback.
    • undo - undoes the last action done. If no such action exists, does nothing. All commands can be undone except list, commit and rollback. It is possible to call undo an unlimited number of times, to undo all actions done since the last commit or rollback.
    • redo - redoes the last undo'ed action. If no such action exists or if the last action wasn't undo, does nothing. It is possible to call redo an unlimited number of times, to redo all undone actions.
  • Mirrorring:
    • mirror <dirA> <dirB> - mirrors dirA and dirB. This means the following:
      • Any copy, move or delete operation done on dirA should be done on dirB, and vice versa (the mirroring is symmetric).
      • No mirror can be established between a directory and one of its ancestors.
      • If dirA and dirB are mirrored, then copying or moving dirA into dirB (or vice versa) is not allowed. Deleting either DirA or DirB is not allowed as well.
      • If dirA (or dirB) is moved or renamed, the mirror is kept with the directory in its new location/name. If dirA (or dirB) is copied, then dirB is mirrored with both the original dirA (or dirB) and its new copy.
      • If a copy, move or delete is done on a file or directory that exists only in one of the mirrored directories, then nothing is done in the other mirrored directory.
      • Mirroring is transitive - if dirA mirrors dirB, and dirB mirrors dirC, then any action done on any of the three directories, is done in all three of them.
    • unmirror <dirA> <dirB> - "breaks" the mirror between dirA and dirB.
  • General:
    • quit - exits the shell.
Here is a small example of the shell:
kraken:/> cd docs
kraken:/docs>cd images
kraken:/docs/images>copy a.gif ../newimages
kraken:/docs/images>cd ..
kraken:/docs/mirror images1 images2
Here is an algorithm for handling the mirror:
  1. Translate command to full paths
  2. Loop over all mirrors, and replace strings where they match (bi-linear)
  3. Build list of strings to execute. Don't add duplicate strings, stop checking for matches when the list stops changing.
  4. Execute the final list of commands. The order is undefined, except the fact that the original command must be executed first.
  5. That same list in the same order is executed upon commit. Undo does the list in the exact same order.
  6. If any command fails (or illegal) - ignore it.
  7. Move is not a mirrored command. After executing an 'mv' command, don't check for mirrors at all.
For example:
assume we have the following:
/
+A
|+B
| +C
|  +a.txt
+D
|+B
| +C
|  +a.txt
There are two mirrors : /A-/D, /A/B/C-/D/B/C. Here is the command:
kraken:/A/B/C>copy a.txt b.txt
First, we normalize the command:
copy /A/B/C/a.txt /A/B/C/b.txt
Then, we add the commands from the mirror /A-/D:
    copy /A/B/C/a.txt /D/B/C/b.txt
    copy /D/B/C/a.txt /A/B/C/b.txt
    copy /D/B/C/a.txt /D/B/C/b.txt
Then, we add the commands from the mirror /A/B/C-/D/B/C:
    nothing new is added
So we execute the following:
copy /A/B/C/a.txt /A/B/C/b.txt (original command, executed first)
copy /A/B/C/a.txt /D/B/C/b.txt
copy /D/B/C/a.txt /A/B/C/b.txt
copy /D/B/C/a.txt /D/B/C/b.txt
Also notice that there are overwrite of files in the copy command, so please address it.

Future requirements

Consider the following possibilities, and address them in your README.html:
  1. Presume we have another command, backup <name>, which saves the system's status. The command restore <name> restores the entire system to the named backed-up version. How would you implement this command? How would you implement the restore?
  2. How would you add symbolic links to system? All the operations should work on the linked file except delete, which should delete the link itself. Explain how you would implement cd and mirror when the link points to a directory.
  3. Suppose we have the following command: define <mime-type> <viewer> which sets the viewer of files with that mime-type (for example, a browser fro html files). The viewer will be launched, in a separate process, when the file name is written in the shell.
  4. Suppose we have a refesh command which synchronizes the memory file system with the actual file system. This is useful when the actual file system is manipulated while kraken is running and there are un-commited actions. Refresh should notify the user on all the un-commited actions that are no longer valid. refesh cannot be undone.
  5. Consider actions are immediately flushed to the file system (as Windows Explorer and Konqueror are working). How would you change your application? In your answer adress all the commands listed above. (You can elaborate here...)
You must design your program so that it is easy to add code that implements the above requirements. For each of the above requirements write an explanation in your README file, not more than five sentences long, which explains how it should be coded. Use the patterns vocabulary, as well as references to the classes in your design.

Code & Unit Test

This exercise intends you to divide your time equally between actual coding and between design, writing UML diagrams, and answering the above design questions. You must base your implemetation on the submitted code from exercise 1. Refactor it if necessary - document these refactorings in the README.html file.

It is also required that you submit unit tests to test your work. Organize your unit tests into classes by subject, and write a method for each small test. Each test should be self-validating - that is, know by itself whether it has passed or failed. Writing unit tests should be an integral part of coding, and is essential when code must be changed in newer versions.

The code you submit must be built with no compiler warnings, and pass all unit tests.

One metric for measuring the usefulness of a set of unit tests is called coverage, which means the percentage of your code that the unit tests actually run. Coverage of 90% or above is considered good, and you should aim to that goal.

This is an advanced course, so there is no intention to take points for coding style or naming conventions - the emphasis is on proper design. However, you are as always expected to write clear code with a consistent style.

README.html - In order to simplify the usage of UML, you are required to export your UML diagrams to image files (jpg or gif) and display them in an HTML file called README.html. This file will replace the README file. It is recommended that you will take advantage of HTML's capabilities (headlines, tables, lists, etc.) but it is not required. No fancy JavaScript/CSS is required as well. You are required to add a meta tag to the header with your logins like this:

    <meta name="students" content="bart, lisa">
Example README.html can be found here.

Submission Submit a zip file contains all your sources, a README.html and the Makefile. If you use Java, submit the shell script as well.
Resources