Skip to content

Commit

Permalink
fix backup logic, more tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd committed Jun 10, 2022
1 parent acfe530 commit 3a68d7e
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ type Existing_File_Behavior
type Error

## PRIVATE
Runs the given action which is given a file output stream and should
write the required contents to it.
Runs the `action` which is given a file output stream and should write
the required contents to it.

The handle is configured depending on the specified behavior, it may
point to a temporary file, for example. The stream may only be used while
the action is being executed and it should not be stored anywhere for
later.

The action may not be run at all in case the `Error` behavior is
The `action` may not be run at all in case the `Error` behavior is
selected.
write : File -> (Output_Stream -> Nothing) -> Nothing ! File_Not_Found | Io_Error | File_Already_Exists_Error
write file action =
Expand All @@ -44,15 +44,46 @@ type Existing_File_Behavior
Append -> file.with_output_stream [Option.Write, Option.Create, Option.Append] action
Error -> file.with_output_stream [Option.Write, Option.Create_New] action
Backup -> Panic.recover [Io_Error, File_Not_Found] <|
parent = file.parent
bak_file = parent / file.name+".bak"
go i =
new_name = file.name + ".new" + if i == 0 then "" else "." + i.to_text
new_file = parent / new_name
handle_existing_file _ = go i+1
Panic.catch File_Already_Exists_Error handler=handle_existing_file <|
Panic.rethrow <| new_file.with_output_stream [Option.Write, Option.Create_New] action
file.move_to bak_file
new_file.move_to file
go 0
handle_existing_file _ =
here.write_file_backing_up_old_one file action
## We first attempt to write the file to the original
destination, but if that files due to the file already
existing, we will run the alternative algorithm which uses a
temporary file and creates a backup.
Panic.catch File_Already_Exists_Error handler=handle_existing_file <|
Panic.rethrow <| file.with_output_stream [Option.Write, Option.Create_New] action

## PRIVATE
write_file_backing_up_old_one : File -> (Output_Stream -> Nothing) -> Nothing ! File_Not_Found | Io_Error | File_Already_Exists_Error
write_file_backing_up_old_one file action = Panic.recover [Io_Error, File_Not_Found] <|
parent = file.parent
bak_file = parent / file.name+".bak"
go i =
new_name = file.name + ".new" + if i == 0 then "" else "." + i.to_text
new_file = parent / new_name
handle_existing_file _ = go i+1
handle_write_failure panic =
## Since we were already inside of the write operation,
the file must have been created, but since we failed, we need to clean it up.
new_file.delete
Panic.throw panic.payload.cause
Panic.catch File_Already_Exists_Error handler=handle_existing_file <|
Panic.catch Internal_Write_Operation_Failed handler=handle_write_failure <|
Panic.rethrow <|
new_file.with_output_stream [Option.Write, Option.Create_New] output_stream->
Panic.catch Any (action output_stream) caught_panic->
Panic.throw (Internal_Write_Operation_Failed caught_panic)
## We ignore the file not found error, because it means that there
is no file to back-up. This may also be caused by someone
removing the original file during the time when we have been
writing the new one to the temporary location. There is nothing
to back-up anymore, but this is not a failure, so it can be
safely ignored.
Panic.catch File_Not_Found handler=(_->Nothing) <|
Panic.rethrow <| file.move_to bak_file
Panic.rethrow <| new_file.move_to file
go 0


## PRIVATE
type Internal_Write_Operation_Failed (cause : Caught_Panic)
35 changes: 27 additions & 8 deletions test/Tests/src/System/File_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ spec =
f = Enso_Project.data / "short.txt"
f.delete_if_exists
f.exists.should_be_false
"Cup".write f
"Cup".write f on_existing_file=Existing_File_Behavior.Overwrite
f.with_input_stream stream->
stream.read_byte.should_equal 67
stream.read_byte.should_equal 117
Expand Down Expand Up @@ -137,10 +137,10 @@ spec =
f = transient / "work.txt"
f.delete_if_exists
f.exists.should_be_false
"line 1!".write f on_existing_file=Existing_File_Behavior.Overwrite
"line 1!".write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing
f.exists.should_be_true
f.read_text.should_equal "line 1!"
'line 2!'.write f on_existing_file=Existing_File_Behavior.Overwrite
'line 2!'.write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing
f.read_text.should_equal 'line 2!'
f.delete
f.exists.should_be_false
Expand All @@ -149,7 +149,7 @@ spec =
f = transient / "work.txt"
f.delete_if_exists
f.exists.should_be_false
"line 1!".write f on_existing_file=Existing_File_Behavior.Error
"line 1!".write f on_existing_file=Existing_File_Behavior.Error . should_equal Nothing
f.exists.should_be_true
f.read_text.should_equal "line 1!"
"line 2!".write f on_existing_file=Existing_File_Behavior.Error . should_fail_with File_Already_Exists_Error
Expand All @@ -161,8 +161,9 @@ spec =
f = transient / "work.txt"
f.delete_if_exists
f.exists.should_be_false
"line 1!".write f
f.exists.should_be_true
"line 1!".write f . should_equal Nothing
if f.exists.not then
Test.fail "The file should have been created."
f.read_text.should_equal "line 1!"

bak = transient / "work.txt.bak"
Expand All @@ -178,10 +179,11 @@ spec =
"new content".write n on_existing_file=Existing_File_Behavior.Overwrite
n3.delete_if_exists

"line 2!".write f
"line 2!".write f . should_equal Nothing
f.read_text.should_equal 'line 2!'
bak.read_text.should_equal 'line 1!'
n3.exists.should_be_false
if n3.exists then
Test.fail "The temporary file should have been cleaned up."
written_news.each n->
n.read_text . should_equal "new content"
[f, bak, n0, n1, n2, n4].each .delete
Expand All @@ -206,6 +208,23 @@ spec =
if new_file.exists then
Test.fail "The temporary file should have been cleaned up."

f.delete
result2 = Panic.recover Illegal_State_Error <|
Existing_File_Behavior.Backup.write f output_stream->
output_stream.write_bytes "foo".utf_8
Panic.throw (Illegal_State_Error "baz")
output_stream.write_bytes "bar".utf_8
result2.should_fail_with Illegal_State_Error
result2.catch.message . should_equal "baz"
if f.exists.not then
Test.fail "Since we were writing to the original destination, the partially written file should have been preserved even upon failure."
f.read_text . should_equal "foo"
if bak_file.exists then
Test.fail "If the operation failed, we shouldn't have even created the backup."
if new_file.exists then
Test.fail "The temporary file should have been cleaned up."
f.delete

Test.group "folder operations" <|
resolve files =
base = Enso_Project.data
Expand Down

0 comments on commit 3a68d7e

Please sign in to comment.