~michihenning/storage-framework/increase-timeout

« back to all changes in this revision

Viewing changes to src/qt/client/internal/local_client/ItemImpl.cpp

  • Committer: Michi Henning
  • Date: 2016-08-10 06:45:41 UTC
  • mfrom: (10.10.15 more-coverage)
  • Revision ID: michi.henning@canonical.com-20160810064541-ty9vzh6dff8jgfpw
Merged lp:~michihenning/storage-framework/more-coverage:
Improved coverage. Better error reporting/handling.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
#include <unity/storage/internal/safe_strerror.h>
22
22
#include <unity/storage/qt/client/Account.h>
23
23
#include <unity/storage/qt/client/Exceptions.h>
24
 
#include <unity/storage/qt/client/internal/make_future.h>
25
24
#include <unity/storage/qt/client/internal/local_client/AccountImpl.h>
26
25
#include <unity/storage/qt/client/internal/local_client/FileImpl.h>
27
26
#include <unity/storage/qt/client/internal/local_client/RootImpl.h>
28
 
#include <unity/storage/qt/client/internal/local_client/tmpfile-prefix.h>
 
27
#include <unity/storage/qt/client/internal/local_client/storage_exception.h>
 
28
#include <unity/storage/qt/client/internal/local_client/tmpfile_prefix.h>
 
29
#include <unity/storage/qt/client/internal/make_future.h>
29
30
 
30
31
#include <boost/algorithm/string/predicate.hpp>
31
32
#pragma GCC diagnostic push
54
55
 
55
56
ItemImpl::ItemImpl(QString const& identity, ItemType type)
56
57
    : ItemBase(identity, type)
57
 
    , deleted_(false)
