1
|
1 /**
|
|
2 * Cloud storage plugins for Orthanc
|
|
3 * Copyright (C) 2017-2020 Osimis S.A., Belgium
|
|
4 *
|
|
5 * This program is free software: you can redistribute it and/or
|
|
6 * modify it under the terms of the GNU Affero General Public License
|
|
7 * as published by the Free Software Foundation, either version 3 of
|
|
8 * the License, or (at your option) any later version.
|
|
9 *
|
|
10 * This program is distributed in the hope that it will be useful, but
|
|
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
13 * Affero General Public License for more details.
|
|
14 *
|
|
15 * You should have received a copy of the GNU Affero General Public License
|
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17 **/
|
|
18
|
|
19 #include "GoogleStoragePlugin.h"
|
|
20
|
|
21 #include "google/cloud/storage/client.h"
|
|
22
|
|
23 // Create aliases to make the code easier to read.
|
|
24 namespace gcs = google::cloud::storage;
|
|
25
|
|
26 class Writer : public IStoragePlugin::IWriter
|
|
27 {
|
|
28 std::string path_;
|
|
29 gcs::Client client_;
|
|
30 std::string bucketName_;
|
|
31 gcs::ObjectWriteStream stream_;
|
|
32 public:
|
|
33 Writer(const std::string& bucketName, const std::string& path, gcs::Client& client)
|
|
34 : path_(path),
|
|
35 client_(client),
|
|
36 bucketName_(bucketName)
|
|
37 {
|
|
38 }
|
|
39
|
|
40 virtual ~Writer()
|
|
41 {
|
|
42 }
|
|
43
|
|
44 virtual void Write(const char* data, size_t size)
|
|
45 {
|
|
46 stream_ = client_.WriteObject(bucketName_, path_);
|
|
47
|
|
48 if (stream_)
|
|
49 {
|
|
50 stream_.write(data, size);
|
|
51 stream_.Close();
|
|
52
|
|
53 if (!stream_.metadata())
|
|
54 {
|
|
55 throw StoragePluginException("GoogleCloudStorage: error while writing file " + std::string(path_) + ": " + stream_.metadata().status().message());
|
|
56 }
|
|
57 }
|
|
58 else
|
|
59 {
|
|
60 throw StoragePluginException("GoogleCloudStorage: error while opening/writing file " + std::string(path_) + ": " + stream_.metadata().status().message());
|
|
61 }
|
|
62 }
|
|
63 };
|
|
64
|
|
65
|
|
66 class Reader : public IStoragePlugin::IReader
|
|
67 {
|
|
68 std::string path_;
|
|
69 gcs::Client client_;
|
|
70 std::string bucketName_;
|
|
71
|
|
72 public:
|
|
73 Reader(const std::string& bucketName, const std::string& path, gcs::Client& client)
|
|
74 : path_(path),
|
|
75 client_(client),
|
|
76 bucketName_(bucketName)
|
|
77 {
|
|
78 }
|
|
79
|
|
80 virtual ~Reader()
|
|
81 {
|
|
82
|
|
83 }
|
|
84 virtual size_t GetSize()
|
|
85 {
|
|
86 auto objectMetadata = client_.GetObjectMetadata(bucketName_, path_);
|
|
87
|
|
88 if (objectMetadata)
|
|
89 {
|
|
90 std::uint64_t fileSize = static_cast<int64_t>(objectMetadata->size());
|
|
91
|
|
92 return fileSize;
|
|
93 }
|
|
94 else
|
|
95 {
|
|
96 throw StoragePluginException("error while getting the size of " + std::string(path_) + ": " + objectMetadata.status().message());
|
|
97 }
|
|
98 }
|
|
99
|
|
100 virtual void Read(char* data, size_t size)
|
|
101 {
|
|
102 auto reader = client_.ReadObject(bucketName_, path_);
|
|
103
|
|
104 if (!reader)
|
|
105 {
|
|
106 throw StoragePluginException("error while opening/reading file " + std::string(path_) + ": " + reader.status().message());
|
|
107 }
|
|
108
|
|
109 reader.read(data, size);
|
|
110
|
|
111 if (!reader)
|
|
112 {
|
|
113 throw StoragePluginException("error while reading file " + std::string(path_) + ": " + reader.status().message());
|
|
114 }
|
|
115 }
|
|
116
|
|
117 };
|
|
118
|
|
119
|
|
120
|
|
121 const char* GoogleStoragePluginFactory::GetStoragePluginName()
|
|
122 {
|
|
123 return "Google Cloud Storage";
|
|
124 }
|
|
125
|
|
126 IStoragePlugin* GoogleStoragePluginFactory::CreateStoragePlugin(const OrthancPlugins::OrthancConfiguration& orthancConfig)
|
|
127 {
|
|
128 static const char* const PLUGIN_SECTION = "GoogleCloudStorage";
|
|
129 if (!orthancConfig.IsSection(PLUGIN_SECTION))
|
|
130 {
|
|
131 OrthancPlugins::LogWarning(std::string(GetStoragePluginName()) + " plugin, section missing. Plugin is not enabled.");
|
|
132 return nullptr;
|
|
133 }
|
|
134
|
|
135 OrthancPlugins::OrthancConfiguration pluginSection;
|
|
136 orthancConfig.GetSection(pluginSection, PLUGIN_SECTION);
|
|
137
|
|
138 std::string pathToGoogleCredentials;
|
|
139
|
|
140 if (!pluginSection.LookupStringValue(pathToGoogleCredentials, "ServiceAccountFile"))
|
|
141 {
|
|
142 OrthancPlugins::LogError("GoogleCloudStorage/ServiceAccountFile configuration missing. Unable to initialize plugin");
|
|
143 return nullptr;
|
|
144 }
|
|
145
|
|
146 std::string googleBucketName;
|
|
147 if (!pluginSection.LookupStringValue(googleBucketName, "BucketName"))
|
|
148 {
|
|
149 OrthancPlugins::LogError("GoogleCloudStorage/BucketName configuration missing. Unable to initialize plugin");
|
|
150 return nullptr;
|
|
151 }
|
|
152
|
|
153 // Use service account credentials from a JSON keyfile:
|
|
154 auto creds = gcs::oauth2::CreateServiceAccountCredentialsFromJsonFilePath(pathToGoogleCredentials);
|
|
155 if (!creds)
|
|
156 {
|
|
157 OrthancPlugins::LogError("GoogleCloudStorage plugin: unable to validate credentials. Check the ServiceAccountFile: " + creds.status().message());
|
|
158 return nullptr;
|
|
159 }
|
|
160
|
|
161 // Create a client to communicate with Google Cloud Storage.
|
|
162 google::cloud::StatusOr<gcs::Client> mainClient = gcs::Client(gcs::ClientOptions(*creds));
|
|
163
|
|
164 if (!mainClient)
|
|
165 {
|
|
166 OrthancPlugins::LogError("GoogleCloudStorage plugin: unable to create client: " + mainClient.status().message());
|
|
167 return nullptr;
|
|
168 }
|
|
169
|
|
170 return new GoogleStoragePlugin(googleBucketName, mainClient.value());
|
|
171 }
|
|
172
|
|
173 GoogleStoragePlugin::GoogleStoragePlugin(const std::string &bucketName, google::cloud::storage::Client& mainClient)
|
|
174 : bucketName_(bucketName),
|
|
175 mainClient_(mainClient)
|
|
176 {
|
|
177
|
|
178 }
|
|
179
|
|
180 IStoragePlugin::IWriter* GoogleStoragePlugin::GetWriterForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
181 {
|
|
182 return new Writer(bucketName_, GetPath(uuid, type, encryptionEnabled), mainClient_);
|
|
183 }
|
|
184
|
|
185 IStoragePlugin::IReader* GoogleStoragePlugin::GetReaderForObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
186 {
|
|
187 return new Reader(bucketName_, GetPath(uuid, type, encryptionEnabled), mainClient_);
|
|
188 }
|
|
189
|
|
190 void GoogleStoragePlugin::DeleteObject(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
191 {
|
|
192 gcs::Client client(mainClient_);
|
|
193
|
|
194 std::string path = GetPath(uuid, type, encryptionEnabled);
|
|
195
|
|
196 auto deletionStatus = client.DeleteObject(bucketName_, path);
|
|
197
|
|
198 if (!deletionStatus.ok())
|
|
199 {
|
|
200 throw StoragePluginException("GoogleCloudStorage: error while deleting file " + std::string(path) + ": " + deletionStatus.message());
|
|
201 }
|
|
202
|
|
203 }
|
|
204
|
|
205 std::string GoogleStoragePlugin::GetPath(const char* uuid, OrthancPluginContentType type, bool encryptionEnabled)
|
|
206 {
|
|
207 std::string path = std::string(uuid);
|
|
208
|
|
209 if (type == OrthancPluginContentType_Dicom)
|
|
210 {
|
|
211 path += ".dcm";
|
|
212 }
|
|
213 else if (type == OrthancPluginContentType_DicomAsJson)
|
|
214 {
|
|
215 path += ".json";
|
|
216 }
|
|
217 else
|
|
218 {
|
|
219 path += ".unk";
|
|
220 }
|
|
221
|
|
222 if (encryptionEnabled)
|
|
223 {
|
|
224 path += ".enc";
|
|
225 }
|
|
226 return path;
|
|
227 }
|