1
|
|
|
""" |
2
|
|
|
Copyright George Sibble 2018 |
3
|
|
|
""" |
4
|
1 |
|
from io import BytesIO |
5
|
1 |
|
from ..utilities import b2_url_encode, b2_url_decode, decode_error |
|
|
|
|
6
|
1 |
|
from ..b2_exceptions import B2Exception |
7
|
1 |
|
from ..api import API |
8
|
|
|
|
9
|
1 |
|
class B2File(object): |
|
|
|
|
10
|
|
|
""" |
11
|
|
|
|
12
|
|
|
""" |
13
|
1 |
|
def __init__(self, connector, parent_list, fileId, fileName, contentSha1, contentLength, contentType, |
|
|
|
|
14
|
|
|
fileInfo, action, uploadTimestamp, *args, **kwargs): |
15
|
|
|
""" |
16
|
|
|
|
17
|
|
|
:param connector: |
18
|
|
|
:param parent_list: |
19
|
|
|
:param fileId: |
20
|
|
|
:param fileName: |
21
|
|
|
:param contentSha1: |
22
|
|
|
:param contentLength: |
23
|
|
|
:param contentType: |
24
|
|
|
:param fileInfo: |
25
|
|
|
:param action: |
26
|
|
|
:param uploadTimestamp: |
27
|
|
|
:param args: |
28
|
|
|
:param kwargs: |
29
|
|
|
""" |
30
|
1 |
|
self.file_id = fileId |
31
|
|
|
# self.file_name_decoded = b2_url_decode(fileName) |
32
|
|
|
#TODO: Find out if this is necessary |
|
|
|
|
33
|
1 |
|
self.file_name = fileName |
34
|
1 |
|
self.content_sha1 = contentSha1 |
35
|
1 |
|
self.content_length = contentLength |
36
|
1 |
|
self.content_type = contentType |
37
|
1 |
|
self.file_info = fileInfo |
38
|
1 |
|
self.action = action |
39
|
1 |
|
self.uploadTimestamp = uploadTimestamp |
|
|
|
|
40
|
1 |
|
self.connector = connector |
41
|
1 |
|
self.parent_list = parent_list |
42
|
1 |
|
self.deleted = False |
43
|
|
|
|
44
|
1 |
|
def get_versions(self, limit=None): |
|
|
|
|
45
|
|
|
""" Fetch list of all versions of the current file. |
46
|
|
|
Params: |
47
|
|
|
limit: (int) Limit number of results returned (optional, default 10000) |
48
|
|
|
|
49
|
|
|
Returns: |
50
|
|
|
file_versions (list) B2FileObject of all file versions |
51
|
|
|
""" |
52
|
1 |
|
bucket_id = self.parent_list.bucket.bucket_id |
53
|
|
|
|
54
|
1 |
|
path = API.list_file_versions |
55
|
1 |
|
file_versions = [] |
56
|
1 |
|
params = { |
57
|
|
|
'bucketId': bucket_id, |
58
|
|
|
'maxFileCount': limit or 10000, |
59
|
|
|
'startFileId': self.file_id, |
60
|
|
|
'startFileName': self.file_name, |
61
|
|
|
} |
62
|
|
|
|
63
|
1 |
|
response = self.connector.make_request(path=path, method='post', params=params) |
64
|
1 |
|
if response.status_code == 200: |
65
|
1 |
|
files_json = response.json() |
66
|
1 |
|
for file_json in files_json['files']: |
67
|
1 |
|
new_file = B2File(connector=self.connector, parent_list=self, **file_json) |
68
|
1 |
|
file_versions.append(new_file) |
69
|
|
|
else: |
70
|
|
|
raise B2Exception.parse(response) |
71
|
1 |
|
return file_versions |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
74
|
1 |
|
def hide(self): |
75
|
|
|
""" Soft-delete a file (hide it from files list, but previous versions are saved.) """ |
76
|
1 |
|
path = API.delete_file |
77
|
1 |
|
params = { |
78
|
|
|
'bucketId': self.parent_list.bucket.bucket_id, |
79
|
|
|
'fileName': b2_url_encode(self.file_name) |
80
|
|
|
} |
81
|
1 |
|
response = self.connector.make_request(path=path, method='post', params=params) |
82
|
1 |
|
if response.status_code == 200: |
83
|
1 |
|
self.deleted = True |
84
|
|
|
# Delete from parent list if exists |
85
|
1 |
|
self.parent_list._files_by_name.pop(self.file_name) |
|
|
|
|
86
|
1 |
|
self.parent_list._files_by_id.pop(self.file_id) |
|
|
|
|
87
|
|
|
else: |
88
|
|
|
raise B2Exception.parse(response) |
89
|
|
|
|
90
|
|
|
|
91
|
1 |
|
def delete_all_versions(self, confirm=False): |
92
|
|
|
""" Delete completely all versions of a file. |
|
|
|
|
93
|
|
|
** NOTE THAT THIS CAN BE VERY EXPENSIVE IN TERMS OF YOUR API LIMITS ** |
94
|
|
|
Each call to delete_all_versions will result in multiple API calls: |
|
|
|
|
95
|
|
|
One API call per file version to be deleted, per file. |
96
|
|
|
1. Call '/b2_list_file_versions' to get file versions |
97
|
|
|
2. Call '/b2_delete_file_version' once for each version of the file |
98
|
|
|
|
99
|
|
|
This means: if you have 10 files with 50 versions each and call delete_all_versions, |
|
|
|
|
100
|
|
|
you will spend (10 + 1) x 50 == 550 API calls against your BackBlaze b2 API limit. |
101
|
|
|
|
102
|
|
|
** You have been warned! BE CAREFUL!!! ** |
103
|
|
|
""" |
104
|
1 |
|
print(self.delete_all_versions.__name__, self.delete_all_versions.__doc__) # Print warnings at call time. |
|
|
|
|
105
|
|
|
|
106
|
|
|
# Confirm deletion |
107
|
1 |
|
if not confirm: |
108
|
|
|
print('To call this function, use delete_all_versions(confirm=True)') |
|
|
|
|
109
|
|
|
return False |
110
|
|
|
|
111
|
1 |
|
versions = self.get_versions() |
112
|
|
|
|
113
|
1 |
|
version_count = len(versions) |
114
|
1 |
|
if not version_count > 0: |
115
|
|
|
print('No file versions') |
|
|
|
|
116
|
|
|
else: |
117
|
1 |
|
print(version_count, 'file versions') |
118
|
1 |
|
for count, v in enumerate(versions): |
|
|
|
|
119
|
1 |
|
print('deleting [{}/{}]'.format(count + 1 , version_count)) |
|
|
|
|
120
|
1 |
|
v.delete() |
121
|
|
|
|
122
|
|
|
|
123
|
1 |
|
def delete(self): |
124
|
|
|
""" Delete a file version (Does not delete entire file history: only most recent version) """ |
|
|
|
|
125
|
1 |
|
path = API.delete_file_version |
126
|
1 |
|
params = { |
127
|
|
|
'fileId': self.file_id, |
128
|
|
|
'fileName': b2_url_encode(self.file_name) |
129
|
|
|
} |
130
|
1 |
|
response = self.connector.make_request(path=path, method='post', params=params) |
131
|
1 |
|
if not response.status_code == 200: |
132
|
|
|
raise B2Exception.parse(response) |
133
|
1 |
|
self.deleted = True |
134
|
|
|
|
135
|
|
|
|
136
|
1 |
|
def download(self): |
137
|
|
|
""" Download latest file version """ |
138
|
1 |
|
response = self.connector.download_file(file_id=self.file_id) |
139
|
1 |
|
if response.status_code == 200: |
140
|
1 |
|
return BytesIO(response.content) |
141
|
|
|
else: |
142
|
|
|
raise B2Exception.parse(response) |
143
|
|
|
|
144
|
1 |
|
@property |
145
|
|
|
def url(self): |
146
|
|
|
""" Return file download URL """ |
147
|
|
|
return self.connector.download_url + '?fileId=' + self.file_id |
148
|
|
|
|