58
58
{
59
59
    assert(!identity.isEmpty());
60
60
    auto path = boost::filesystem::canonical(identity.toStdString());
64
64
 
65
65
ItemImpl::~ItemImpl() = default;
66
66
 
67
 
QString ItemImpl::name() const
68
 
{
69
 
    lock_guard<mutex> guard(mutex_);
70
 
 
71
 
    if (deleted_)
72
 
    {
73
 
        throw deleted_ex("Item::name()");
74
 
    }
75
 
    return name_;
76
 
}
77
 
 
78
67
QString ItemImpl::etag() const
79
68
{
80
 
    lock_guard<mutex> guard(mutex_);
 
69
    lock_guard<decltype(mutex_)> guard(mutex_);
81
70
 
82
 
    if (deleted_)
83
 
    {
84
 
        throw deleted_ex("Item::etag()");
85
 
    }
 
71
    throw_if_destroyed("Item::etag()");
86
72
    return etag_;
87
73
}
88
74
 
89
75
QVariantMap ItemImpl::metadata() const
90
76
{
91
 
    lock_guard<mutex> guard(mutex_);
 
77
    lock_guard<decltype(mutex_)> guard(mutex_);
92
78
 
93
 
    if (deleted_)
94
 
    {
95
 
        throw deleted_ex("Item::metadata()");
96
 
    }
 
79
    throw_if_destroyed("Item::metadata()");
97
80
    return metadata_;
98
81
}
99
82
 
100
83
QDateTime ItemImpl::last_modified_time() const
101
84
{
102
 
    lock_guard<mutex> guard(mutex_);
 
85
    lock_guard<decltype(mutex_)> guard(mutex_);
103
86
 
104
 
    if (deleted_)
105
 
    {
106
 
        throw deleted_ex("Item::last_modified_time()");
107
 
    }
 
87
    throw_if_destroyed("Item::last_modified_time()");
108
88
    return modified_time_;
109
89
}
110
90
 
111
 
namespace
112
 
{
113
 
 
114
 
using namespace boost::filesystem;
115
 
 
116
 
void copy_recursively(path const& source, path const& target)
117
 
{
118
 
    auto s = status(source);
119
 
    if (is_regular_file(s))
120
 
    {
121
 
        copy_file(source, target);
122
 
        return;
123
 
    }
124
 
    else if (is_directory(s))
125
 
    {
126
 
        copy_directory(source, target);  // Poorly named in boost; this creates the target dir without recursion
127
 
        for (directory_iterator it(source); it != directory_iterator(); ++it)
128
 
        {
129
 
            path source_entry = it->path();
130
 
            path target_entry = target;
131
 
            target_entry /= source_entry.filename();
132
 
            copy_recursively(source_entry, target_entry);
133
 
        }
134
 
    }
135
 
    else
136
 
    {
137
 
        // Ignore everything that's not a directory or file.
138
 
    }
139
 
}
140
 
 
141
 
}  // namespace
142
 
 
143
91
QFuture<shared_ptr<Item>> ItemImpl::copy(shared_ptr<Folder> const& new_parent, QString const& new_name)
144
92
{
 
93
    if (!new_parent)
 
94
    {
 
95
        QString msg = "Item::copy(): new_parent cannot be nullptr";
 
96
        return internal::make_exceptional_future<shared_ptr<Item>>(InvalidArgumentException(msg));
 
97
    }
 
98
    auto new_parent_impl = dynamic_pointer_cast<FolderImpl>(new_parent->p_);
 
99
 
 
100
    lock(mutex_, new_parent_impl->mutex_);
 
101
    lock_guard<decltype(mutex_)> this_guard(mutex_, std::adopt_lock);
 
102
    lock_guard<decltype(mutex_)> other_guard(new_parent_impl->mutex_, adopt_lock);
 
103
 
 
104
    try
 
105
    {
 
106
        throw_if_destroyed("Item::copy()");
 
107
        new_parent_impl->throw_if_destroyed("Item::copy()");
 
108
    }
 
109
    catch (StorageException const& e)
 
110
    {
 
111
        return internal::make_exceptional_future<shared_ptr<Item>>(e);
 
112
    }
 
113
 
145
114
    auto This = dynamic_pointer_cast<ItemImpl>(shared_from_this());  // Keep this item alive while the lambda is alive.
146
115
    auto copy = [This, new_parent, new_name]() -> Item::SPtr
147
116
    {
148
117
        auto new_parent_impl = dynamic_pointer_cast<FolderImpl>(new_parent->p_);
149
118
 
150
119
        lock(This->mutex_, new_parent_impl->mutex_);
151
 
        lock_guard<mutex> this_guard(This->mutex_, std::adopt_lock);
152
 
        lock_guard<mutex> other_guard(new_parent_impl->mutex_, adopt_lock);
153
 
 
154
 
        if (This->deleted_ || new_parent_impl->deleted_)
155
 
        {
156
 
            throw This->deleted_ex("Item::copy");
157
 
        }
158
 
 
159
 
        if (This->root()->account() != new_parent->root()->account())
 
120
        lock_guard<decltype(mutex_)> this_guard(This->mutex_, std::adopt_lock);
 
121
        lock_guard<decltype(mutex_)> other_guard(new_parent_impl->mutex_, adopt_lock);
 
122
 
 
123
        This->throw_if_destroyed("Item::copy()");
 
124
        new_parent_impl->throw_if_destroyed("Item::copy()");
 
125
 
 
126
        // TODO: This needs to deeply compare account identity because the client may have refreshed the accounts list.
 
127
        if (This->root()->account() != new_parent->root()->account())  // Throws if account or runtime were destroyed.
160
128
        {
161
129
            // Can't do cross-account copy.
162
 
            QString msg = QString("Item::copy(): Source (") + This->name_ + ") and target ("
 
130
            QString msg = QString("Item::copy(): source (") + This->name_ + ") and target ("
163
131
                          + new_name + ") must belong to the same account";
164
132
            throw LogicException(msg);
165
133
        }
175
143
            target_path /= sanitized_name;
176
144
            if (is_reserved_path(target_path))
177
145
            {
178
 
                QString msg = "Item::copy(): names beginning with " + QString(TMPFILE_PREFIX) + " are reserved";
 
146
                QString msg = "Item::copy(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved";
179
147
                throw InvalidArgumentException(msg);
180
148
            }
181
149
 
 
150
            if (exists(target_path))
 
151
            {
 
152
                QString msg = "Item::copy(): item with name \"" + new_name + "\" exists already";
 
153
                throw ExistsException(msg, This->identity_, This->name_);
 
154
            }
 
155
 
182
156
            if (This->type_ == ItemType::file)
183
157
            {
184
158
                copy_file(source_path, target_path);
185
159
                return FileImpl::make_file(QString::fromStdString(target_path.native()), new_parent_impl->root_);
186
160
            }
187
161
 
188
 
            if (exists(target_path))
189
 
            {
190
 
                QString msg = "Item::copy(): item with name \"" + new_name + "\" exists already";
191
 
                throw ExistsException(msg, This->identity_, This->name_);
192
 
            }
193
 
 
194
162
            // For recursive copy, we create a temporary directory in lieu of target_path and recursively copy
195
163
            // everything into the temporary directory. This ensures that we don't invalidate directory iterators
196
164
            // by creating things while we are iterating, potentially getting trapped in an infinite loop.
199
167
            create_directories(tmp_path);
200
168
            for (directory_iterator it(source_path); it != directory_iterator(); ++it)
201
169
            {
202
 
                if (tmp_path.compare(canonical(it->path())) == 0)
 
170
                if (is_reserved_path(it->path()))
203
171
                {
204
172
                    continue;  // Don't recurse into the temporary directory
205
173
                }
209
177
                    path source_entry = it->path();
210
178
                    path target_entry = tmp_path;
211
179
                    target_entry /= source_entry.filename();
212
 
                    copy_recursively(source_entry, target_entry);
 
180
                    ItemImpl::copy_recursively(source_entry, target_entry);
213
181
                }
214
182
            }
215
183
            rename(tmp_path, target_path);
216
184
            return FolderImpl::make_folder(QString::fromStdString(target_path.native()), new_parent_impl->root_);
217
185
        }
218
 
        catch (std::exception const& e)
 
186
        catch (std::exception const&)
219
187
        {
220
 
            throw ResourceException(QString("Item::copy(): ") + e.what());
 
188
            throw_storage_exception("Item::copy()", current_exception());
221
189
        }
222
190
    };
