local_file_operations.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "remoting/host/file_transfer/local_file_operations.h"
  5. #include <cstdint>
  6. #include "base/bind.h"
  7. #include "base/files/file_proxy.h"
  8. #include "base/files/file_util.h"
  9. #include "base/logging.h"
  10. #include "base/memory/ptr_util.h"
  11. #include "base/path_service.h"
  12. #include "base/sequence_checker.h"
  13. #include "base/task/sequenced_task_runner.h"
  14. #include "base/task/task_runner_util.h"
  15. #include "base/task/thread_pool.h"
  16. #include "base/threading/sequenced_task_runner_handle.h"
  17. #include "build/build_config.h"
  18. #include "remoting/base/result.h"
  19. #include "remoting/host/file_transfer/ensure_user.h"
  20. #include "remoting/host/file_transfer/file_chooser.h"
  21. #include "remoting/host/file_transfer/get_desktop_directory.h"
  22. #include "remoting/protocol/file_transfer_helpers.h"
  23. #include "third_party/abseil-cpp/absl/types/optional.h"
  24. namespace remoting {
  25. namespace {
  26. constexpr char kTempFileExtension[] = ".part";
  27. remoting::protocol::FileTransfer_Error_Type FileErrorToResponseErrorType(
  28. base::File::Error file_error) {
  29. switch (file_error) {
  30. case base::File::FILE_ERROR_ACCESS_DENIED:
  31. return remoting::protocol::FileTransfer_Error_Type_PERMISSION_DENIED;
  32. case base::File::FILE_ERROR_NO_SPACE:
  33. return remoting::protocol::FileTransfer_Error_Type_OUT_OF_DISK_SPACE;
  34. default:
  35. return remoting::protocol::FileTransfer_Error_Type_IO_ERROR;
  36. }
  37. }
  38. scoped_refptr<base::SequencedTaskRunner> CreateFileTaskRunner() {
  39. #if BUILDFLAG(IS_WIN)
  40. // On Windows, we use user impersonation to write files as the currently
  41. // logged-in user, while the process as a whole runs as SYSTEM. Since user
  42. // impersonation is per-thread on Windows, we need a dedicated thread to
  43. // ensure that no other code is accidentally run with the wrong privileges.
  44. return base::ThreadPool::CreateSingleThreadTaskRunner(
  45. {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
  46. base::SingleThreadTaskRunnerThreadMode::DEDICATED);
  47. #else
  48. return base::ThreadPool::CreateSequencedTaskRunner(
  49. {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
  50. #endif
  51. }
  52. class LocalFileReader : public FileOperations::Reader {
  53. public:
  54. explicit LocalFileReader(
  55. scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
  56. LocalFileReader(const LocalFileReader&) = delete;
  57. LocalFileReader& operator=(const LocalFileReader&) = delete;
  58. ~LocalFileReader() override;
  59. // FileOperations::Reader implementation.
  60. void Open(OpenCallback callback) override;
  61. void ReadChunk(std::size_t size, ReadCallback callback) override;
  62. const base::FilePath& filename() const override;
  63. std::uint64_t size() const override;
  64. FileOperations::State state() const override;
  65. private:
  66. void OnEnsureUserResult(OpenCallback callback,
  67. protocol::FileTransferResult<absl::monostate> result);
  68. void OnFileChooserResult(OpenCallback callback, FileChooser::Result result);
  69. void OnOpenResult(OpenCallback callback, base::File::Error error);
  70. void OnGetInfoResult(OpenCallback callback,
  71. base::File::Error error,
  72. const base::File::Info& info);
  73. void OnReadResult(ReadCallback callback,
  74. base::File::Error error,
  75. const char* data,
  76. int bytes_read);
  77. void SetState(FileOperations::State state);
  78. base::FilePath filename_;
  79. std::uint64_t size_ = 0;
  80. std::uint64_t offset_ = 0;
  81. FileOperations::State state_ = FileOperations::kCreated;
  82. scoped_refptr<base::SequencedTaskRunner> ui_task_runner_;
  83. scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
  84. std::unique_ptr<FileChooser> file_chooser_;
  85. absl::optional<base::FileProxy> file_proxy_;
  86. SEQUENCE_CHECKER(sequence_checker_);
  87. base::WeakPtrFactory<LocalFileReader> weak_ptr_factory_{this};
  88. };
  89. class LocalFileWriter : public FileOperations::Writer {
  90. public:
  91. LocalFileWriter();
  92. LocalFileWriter(const LocalFileWriter&) = delete;
  93. LocalFileWriter& operator=(const LocalFileWriter&) = delete;
  94. ~LocalFileWriter() override;
  95. // FileOperations::Writer implementation.
  96. void Open(const base::FilePath& filename, Callback callback) override;
  97. void WriteChunk(std::vector<std::uint8_t> data, Callback callback) override;
  98. void Close(Callback callback) override;
  99. FileOperations::State state() const override;
  100. private:
  101. void Cancel();
  102. // Callbacks for Open().
  103. void OnGetTargetDirectoryResult(
  104. base::FilePath filename,
  105. Callback callback,
  106. protocol::FileTransferResult<base::FilePath> target_directory_result);
  107. void CreateTempFile(Callback callback, base::FilePath temp_filepath);
  108. void OnCreateResult(Callback callback, base::File::Error error);
  109. void OnWriteResult(std::vector<std::uint8_t> data,
  110. Callback callback,
  111. base::File::Error error,
  112. int bytes_written);
  113. // Callbacks for Close().
  114. void OnCloseResult(Callback callback, base::File::Error error);
  115. void MoveToDestination(Callback callback,
  116. base::FilePath destination_filepath);
  117. void OnMoveResult(Callback callback, bool success);
  118. void SetState(FileOperations::State state);
  119. FileOperations::State state_ = FileOperations::kCreated;
  120. base::FilePath destination_filepath_;
  121. base::FilePath temp_filepath_;
  122. std::uint64_t bytes_written_ = 0;
  123. scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
  124. absl::optional<base::FileProxy> file_proxy_;
  125. SEQUENCE_CHECKER(sequence_checker_);
  126. base::WeakPtrFactory<LocalFileWriter> weak_ptr_factory_{this};
  127. };
  128. LocalFileReader::LocalFileReader(
  129. scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
  130. : ui_task_runner_(std::move(ui_task_runner)) {}
  131. LocalFileReader::~LocalFileReader() = default;
  132. void LocalFileReader::Open(OpenCallback callback) {
  133. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  134. DCHECK_EQ(FileOperations::kCreated, state_);
  135. SetState(FileOperations::kBusy);
  136. file_task_runner_ = CreateFileTaskRunner();
  137. file_proxy_.emplace(file_task_runner_.get());
  138. base::PostTaskAndReplyWithResult(
  139. file_task_runner_.get(), FROM_HERE, base::BindOnce(&EnsureUserContext),
  140. base::BindOnce(&LocalFileReader::OnEnsureUserResult,
  141. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  142. }
  143. void LocalFileReader::ReadChunk(std::size_t size, ReadCallback callback) {
  144. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  145. DCHECK_EQ(FileOperations::kReady, state_);
  146. SetState(FileOperations::kBusy);
  147. file_proxy_->Read(
  148. offset_, size,
  149. base::BindOnce(&LocalFileReader::OnReadResult,
  150. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  151. }
  152. const base::FilePath& LocalFileReader::filename() const {
  153. return filename_;
  154. }
  155. uint64_t LocalFileReader::size() const {
  156. return size_;
  157. }
  158. FileOperations::State LocalFileReader::state() const {
  159. return state_;
  160. }
  161. void LocalFileReader::OnEnsureUserResult(
  162. FileOperations::Reader::OpenCallback callback,
  163. protocol::FileTransferResult<absl::monostate> result) {
  164. if (!result) {
  165. SetState(FileOperations::kFailed);
  166. std::move(callback).Run(std::move(result.error()));
  167. return;
  168. }
  169. file_chooser_ = FileChooser::Create(
  170. ui_task_runner_,
  171. base::BindOnce(&LocalFileReader::OnFileChooserResult,
  172. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  173. file_chooser_->Show();
  174. }
  175. void LocalFileReader::OnFileChooserResult(OpenCallback callback,
  176. FileChooser::Result result) {
  177. file_chooser_.reset();
  178. if (!result) {
  179. SetState(FileOperations::kFailed);
  180. std::move(callback).Run(std::move(result.error()));
  181. return;
  182. }
  183. filename_ = result->BaseName();
  184. file_proxy_->CreateOrOpen(
  185. *result, base::File::FLAG_OPEN | base::File::FLAG_READ,
  186. base::BindOnce(&LocalFileReader::OnOpenResult,
  187. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  188. }
  189. void LocalFileReader::OnOpenResult(OpenCallback callback,
  190. base::File::Error error) {
  191. if (error != base::File::FILE_OK) {
  192. SetState(FileOperations::kFailed);
  193. std::move(callback).Run(protocol::MakeFileTransferError(
  194. FROM_HERE, FileErrorToResponseErrorType(error), error));
  195. return;
  196. }
  197. file_proxy_->GetInfo(base::BindOnce(&LocalFileReader::OnGetInfoResult,
  198. weak_ptr_factory_.GetWeakPtr(),
  199. std::move(callback)));
  200. }
  201. void LocalFileReader::OnGetInfoResult(OpenCallback callback,
  202. base::File::Error error,
  203. const base::File::Info& info) {
  204. if (error != base::File::FILE_OK) {
  205. SetState(FileOperations::kFailed);
  206. std::move(callback).Run(protocol::MakeFileTransferError(
  207. FROM_HERE, FileErrorToResponseErrorType(error), error));
  208. return;
  209. }
  210. size_ = info.size;
  211. SetState(FileOperations::kReady);
  212. std::move(callback).Run(kSuccessTag);
  213. }
  214. void LocalFileReader::OnReadResult(ReadCallback callback,
  215. base::File::Error error,
  216. const char* data,
  217. int bytes_read) {
  218. if (error != base::File::FILE_OK) {
  219. SetState(FileOperations::kFailed);
  220. std::move(callback).Run(protocol::MakeFileTransferError(
  221. FROM_HERE, FileErrorToResponseErrorType(error), error));
  222. return;
  223. }
  224. offset_ += bytes_read;
  225. SetState(bytes_read > 0 ? FileOperations::kReady : FileOperations::kComplete);
  226. // The read buffer is provided and owned by FileProxy, so there's no way to
  227. // avoid a copy, here.
  228. std::move(callback).Run(std::vector<std::uint8_t>(data, data + bytes_read));
  229. }
  230. void LocalFileReader::SetState(FileOperations::State state) {
  231. switch (state) {
  232. case FileOperations::kCreated:
  233. NOTREACHED(); // Can never return to initial state.
  234. break;
  235. case FileOperations::kReady:
  236. DCHECK_EQ(FileOperations::kBusy, state_);
  237. break;
  238. case FileOperations::kBusy:
  239. DCHECK(state_ == FileOperations::kCreated ||
  240. state_ == FileOperations::kReady);
  241. break;
  242. case FileOperations::kComplete:
  243. DCHECK_EQ(FileOperations::kBusy, state_);
  244. break;
  245. case FileOperations::kFailed:
  246. // Any state can change to kFailed.
  247. break;
  248. }
  249. state_ = state;
  250. }
  251. LocalFileWriter::LocalFileWriter() {}
  252. LocalFileWriter::~LocalFileWriter() {
  253. Cancel();
  254. }
  255. void LocalFileWriter::Open(const base::FilePath& filename, Callback callback) {
  256. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  257. DCHECK_EQ(FileOperations::kCreated, state_);
  258. SetState(FileOperations::kBusy);
  259. file_task_runner_ = CreateFileTaskRunner();
  260. file_proxy_.emplace(file_task_runner_.get());
  261. base::PostTaskAndReplyWithResult(
  262. file_task_runner_.get(), FROM_HERE, base::BindOnce([] {
  263. return EnsureUserContext().AndThen(
  264. [](absl::monostate) { return GetDesktopDirectory(); });
  265. }),
  266. base::BindOnce(&LocalFileWriter::OnGetTargetDirectoryResult,
  267. weak_ptr_factory_.GetWeakPtr(), filename,
  268. std::move(callback)));
  269. }
  270. void LocalFileWriter::WriteChunk(std::vector<std::uint8_t> data,
  271. Callback callback) {
  272. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  273. DCHECK_EQ(FileOperations::kReady, state_);
  274. SetState(FileOperations::kBusy);
  275. // TODO(rkjnsn): Under what circumstances can posting the task fail? Is it
  276. // worth checking for? If so, what should we do in that case,
  277. // given that callback is moved into the task and not returned
  278. // on error?
  279. // Ensure buffer pointer is obtained before data is moved.
  280. const std::uint8_t* buffer = data.data();
  281. const std::size_t size = data.size();
  282. file_proxy_->Write(bytes_written_, reinterpret_cast<const char*>(buffer),
  283. size,
  284. base::BindOnce(&LocalFileWriter::OnWriteResult,
  285. weak_ptr_factory_.GetWeakPtr(),
  286. std::move(data), std::move(callback)));
  287. }
  288. void LocalFileWriter::Close(Callback callback) {
  289. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  290. DCHECK_EQ(FileOperations::kReady, state_);
  291. SetState(FileOperations::kBusy);
  292. file_proxy_->Close(base::BindOnce(&LocalFileWriter::OnCloseResult,
  293. weak_ptr_factory_.GetWeakPtr(),
  294. std::move(callback)));
  295. }
  296. void LocalFileWriter::Cancel() {
  297. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  298. if (state_ == FileOperations::kCreated ||
  299. state_ == FileOperations::kComplete ||
  300. state_ == FileOperations::kFailed) {
  301. return;
  302. }
  303. // Ensure we don't receive further callbacks.
  304. weak_ptr_factory_.InvalidateWeakPtrs();
  305. // Drop FileProxy, which will close the underlying file on the file
  306. // sequence after any possible pending operation is complete.
  307. file_proxy_.reset();
  308. // And finally, queue deletion of the temp file.
  309. if (!temp_filepath_.empty()) {
  310. file_task_runner_->PostTask(FROM_HERE,
  311. base::GetDeleteFileCallback(temp_filepath_));
  312. }
  313. SetState(FileOperations::kFailed);
  314. }
  315. FileOperations::State LocalFileWriter::state() const {
  316. return state_;
  317. }
  318. void LocalFileWriter::OnGetTargetDirectoryResult(
  319. base::FilePath filename,
  320. Callback callback,
  321. protocol::FileTransferResult<base::FilePath> target_directory) {
  322. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  323. if (!target_directory) {
  324. LOG(ERROR) << "Failed to get target directory.";
  325. SetState(FileOperations::kFailed);
  326. std::move(callback).Run(std::move(target_directory.error()));
  327. return;
  328. }
  329. destination_filepath_ =
  330. target_directory.success().Append(filename.BaseName());
  331. // Don't store in temp_filepath_ until we have the final path to make sure
  332. // Cancel can never delete the wrong file.
  333. base::FilePath temp_filepath =
  334. destination_filepath_.AddExtensionASCII(kTempFileExtension);
  335. PostTaskAndReplyWithResult(
  336. file_task_runner_.get(), FROM_HERE,
  337. base::BindOnce(&base::GetUniquePath, temp_filepath),
  338. base::BindOnce(&LocalFileWriter::CreateTempFile,
  339. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  340. }
  341. void LocalFileWriter::CreateTempFile(Callback callback,
  342. base::FilePath temp_filepath) {
  343. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  344. if (temp_filepath.empty()) {
  345. LOG(ERROR) << "Failed to get unique path number.";
  346. SetState(FileOperations::kFailed);
  347. std::move(callback).Run(protocol::MakeFileTransferError(
  348. FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
  349. return;
  350. }
  351. temp_filepath_ = std::move(temp_filepath);
  352. // FLAG_WIN_SHARE_DELETE allows the file to be marked as deleted on Windows
  353. // while the handle is still open. (Other OS's allow this by default.) This
  354. // allows Cancel to clean up the temporary file even if there are writes
  355. // pending.
  356. file_proxy_->CreateOrOpen(
  357. temp_filepath_,
  358. base::File::FLAG_CREATE | base::File::FLAG_WRITE |
  359. base::File::FLAG_WIN_SHARE_DELETE,
  360. base::BindOnce(&LocalFileWriter::OnCreateResult,
  361. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  362. }
  363. void LocalFileWriter::OnCreateResult(Callback callback,
  364. base::File::Error error) {
  365. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  366. if (error != base::File::FILE_OK) {
  367. LOG(ERROR) << "Creating temp file failed with error: " << error;
  368. SetState(FileOperations::kFailed);
  369. std::move(callback).Run(protocol::MakeFileTransferError(
  370. FROM_HERE, FileErrorToResponseErrorType(error), error));
  371. return;
  372. }
  373. SetState(FileOperations::kReady);
  374. // Now that the temp file has been created successfully, we could lock it
  375. // using base::File::Lock(), but this would not prevent the file from being
  376. // deleted. When the file is deleted, WriteChunk() will continue to write to
  377. // the file as if the file was still there, and an error will occur when
  378. // calling base::Move() to move the temp file. Chrome exhibits the same
  379. // behavior with its downloads.
  380. std::move(callback).Run(kSuccessTag);
  381. }
  382. void LocalFileWriter::OnWriteResult(std::vector<std::uint8_t> data,
  383. Callback callback,
  384. base::File::Error error,
  385. int bytes_written) {
  386. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  387. if (error != base::File::FILE_OK) {
  388. LOG(ERROR) << "Write failed with error: " << error;
  389. Cancel();
  390. std::move(callback).Run(protocol::MakeFileTransferError(
  391. FROM_HERE, FileErrorToResponseErrorType(error), error));
  392. return;
  393. }
  394. SetState(FileOperations::kReady);
  395. bytes_written_ += bytes_written;
  396. // bytes_written should never be negative if error is FILE_OK.
  397. if (static_cast<std::size_t>(bytes_written) != data.size()) {
  398. // Write already makes a "best effort" to write all of the data, so this
  399. // probably means that an error occurred. Unfortunately, the only way to
  400. // find out what went wrong is to try again.
  401. // TODO(rkjnsn): Would it be better just to return a generic error, here?
  402. WriteChunk(
  403. std::vector<std::uint8_t>(data.begin() + bytes_written, data.end()),
  404. std::move(callback));
  405. return;
  406. }
  407. std::move(callback).Run(kSuccessTag);
  408. }
  409. void LocalFileWriter::OnCloseResult(Callback callback,
  410. base::File::Error error) {
  411. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  412. if (error != base::File::FILE_OK) {
  413. LOG(ERROR) << "Close failed with error: " << error;
  414. Cancel();
  415. std::move(callback).Run(protocol::MakeFileTransferError(
  416. FROM_HERE, FileErrorToResponseErrorType(error), error));
  417. return;
  418. }
  419. base::PostTaskAndReplyWithResult(
  420. file_task_runner_.get(), FROM_HERE,
  421. base::BindOnce(&base::GetUniquePath, destination_filepath_),
  422. base::BindOnce(&LocalFileWriter::MoveToDestination,
  423. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  424. }
  425. void LocalFileWriter::MoveToDestination(Callback callback,
  426. base::FilePath destination_filepath) {
  427. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  428. if (destination_filepath.empty()) {
  429. LOG(ERROR) << "Failed to get unique path number.";
  430. SetState(FileOperations::kFailed);
  431. std::move(callback).Run(protocol::MakeFileTransferError(
  432. FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
  433. return;
  434. }
  435. destination_filepath_ = std::move(destination_filepath);
  436. PostTaskAndReplyWithResult(
  437. file_task_runner_.get(), FROM_HERE,
  438. base::BindOnce(&base::Move, temp_filepath_, destination_filepath_),
  439. base::BindOnce(&LocalFileWriter::OnMoveResult,
  440. weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  441. }
  442. void LocalFileWriter::OnMoveResult(Callback callback, bool success) {
  443. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  444. if (success) {
  445. SetState(FileOperations::kComplete);
  446. std::move(callback).Run(kSuccessTag);
  447. } else {
  448. LOG(ERROR) << "Failed to move file to final destination.";
  449. Cancel();
  450. std::move(callback).Run(protocol::MakeFileTransferError(
  451. FROM_HERE, protocol::FileTransfer_Error_Type_IO_ERROR));
  452. }
  453. }
  454. void LocalFileWriter::SetState(FileOperations::State state) {
  455. switch (state) {
  456. case FileOperations::kCreated:
  457. NOTREACHED(); // Can never return to initial state.
  458. break;
  459. case FileOperations::kReady:
  460. DCHECK(state_ == FileOperations::kBusy);
  461. break;
  462. case FileOperations::kBusy:
  463. DCHECK(state_ == FileOperations::kCreated ||
  464. state_ == FileOperations::kReady);
  465. break;
  466. case FileOperations::kComplete:
  467. DCHECK(state_ == FileOperations::kBusy);
  468. break;
  469. case FileOperations::kFailed:
  470. // Any state can change to kFailed.
  471. break;
  472. }
  473. state_ = state;
  474. }
  475. } // namespace
  476. LocalFileOperations::LocalFileOperations(
  477. scoped_refptr<base::SequencedTaskRunner> ui_task_runner)
  478. : ui_task_runner_(std::move(ui_task_runner)) {}
  479. LocalFileOperations::~LocalFileOperations() = default;
  480. std::unique_ptr<FileOperations::Reader> LocalFileOperations::CreateReader() {
  481. return std::make_unique<LocalFileReader>(ui_task_runner_);
  482. }
  483. std::unique_ptr<FileOperations::Writer> LocalFileOperations::CreateWriter() {
  484. return std::make_unique<LocalFileWriter>();
  485. }
  486. } // namespace remoting