libqicore-api  release-2.5.3-2016-11-18
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
fileoperation.hxx
Go to the documentation of this file.
1 #pragma once
2 #ifndef _QICORE_FILEOPERATION_HPP_
3 #define _QICORE_FILEOPERATION_HPP_
4 
5 #include <memory>
6 #include <boost/filesystem/fstream.hpp>
7 #include <boost/filesystem/operations.hpp>
8 #include <qi/detail/warn_push_ignore_deprecated.hpp>
9 
10 namespace qi
11 {
18  {
19  public:
23  virtual ~FileOperation()
24  {
25  auto task = std::move(_task);
26  if (task)
27  {
28  task->promise.future().cancel();
29  }
30  }
31 
32  // Move only
33  FileOperation(const FileOperation&) = delete;
34  FileOperation& operator=(const FileOperation&) = delete;
35 
40  FileOperation(FileOperation&& other) // TODO VS2015 C++11: = default;
41  : _task(std::move(other._task))
42  {}
43 
48  FileOperation& operator=(FileOperation&& other) // TODO VS2015 C++11: = default;
49  {
50  _task = std::move(other._task);
51  return *this;
52  }
53 
61  qi::Future<void> start()
62  {
63  if (!_task)
64  {
65  throw std::runtime_error{ "Tried to start an invalid FileOperation" };
66  }
67 
68  if (_task->isLaunched.swap(true))
69  {
70  throw std::runtime_error{ "Called FileOperation::start() more than once!" };
71  }
72 
73  return _task->run();
74  }
75 
87  qi::Future<void> detach()
88  {
89  boost::shared_ptr<Task> sharedTask = std::move(_task);
90 
91  if (!sharedTask)
92  {
93  throw std::runtime_error("Called FileOperation::detach() but no task is owned!");
94  }
95 
96  if (!sharedTask->isLaunched._value)
97  {
98  throw std::runtime_error("Called FileOperation::detach() but task was not started!");
99  }
100 
101  auto future = sharedTask->promise.future();
102  future.connect([sharedTask](const Future<void>&){}); // keep the task alive until it ends
103  return future;
104  }
105 
107  auto operator()() -> decltype(start()) { return start(); }
108 
112  ProgressNotifierPtr notifier() const { return _task ? _task->localNotifier : ProgressNotifierPtr{}; }
113 
119  bool isValid() const { return _task ? true : false; }
120 
122  explicit operator bool() const { return isValid(); }
123 
124  protected:
125  struct Task
126  : public boost::enable_shared_from_this<Task>
127  {
128  Task(FilePtr file)
129  : sourceFile{ std::move(file) }
130  , fileSize{ sourceFile->size() }
131  , promise{ PromiseNoop<void> }
133  , remoteNotifier{ sourceFile->operationProgress() }
134  , isRemoteDeprecated(sourceFile.metaObject().findMethod("read").empty())
135  {
136  }
137 
138  virtual ~Task() = default;
139 
140  qi::Future<void> run()
141  {
142  localNotifier->reset();
143  isRemoteDeprecated ? remoteNotifier->_reset() : remoteNotifier->reset();
144  localNotifier->notifyRunning();
145  isRemoteDeprecated ? remoteNotifier->_notifyRunning() : remoteNotifier->notifyRunning();
146  start();
147  return promise.future();
148  }
149 
150  void finish()
151  {
152  promise.setValue(0);
153  localNotifier->notifyFinished();
154  isRemoteDeprecated ? remoteNotifier->_notifyFinished() : remoteNotifier->notifyFinished();
155  }
156 
157  void fail(const std::string& errorMessage)
158  {
159  promise.setError(errorMessage);
160  localNotifier->notifyFailed();
161  isRemoteDeprecated ? remoteNotifier->_notifyFailed() : remoteNotifier->notifyFailed();
162  }
163 
164  void cancel()
165  {
166  promise.setCanceled();
167  localNotifier->notifyCanceled();
168  isRemoteDeprecated ? remoteNotifier->_notifyCanceled() : remoteNotifier->notifyCanceled();
169  }
170 
171  void notifyProgressed(double newProgress)
172  {
173  localNotifier->notifyProgressed(newProgress);
174  isRemoteDeprecated ? remoteNotifier->_notifyProgressed(newProgress) : remoteNotifier->notifyProgressed(newProgress);
175  }
176 
177  virtual void start() = 0;
178 
179  qi::Atomic<bool> isLaunched{ false };
181  const std::streamsize fileSize;
182  Promise<void> promise;
185  const bool isRemoteDeprecated;
186  };
187 
188  using TaskPtr = boost::shared_ptr<Task>;
189 
190  explicit FileOperation(TaskPtr task)
191  : _task{ std::move(task) }
192  {
193  if (!_task)
194  throw std::runtime_error("FileOperation requires a non-null task on constrution.");
195  }
196 
197  private:
198  TaskPtr _task;
199  };
200 
202 
204  using FileOperationPtr = Object<FileOperation>;
205 
208  : public FileOperation
209  {
210  public:
217  FileCopyToLocal(qi::FilePtr file, qi::Path localPath)
218  : FileOperation(boost::make_shared<Task>(std::move(file), std::move(localPath)))
219  {
220  }
221 
222  private:
223  class Task
224  : public FileOperation::Task
225  {
226  public:
227  Task(FilePtr sourceFile, qi::Path localFilePath)
228  : FileOperation::Task(std::move(sourceFile))
229  , localPath(std::move(localFilePath))
230  {
231  }
232 
233  void start() override
234  {
235  if (makeLocalFile())
236  {
237  fetchData();
238  }
239  }
240 
241  void stop()
242  {
243  localFile.close();
244  finish();
245  }
246 
247  bool makeLocalFile()
248  {
249  localFile.open(localPath.bfsPath(), std::ios::out | std::ios::binary);
250  if (!localFile.is_open())
251  {
252  fail("Failed to create local file copy.");
253  return false;
254  }
255  return true;
256  }
257 
258  void write(Buffer buffer)
259  {
260  assert(localFile.is_open());
261  localFile.write(static_cast<const char*>(buffer.data()), buffer.totalSize());
262  bytesWritten += buffer.totalSize();
263  assert(fileSize >= bytesWritten);
264 
265  const double progress = static_cast<double>(bytesWritten) / static_cast<double>(fileSize);
266  notifyProgressed(progress);
267  }
268 
269  void fetchData()
270  {
271  static const size_t ARBITRARY_BYTES_TO_READ_PER_CYCLE = 512 * 1024;
272  auto myself = shared_from_this();
273 
274  const auto readFuncName = isRemoteDeprecated ? "_read" : "read";
275 
276  sourceFile.async<Buffer>(readFuncName, bytesWritten, ARBITRARY_BYTES_TO_READ_PER_CYCLE)
277  .connect([this, myself](Future<Buffer> futureBuffer)
278  {
279  if (futureBuffer.hasError())
280  {
281  fail(futureBuffer.error());
282  clearLocalFile();
283  return;
284  }
285  if (promise.isCancelRequested())
286  {
287  clearLocalFile();
288  cancel();
289  return;
290  }
291 
292  write(futureBuffer.value());
293  if (bytesWritten < fileSize)
294  fetchData();
295  else
296  stop();
297  }
298  );
299  }
300 
301  void clearLocalFile()
302  {
303  localFile.close();
304  boost::filesystem::remove(localPath);
305  }
306 
307  boost::filesystem::ofstream localFile;
308  std::streamsize bytesWritten = 0;
309  const qi::Path localPath;
310  };
311 
312  };
313 
321  QICORE_API FutureSync<void> copyToLocal(FilePtr file, Path localPath);
322 }
323 
324 #include <qi/detail/warn_pop_ignore_deprecated.hpp>
325 #endif
qi::Future< void > start()
Object< FileOperation > FileOperationPtr
Pointer to a file operation with sharing semantic.
FileOperation(FileOperation &&other)
virtual ~Task()=default
ProgressNotifierPtr notifier() const
qi::Object< File > FilePtr
Pointer to a file with shared/remote semantic.
Definition: file.hpp:213
FutureSync< void > copyToLocal(FilePtr file, Path localPath)
boost::shared_ptr< Task > TaskPtr
qi::Object< ProgressNotifier > ProgressNotifierPtr
Pointer to a ProgressNotifier with shared/remote semantic.
Definition: file.hpp:114
void fail(const std::string &errorMessage)
FileOperation(const FileOperation &)=delete
FileOperation & operator=(const FileOperation &)=delete
qi::Future< void > run()
virtual void start()=0
const ProgressNotifierPtr localNotifier
ProgressNotifierPtr createProgressNotifier(Future< void > operationFuture={})
const ProgressNotifierPtr remoteNotifier
void notifyProgressed(double newProgress)
FileCopyToLocal(qi::FilePtr file, qi::Path localPath)
auto operator()() -> decltype(start())
Call operator: calls start()
FileOperation(TaskPtr task)
qi::Atomic< bool > isLaunched
const std::streamsize fileSize
bool isValid() const
virtual ~FileOperation()
FileOperation & operator=(FileOperation &&other)
qi::Future< void > detach()
#define QICORE_API
Definition: api.hpp:14