223
191
    return QtConcurrent::run(copy);
225
193
 
226
194
QFuture<shared_ptr<Item>> ItemImpl::move(shared_ptr<Folder> const& new_parent, QString const& new_name)
227
195
{
 
196
    if (!new_parent)
 
197
    {
 
198
        QString msg = "Item::move(): new_parent cannot be nullptr";
 
199
        return internal::make_exceptional_future<shared_ptr<Item>>(InvalidArgumentException(msg));
 
200
    }
 
201
    auto new_parent_impl = dynamic_pointer_cast<FolderImpl>(new_parent->p_);
 
202
 
 
203
    lock(mutex_, new_parent_impl->mutex_);
 
204
    lock_guard<decltype(mutex_)> this_guard(mutex_, std::adopt_lock);
 
205
    lock_guard<decltype(mutex_)> other_guard(new_parent_impl->mutex_, adopt_lock);
 
206
 
 
207
    try
 
208
    {
 
209
        throw_if_destroyed("Item::move()");
 
210
        new_parent_impl->throw_if_destroyed("Item::move()");
 
211
    }
 
212
    catch (StorageException const& e)
 
213
    {
 
214
        return internal::make_exceptional_future<shared_ptr<Item>>(e);
 
215
    }
 
216
 
228
217
    auto This = dynamic_pointer_cast<ItemImpl>(shared_from_this());  // Keep this item alive while the lambda is alive.
229
218
    auto move = [This, new_parent, new_name]() -> Item::SPtr
230
219
    {
231
220
        auto new_parent_impl = dynamic_pointer_cast<FolderImpl>(new_parent->p_);
232
221
 
233
222
        lock(This->mutex_, new_parent_impl->mutex_);
234
 
        lock_guard<mutex> this_guard(This->mutex_, std::adopt_lock);
235
 
        lock_guard<mutex> other_guard(new_parent_impl->mutex_, adopt_lock);
236
 
 
237
 
        if (This->deleted_ || new_parent_impl->deleted_)
238
 
        {
239
 
            throw This->deleted_ex("Item::move");
240
 
        }
241
 
 
242
 
        if (This->root()->account() != new_parent->root()->account())
 
223
        lock_guard<decltype(mutex_)> this_guard(This->mutex_, std::adopt_lock);
 
224
        lock_guard<decltype(mutex_)> other_guard(new_parent_impl->mutex_, adopt_lock);
 
225
 
 
226
        This->throw_if_destroyed("Item::move()");
 
227
        new_parent_impl->throw_if_destroyed("Item::move()");
 
228
 
 
229
        // TODO: This needs to deeply compare account identity because the client may have refreshed the accounts list.
 
230
        if (This->root()->account() != new_parent->root()->account())  // Throws if account or runtime were destroyed.
243
231
        {
244
232
            // Can't do cross-account move.
245
 
            QString msg = QString("Item::move(): Source (") + This->name_ + ") and target ("
 
233
            QString msg = QString("Item::move(): source (") + This->name_ + ") and target ("
246
234
                          + new_name + ") must belong to the same account";
247
235
            throw LogicException(msg);
248
236
        }
249
237
        if (This->type_ == ItemType::root)
250
238
        {
251
239
            // Can't move a root.
252
 
            throw LogicException("Item::move(): Cannot move root folder");
 
240
            throw LogicException("Item::move(): cannot move root folder");
253
241
        }
254
242
 
255
243
        try
265
253
            }
266
254
            if (is_reserved_path(target_path))
267
255
            {
268
 
                QString msg = "Item::move(): names beginning with " + QString(TMPFILE_PREFIX) + " are reserved";
 
256
                QString msg = "Item::move(): names beginning with \"" + QString(TMPFILE_PREFIX) + "\" are reserved";
269
257
                throw InvalidArgumentException(msg);
270
258
            }
271
259
            rename(This->native_identity().toStdString(), target_path);
276
264
            }
