Repository: Backblaze/B2_Command_Line_Tool

Issue:
LargeFileUploadState object has no attribute get_error_message
In my backup log file I saw this line:

`b2_upload(/backup/#########.tar.gz,#########.tar.gz, 1466468375649): AttributeError("'LargeFileUploadState' object has no attribute 'get_error_message'",) 'LargeFileUploadState' object has no attribute 'get_error_message'`

No retry attempt were made and the file failed to upload.

(the ### were intentional and not the real file name)

Discussion:

Patch:
diff --git a/b2/bucket.py b/b2/bucket.py
index 9347570..6d4332a 100644
--- a/b2/bucket.py
+++ b/b2/bucket.py
@@ -34,7 +34,7 @@ class LargeFileUploadState(object):
     """
 
     def __init__(self, file_progress_listener):
-        self.lock = threading.Lock()
+        self.lock = threading.RLock()
         self.error_message = None
         self.file_progress_listener = file_progress_listener
         self.part_number_to_part_state = {}
@@ -48,6 +48,11 @@ class LargeFileUploadState(object):
         with self.lock:
             return self.error_message is not None
 
+    def get_error_message(self):
+        with self.lock:
+            assert self.has_error()
+            return self.error_message
+
     def update_part_bytes(self, bytes_delta):
         with self.lock:
             self.bytes_completed += bytes_delta


Test patch:
diff --git a/test/test_bucket.py b/test/test_bucket.py
index 766af3c..44cb2e2 100644
--- a/test/test_bucket.py
+++ b/test/test_bucket.py
@@ -18,8 +18,9 @@ import six
 
 from b2.account_info import StubAccountInfo
 from b2.api import B2Api
+from b2.bucket import LargeFileUploadState
 from b2.download_dest import DownloadDestBytes
-from b2.exception import B2Error, InvalidAuthToken, MaxRetriesExceeded
+from b2.exception import AlreadyFailed, B2Error, InvalidAuthToken, MaxRetriesExceeded
 from b2.file_version import FileVersionInfo
 from b2.part import Part
 from b2.progress import AbstractProgressListener
@@ -146,6 +147,22 @@ class TestListParts(TestCaseWithBucket):
         self.assertEqual(expected_parts, list(self.bucket.list_parts(file1.file_id, batch_size=1)))
 
 
+class TestUploadPart(TestCaseWithBucket):
+    def test_error_in_state(self):
+        file1 = self.bucket.start_large_file('file1.txt', 'text/plain', {})
+        content = six.b('hello world')
+        file_progress_listener = mock.MagicMock()
+        large_file_upload_state = LargeFileUploadState(file_progress_listener)
+        large_file_upload_state.set_error('test error')
+        try:
+            self.bucket._upload_part(
+                file1.file_id, 1, (0, 11), UploadSourceBytes(content), large_file_upload_state
+            )
+            self.fail('should have thrown')
+        except AlreadyFailed:
+            pass
+
+
 class TestListUnfinished(TestCaseWithBucket):
     def test_empty(self):
         self.assertEqual([], list(self.bucket.list_unfinished_large_files()))
diff --git a/test_b2_command_line.py b/test_b2_command_line.py
index 7cadd76..8d23678 100644
--- a/test_b2_command_line.py
+++ b/test_b2_command_line.py
@@ -319,8 +319,8 @@ def basic_test(b2_tool, bucket_name):
 
     file_to_upload = 'README.md'
 
-    with open(file_to_upload, 'rb') as f:
-        hex_sha1 = hashlib.sha1(f.read()).hexdigest()
+    hex_sha1 = hashlib.sha1(read_file(file_to_upload)).hexdigest()
+
     uploaded_a = b2_tool.should_succeed_json(
         [
             'upload_file', '--noProgress', '--quiet', bucket_name, file_to_upload, 'a'