Racket代写 – Exercise 7: Your Computer’s File System

Exercise 7: Your Computer’s File System
EECS 111, Fall 2017
Due Monday, November 20th by noon
In this exercise, you’ll learn to write programs that explore your computer’s le system. Speci cally, you’ll be writing:
• Procedures to back up your lesystem by copying folders and les from one location to another, and
• Procedures to search your lesystem for les of a certain name, extension, etc. Introduction: Filesystem terminology
There are three concepts from le systems we are using here: les, folders, and paths. Filesystems are shaped like trees, so this should feel familiar from past assignments. If you feel comfortable with lesystem data structures, feel free to skip this section.
• Files have names and contain data, like the .rkt le you’re currently working in. (These are like leaf nodes on a tree.)
• Folders, also known as directories, are recursive structures that have names and contain two types of data:
– Files
– Other folders
Folders are like non-leaf nodes on trees.
• Paths represent where to nd les in a le system. They consist of a sequence of folder names, and may or may not end in a le name. For example:
– /test/Test2/bar.txt says there is a folder test, which contains a folder called Test2, which contains a le called bar.txt and we are referring to this le.
– /test/Test3/ says there is a folder test, which contains a folder called Test3, and we are referring to this folder.
You may have seen paths written as strings, with folder names separated by a “/”. In Racket, paths are their own data type – they are NOT strings.
To create a new path, use build-path : string … -> Path: (build-path “test” “Test2” “bar.txt”) ; /test/Test2/bar.txt
(build-path “test” “Test3”) ; /test/Test3/
You can also convert between the two data types using the procedures string->path and
(define p (build-path “test” “Test3”))
(path->string p) ; “test/Test3”
(string->path “test/Test2/bar.txt”) ; # 1
Figure 1: Sample lesystem for this assignment
In this assignment, you will use the sample lesystem we have created for you to test with:
The functions you’ll be writing have real side e ects (for instance, delete-file! will actually delete real les). As a safeguard, all of these operations will only work on les and directories that are descendants of the folder where your assignment le is saved.
However, this does not prevent you from accidentally deleting your homework. BE CAREFUL NOT TO DO THIS! Make sure that whenever you close DrRacket, your homework le still exists. We will not grant extensions for accidentally erasing your work. #imperativeprogramming
Troubleshooting documentation
All of the le operations you need for this assignment are documented under cs111/file-operations. If, when you search, you see something like this:
Figure 2: “No matches found in Advanced Student”
then click the “clear” link and the documentation you’re looking for should appear.
Part 1: Backing up les
Before you write any new code, read through and understand the starter code for the backup! procedure.
You can try using the procedure like this:
(backup! (string->path “test”) (string->path “output”))
This command will take all the les in the test subdirectory of the assignment, and copy them into another directory called output. You should also see the following in the interactions window:
Copying file test\foo.bmp to output\foo.bmp
Copying file test\Test.txt to output\Test.txt
Copying file test\Test2\bar.txt to output\Test2\bar.txt
Copying file test\Test2\foo.bmp to output\Test2\foo.bmp
Question 1: Not copying existing les
Modify backup! so that it only copies a le if it does not already exist in the destination directory.
In its current form, (backup! (string->path “test”) (string->path “output”)) will create a directory called output that holds identical copies of all the les and subdirectories of test. Unfortunately, if we run it a second time, it will re-copy all the les into output, even though the les already exist!
You can test this procedure by doing the following:
1. Run(backup!(string->path”test”)(string->path”output”)),andverifytheoutputdirectory is created
2. It doesn’t copy les unnecessarily: Create a new le, such as new-file.txt, somewhere within the test directory. Run the same command, and verifying that new-file.txt gets copied into output, but none of the other les are re-copied. (You can check the “Date Added” eld in your le explorer to con rm this.)
3. It still copies les when it needs to: Delete one of the les from output, and re-run the command. The deleted le should be re-copied.
Useful functions
• file-exists? : Path -> Boolean returns #true if the le at the given path exists, else #false. Hints
• when and unless are the imperative counterparts to if:
(when )
;; If returns #true, run .
;; Returns (void) either way.
(unless )
;; If returns #false, run .
;; Returns (void) either way.
when is very similar to:
(if (void))
• The piece of code that handles copying les is this:
(printf “Copying file ~A to ~A~n” file to) (copy-file! file
(build-path to (path-filename file))
You’ll want to conditionally execute this begin statement depending on whether or not the destination le exists.
Question 2: Updating stale backups
Modify backup! to copy les that exist in the output directory, but have been modi ed in the origin since they were last backed up.
In the previous question, we modi ed backup! to avoid making copies needlessly, but we made it too aggressive: now, if we make a backup and then change the original le, backup! won’t copy the revised version into the output folder.
Update your code from Question 1 to copy all les, when
• The le does not already exist in the backup directory (this was Question 1), OR
• The le already exists in the backup directory, but the original le has been modi ed since the backup
was created. In other words,
To test this function:
DateModifiedfrom ≥ DateModifiedto
1. Check for regressions: Repeat all the tests from the previous question, to make sure no behavior regressed (used to work and now doesn’t).
2. It re-copies les that have been modi ed: Modify one of the les in the origin test directory, for example, by editing Test.txt. Now re-run the backup! command, and check to ensure the le was copied over.
Useful functions
• file-or-directory-modify-seconds : Path -> Number takes a path, and returns a number repre- senting when the le was last changed. Greater numbers correspond to later times, meaning the le was modi ed more recently.
> (file-or-directory-modify-seconds (build-path “test” “Test2” “bar.txt”)) 1479027433
• On Windows, copying a le gives it the same “Date Modi ed” as the original, so make sure you do
not copy the le if the modi cation times are the same.
• If you are getting the following error:
file-or-directory-modify-seconds: error getting file/directory time …
system error: No such file or directory; errno=2
Remember that order matters in your code, and it’s important to check whether the le exists BEFORE you check when it was last modi ed. (You can’t ask the operating system for the date modi ed of a nonexistent le!)
So you should structure your code like this:
(and (file-exists? …)
…check last modification time…)
You won’t have to worry about the error, because and will evaluate conditionals in order and bail early as soon as one condition returns #false.
Part 2: Searching for les
Now that we have a functioning backup program, we’ll write some procedures for searching through the lesystem to give you information about your les and directories.
Question 3: Counting les
Write a procedure, count-files, that takes the pathname of a directory as input and returns the number of les within the directory and all its subdirectories (and their subdirectories, recursively).
;; count-files : path -> void
;; Takes a path to a directory, and returns the number of files within the directory
;; and all its descendants, recursively.
(count-files (build-path “test”)) ; 4, assuming you didn’t modify the test filesystem Useful functions
• directory-files : Path -> List-of-Path takes a directory path, and returns a list of paths to the les contained in that directory.
> (directory-files (build-path “test”)) (list
# ; invisible system file, ignore # #)
• directory-subdirectories : Path -> List-of-Path takes a directory path, and returns a list of paths to the sub-directories contained in that directory.
> (directory-subdirectories (build-path “test”)) (list # #)
> (directory-files (build-path “test” “Test2”)) (list
# #)
You can break this problem down as follows:
1. Write a simple procedure called count-files to count the number of les in a directory itself (i.e. not the subdirectories).
(count-files (build-path “test”)) ;2 (count-files (build-path “test” “Test2”)) ; 2 (count-files (build-path “test” “Test3”)) ; 0
2. Modify that procedure to call itself recursively for each subdirectory.
(count-files (build-path “test”)) ; now returns 4 (count-files (build-path “test” “Test2”)) ; still 2 (count-files (build-path “test” “Test3”)) ; still 0
You can do this by using map to recursively call count-files on each path in the list of subdirectories!
3. Finally, use foldl or apply to add up the number of les in the current directory, as well as the number
of les in all subdirectories.
Remember (map count-files …) will return a list of results, and foldl and apply take a list argument!
Question 4: Getting the size of a directory
Write a procedure called directory-size, which takes a path and returns the total size in bytes of all les in the directory and its subdirectories, recursively.
;; directory-size : Path -> Number
;; Returns the number of bytes of the given directory and all its contents, recursively.
(directory-size (build-path “test”)) ; 7177, assuming no modifications to the filesystem This will be very similar to the previous function, except instead of getting the number of les, you’re getting
the size of all the les.
Useful functions
• file-size : Path -> Number takes a path to a le, and returns its size in bytes. > (file-size (build-path “test” “Test.txt”))
;; file-size only works on files, not directories!
> (file-size (build-path “test” “Test2”)) file-size: cannot get size
path: /Users/sarah/ex7/test/Test2
system error: path refers to a directory; rktio_err=9
Question 5: Searching a directory
Write a procedure called search-directory, which takes a search string and a directory path. Return a list of paths, where each path points to a le whose name contains the given string.
As with previous functions, you need to recursively search the given directory and all its subdirectories.
;; search-directory : String, Path -> List-of-Path
;; Returns a list of paths to files within the original directory, whose
;; filenames contain the given string.
> (search-directory “foo” (build-path “test”)) (list
# #)
;; Only search filenames, NOT folder names!
> (search-directory “Test2” (build-path “test”)) ‘()
Useful functions
• path-filename : Path -> Path takes a path to a le, and returns a shortened path containing only the lename portion.
> (define file (build-path “test” “Test2” “foo.bmp”)) > file
# > (path-filename file)
# 6
• path->string : Path -> String takes a path, and returns a string version. > (path->string (build-path “some” “path” “to” “file”))
• string-contains? : String, String -> Boolean takes a query string and a string to search, and returns #true if the second string contains the query:
> (string-contains? “ack” “Racket”) #true
> (string-contains? “Racketttt” “Racket”) #false
As before, you can follow this recipe to break down your function:
1. Write search-directory to only search the les immediately contained by the original directory. Ignore subdirectories for now.
> (search-directory “foo” (build-path “test”)) (list #) ; only one file
2. Now use map to recursively call search-directory on all of the subdirectories. Note that since search-directory itself returns a List-of-Path, calling (map search-directory …) will return a List-of-List-of-Path (woah):
; (map search-directory …)
; | |__________|
; | Listof Path
; |_____________________|
; Listof Listof Path
Note that map can’t call search-directory directly, since search-directory takes two inputs and map only iterates one list. So you should use lambda to create a new one-argument procedure that calls search-directory. (Sound familiar? Think back to the homework with artist-is-versatile?!)
3. Finally, use append to merge all the lists of pathnames together. You can use (apply append …) to atten a list of lists:
;; `append` only merges lists one level deep, so passing a single list of lists ;; will just return the same thing:
> (append (list
(list 1 2)
(list 3 4))) (list (list 1 2) (list 3 4))
;; Using `apply` calls `append`, but “spreads” the list of arguments as the inputs ;; to `append`. We can use this to flatten a single list of lists:
> (apply append
(list 1 2 3 4)
(list 1 2) (list 3 4)))
Question 6: Filtering directory contents
Write a variant of search-directory called filter-directory, which takes a predicate and a path to a directory, and returns all les within that directory (and its descendants) that pass the predicate.
;; filter-directory : (Path -> Boolean), Path -> List-of-Path
;; Returns a list of paths to files within the original directory, which
;; pass the given predicate.
;; Example usage (results will vary from computer to computer):
> (filter-directory
(lambda (file-path) (< (file-size file-path) 10)) (build-path "test")) Hints • Recall that a predicate is simply a procedure that returns a boolean. • This is just a more general version of search-directory! You should be able to pass in a predicate that will make filter-directory behave exactly like search-directory. Question 7: Finding certain letypes Use filter-directory to write a procedure find-file-type, which takes a le extension such as ".jpg" and a path, and returns a list of paths to all les with that extension. ;; find-file-type: String, Path -> List-of-Path
;; Returns a list of paths to files within the original directory, which
;; have the given extension.
;; You should call `filter-directory` in your solution.
> (find-file-type “.bmp”
(build-path “test”)) (list
# #)
Useful functions
• path-has-extension? : Path, String -> Boolean tests whether a given path has a given extension (anything that starts with a .).
> (path-has-extension? (build-path “test” “Test.txt”) “.txt”) #true
> (path-has-extension? (build-path “test” “Test.txt”) “.jpg”) #false
Question 8: Finding le type storage space
Use find-file-type to write a procedure file-type-disk-usage, which takes an extension and a directory path, and returns the number of bytes used by all les within that directory and its descendants.
;; file-type-disk-usage: String, Path -> Number
;; Returns the number of bytes used by files within the original directory,
;; which have the given extension.
;; You should call `find-file-type` in your solution.
;; Example usage (results will vary by computer)
> (file-type-disk-usage “.bmp” (build-path “test”)) Hints
• Although you previously wrote a procedure called directory-size, remember that you have to use find-file-type in your solution. Before you immediately jump to replicating your logic from directory-size, think about what find-file-type returns, and how you might use that to simplify your answer!
Turning it in
1. Make sure your code is at the end of the Exercise 7.rkt le.
2. Very important: Remove any function calls at the top level of your le (i.e., not inside a
check-expect). If your le includes testing code (calls to backup!, etc.) it will CRASH in the grader,
because it will be run on a di erent set of les than you have on your hard disk.
3. Upload to the handin server as usual. Congrats!
Appendix 1: Understanding printf in the starter code
The starter code includes a function called copy-tree!
;; copy-tree! : Path, Path -> Void
which takes a from Path and a to Path, and recursively (deeply) copies the contents of from into to.
This is a modi ed version of the copy-tree procedure discussed in class. Namely, it includes the following line:
(printf “Copying file ~A to ~A~n” file to)
The printf function has the signature
;; printf : string, any … -> void
and prints the given format string to the screen (the REPL), with two twists:
• If the format string contains the magic code ~A, it replaces each ~A with the corresponding argument after the format string. For example:
(printf “~A is ~A” “Racket” “great”)
will print “Racket is great”.
• If the format string contains ~n, then printf starts a new line of output.
Note: It’s called printf and not printf! for historical reasons; printf is the name used in the original C language from the early 1970s, and it stuck.


电子邮件地址不会被公开。 必填项已用*标注