277
265
            return FileImpl::make_file(QString::fromStdString(target_path.native()), new_parent_impl->root_);
278
266
        }
279
 
        catch (std::exception const& e)
 
267
        catch (std::exception const&)
280
268
        {
281
 
            throw ResourceException(QString("Item::move(): ") + e.what());
 
269
            throw_storage_exception(QString("Item::move(): "), current_exception());
282
270
        }
283
271
    };
284
272
    return QtConcurrent::run(move);
286
274
 
287
275
QFuture<QVector<Folder::SPtr>> ItemImpl::parents() const
288
276
{
289
 
    lock_guard<mutex> guard(mutex_);
 
277
    lock_guard<decltype(mutex_)> guard(mutex_);
290
278
 
291
 
    QFutureInterface<QVector<Folder::SPtr>> qf;
292
 
    if (deleted_)
 
279
    try
293
280
    {
294
 
        return make_exceptional_future<QVector<Folder::SPtr>>(deleted_ex("Item::parents()"));
 
281
        throw_if_destroyed("Item::parents()");
295
282
    }
296
 
 
297
 
    Root::SPtr root = root_.lock();
298
 
    if (!root)
 
283
    catch (StorageException const& e)
299
284
    {
300
 
        return make_exceptional_future<QVector<Folder::SPtr>>(RuntimeDestroyedException("Item::parents()"));
 
285
        return internal::make_exceptional_future<QVector<Folder::SPtr>>(e);
301
286
    }
302
287
 
303
288
    using namespace boost::filesystem;
306
291
    path p = native_identity().toStdString();
307
292
    QString parent_path = QString::fromStdString(p.parent_path().native());
308
293
 
 
294
    auto root = root_.lock();
309
295
    QVector<Folder::SPtr> results;
310
296
    if (parent_path != root->native_identity())
311
297
    {
312
 
        results.append(FolderImpl::make_folder(parent_path, root_));
 
298
        results.append(FolderImpl::make_folder(parent_path, root));
313
299
    }
314
300
    else
315
301
    {
320
306
 
321
307
QVector<QString> ItemImpl::parent_ids() const
322
308
{
323
 
    lock_guard<mutex> guard(mutex_);
 
309
    lock_guard<decltype(mutex_)> guard(mutex_);
324
310
 
325
 
    if (deleted_)
326
 
    {
327
 
        throw deleted_ex("Item::parent_ids()");
328
 
    }
 
311
    throw_if_destroyed("Item::parent_ids()");
329
312
 
330
313
    using namespace boost::filesystem;
331
314
 
340
323
 
341
324
QFuture<void> ItemImpl::delete_item()
342
325
{
 
326
    lock_guard<decltype(mutex_)> guard(mutex_);
 
327
 
 
328
    try
 
329
    {
 
330
        throw_if_destroyed("Item::delete_item()");
 
331
    }
 
332
    catch (StorageException const& e)
 
333
    {
 
334
        return internal::make_exceptional_future(e);
 
335
    }
 
336
 
343
337
    auto This = dynamic_pointer_cast<ItemImpl>(shared_from_this());  // Keep this item alive while the lambda is alive.
344
338
    auto destroy = [This]()
345
339
    {
346
 
        lock_guard<mutex> guard(This->mutex_);
347
 
 
348
 
        if (This->deleted_)
349
 
        {
350
 
            throw This->deleted_ex("Item::delete_item()");
351
 
        }
352
 
 
 
340
        lock_guard<decltype(mutex_)> guard(This->mutex_);
 
341
 
 
342
        This->throw_if_destroyed("Item::delete_item()");
353
343
        try
354
344
        {
355
345
            boost::filesystem::remove_all(This->native_identity().toStdString());
356
346
            This->deleted_ = true;
357
347
        }
358
 
        catch (std::exception const& e)
 
348
        catch (std::exception const&)
359
349
        {
360
 
            throw ResourceException(QString("Item::delete_item(): ") + e.what());
 
350
            throw_storage_exception(QString("Item::delete_item()"), current_exception());
361
351
        }
362
352
    };
363
353
    return QtConcurrent::run(destroy);
365
355
 
366
356
QDateTime ItemImpl::creation_time() const
367
357
{
 
358
    lock_guard<decltype(mutex_)> guard(mutex_);
 
359
 
 
360
    throw_if_destroyed("Item::creation_time()");
368
361
    return QDateTime();
369
362
}
370
363
 
371
364
MetadataMap ItemImpl::native_metadata() const
372
365
{
 
366
    lock_guard<decltype(mutex_)> guard(mutex_);
 
367
 
 
368
    throw_if_destroyed("Item::native_metadata()");
373
369
    return MetadataMap();
374
370
}
375
371
 
383
379
    }
384
380
 
385
381
    lock(mutex_, other_impl->mutex_);
386
 
    lock_guard<mutex> this_guard(mutex_, std::adopt_lock);
387
 
    lock_guard<mutex> other_guard(other_impl->mutex_, adopt_lock);
 
382
    lock_guard<decltype(mutex_)> this_guard(mutex_, std::adopt_lock);
 
383
    lock_guard<decltype(mutex_)> other_guard(other_impl->mutex_, adopt_lock);
388
384
 
389
385
    if (deleted_ || other_impl->deleted_)
390
386
    {
395
391
 
396
392
void ItemImpl::set_timestamps() noexcept
397
393
{
398
 
    lock_guard<mutex> guard(mutex_);
 
394
    lock_guard<decltype(mutex_)> guard(mutex_);
399
395
 
400
396
    string id = identity_.toStdString();
401
397
    // Use nano-second resolution for the ETag, if the file system supports it.
412
408
 
413
409
bool ItemImpl::has_conflict() const noexcept
414
410
{
415
 
    lock_guard<mutex> guard(mutex_);
 
411
    lock_guard<decltype(mutex_)> guard(mutex_);
416
412
 
417
413
    string id = identity_.toStdString();
418
414
    struct stat st;
425
421
    return etag_ != new_etag;
426
422
}
427
423
 
428
 
unique_lock<mutex> ItemImpl::get_lock()
429
 
{
430
 
    return unique_lock<mutex>(mutex_);
431
 
}
432
 
 
433
424
// Throw if name contains more than one path component.
434
425
// Otherwise, return the relative path for the name.
435
426
// This is to make sure that calling, say, create_file()
465
456
    return boost::starts_with(filename, TMPFILE_PREFIX);
466
457
}
467
458
 
468
 
DeletedException ItemImpl::deleted_ex(QString const& method) const noexcept
 
459
void ItemImpl::copy_recursively(boost::filesystem::path const& source, boost::filesystem::path const& target)
469
460
{
470
 
    QString msg = method + ": " + identity_ + " was deleted previously";
471
 
    return DeletedException(msg, identity_, name_);
 
461
    using namespace boost::filesystem;
 
462
 
 
463
    if (is_reserved_path(source))
 
464
    {
 
465
        return;  // Don't copy temporary directories.
 
466
    }
 
467
 
 
468
    auto s = status(source);
 
469
    if (is_regular_file(s))
 
470
    {
 
471
        copy_file(source, target);
 
472
    }
 
473
    else if (is_directory(s))
 
474
    {
 
475
        copy_directory(source, target);  // Poorly named in boost; this creates the target dir without recursion
 
476
        for (directory_iterator it(source); it != directory_iterator(); ++it)
 
477
        {
 
478
            path source_entry = it->path();
 
479
            path target_entry = target;
 
480
            target_entry /= source_entry.filename();
 
481
            copy_recursively(source_entry, target_entry);
 
482
        }
 
483
    }
 
484
    else
 
485
    {
 
486
        // Ignore everything that's not a directory or file.
 
487
    }
472
488
}
473
489
 
474
490
}  // namespace local_client