Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: chrome/browser/chromeos/file_system_provider/service_unittest.cc

Issue 1148293006: Rename service/service_factory of file_system_provider and launcher_search_provider (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: lsp Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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
5 #include "chrome/browser/chromeos/file_system_provider/service.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/files/file.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "chrome/browser/chromeos/file_system_provider/fake_provided_file_system .h"
15 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
16 #include "chrome/browser/chromeos/file_system_provider/observer.h"
17 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info .h"
18 #include "chrome/browser/chromeos/file_system_provider/registry_interface.h"
19 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
20 #include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
21 #include "chrome/common/extensions/api/file_system_provider_capabilities/file_sy stem_provider_capabilities_handler.h"
22 #include "chrome/test/base/testing_browser_process.h"
23 #include "chrome/test/base/testing_pref_service_syncable.h"
24 #include "chrome/test/base/testing_profile.h"
25 #include "chrome/test/base/testing_profile_manager.h"
26 #include "components/user_prefs/user_prefs.h"
27 #include "content/public/test/test_browser_thread_bundle.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/manifest_constants.h"
31 #include "storage/browser/fileapi/external_mount_points.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33
34 namespace chromeos {
35 namespace file_system_provider {
36 namespace {
37
38 const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
39 const char kDisplayName[] = "Camera Pictures";
40
41 // The dot in the file system ID is there in order to check that saving to
42 // preferences works correctly. File System ID is used as a key in
43 // a base::DictionaryValue, so it has to be stored without path expansion.
44 const char kFileSystemId[] = "camera/pictures/id .!@#$%^&*()_+";
45
46 // Utility observer, logging events from file_system_provider::Service.
47 class LoggingObserver : public Observer {
48 public:
49 class Event {
50 public:
51 Event(const ProvidedFileSystemInfo& file_system_info,
52 MountContext context,
53 base::File::Error error)
54 : file_system_info_(file_system_info),
55 context_(context),
56 error_(error) {}
57 ~Event() {}
58
59 const ProvidedFileSystemInfo& file_system_info() const {
60 return file_system_info_;
61 }
62 MountContext context() const { return context_; }
63 base::File::Error error() const { return error_; }
64
65 private:
66 ProvidedFileSystemInfo file_system_info_;
67 MountContext context_;
68 base::File::Error error_;
69 };
70
71 LoggingObserver() {}
72 virtual ~LoggingObserver() {}
73
74 // file_system_provider::Observer overrides.
75 void OnProvidedFileSystemMount(const ProvidedFileSystemInfo& file_system_info,
76 MountContext context,
77 base::File::Error error) override {
78 mounts.push_back(Event(file_system_info, context, error));
79 }
80
81 void OnProvidedFileSystemUnmount(
82 const ProvidedFileSystemInfo& file_system_info,
83 base::File::Error error) override {
84 // TODO(mtomasz): Split these events, as mount context doesn't make sense
85 // for unmounting.
86 unmounts.push_back(Event(file_system_info, MOUNT_CONTEXT_USER, error));
87 }
88
89 std::vector<Event> mounts;
90 std::vector<Event> unmounts;
91
92 DISALLOW_COPY_AND_ASSIGN(LoggingObserver);
93 };
94
95 // Fake implementation of the registry, since it's already tested separately.
96 // For simplicity it can remember at most only one file system.
97 class FakeRegistry : public RegistryInterface {
98 public:
99 FakeRegistry() {}
100 ~FakeRegistry() override {}
101
102 // RegistryInterface overrides.
103 void RememberFileSystem(const ProvidedFileSystemInfo& file_system_info,
104 const Watchers& watchers) override {
105 file_system_info_.reset(new ProvidedFileSystemInfo(file_system_info));
106 watchers_.reset(new Watchers(watchers));
107 }
108
109 void ForgetFileSystem(const std::string& extension_id,
110 const std::string& file_system_id) override {
111 if (!file_system_info_.get() || !watchers_.get())
112 return;
113 if (file_system_info_->extension_id() == extension_id &&
114 file_system_info_->file_system_id() == file_system_id) {
115 file_system_info_.reset();
116 watchers_.reset();
117 }
118 }
119
120 scoped_ptr<RestoredFileSystems> RestoreFileSystems(
121 const std::string& extension_id) override {
122 scoped_ptr<RestoredFileSystems> result(new RestoredFileSystems);
123
124 if (file_system_info_.get() && watchers_.get()) {
125 RestoredFileSystem restored_file_system;
126 restored_file_system.extension_id = file_system_info_->extension_id();
127
128 MountOptions options;
129 options.file_system_id = file_system_info_->file_system_id();
130 options.display_name = file_system_info_->display_name();
131 options.writable = file_system_info_->writable();
132 options.supports_notify_tag = file_system_info_->supports_notify_tag();
133 restored_file_system.options = options;
134 restored_file_system.watchers = *watchers_.get();
135
136 result->push_back(restored_file_system);
137 }
138
139 return result;
140 }
141
142 void UpdateWatcherTag(const ProvidedFileSystemInfo& file_system_info,
143 const Watcher& watcher) override {
144 ASSERT_TRUE(watchers_.get());
145 const Watchers::iterator it =
146 watchers_->find(WatcherKey(watcher.entry_path, watcher.recursive));
147 ASSERT_NE(watchers_->end(), it);
148 it->second.last_tag = watcher.last_tag;
149 }
150
151 ProvidedFileSystemInfo* const file_system_info() const {
152 return file_system_info_.get();
153 }
154 Watchers* const watchers() const { return watchers_.get(); }
155
156 private:
157 scoped_ptr<ProvidedFileSystemInfo> file_system_info_;
158 scoped_ptr<Watchers> watchers_;
159
160 DISALLOW_COPY_AND_ASSIGN(FakeRegistry);
161 };
162
163 // Creates a fake extension with the specified |extension_id|.
164 scoped_refptr<extensions::Extension> CreateFakeExtension(
165 const std::string& extension_id) {
166 base::DictionaryValue manifest;
167 std::string error;
168 manifest.SetStringWithoutPathExpansion(extensions::manifest_keys::kVersion,
169 "1.0.0.0");
170 manifest.SetStringWithoutPathExpansion(extensions::manifest_keys::kName,
171 "unused");
172 return extensions::Extension::Create(base::FilePath(),
173 extensions::Manifest::UNPACKED,
174 manifest,
175 extensions::Extension::NO_FLAGS,
176 extension_id,
177 &error);
178 }
179
180 } // namespace
181
182 class FileSystemProviderServiceTest : public testing::Test {
183 protected:
184 FileSystemProviderServiceTest() : profile_(NULL) {}
185
186 ~FileSystemProviderServiceTest() override {}
187
188 void SetUp() override {
189 profile_manager_.reset(
190 new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
191 ASSERT_TRUE(profile_manager_->SetUp());
192 profile_ = profile_manager_->CreateTestingProfile("test-user@example.com");
193 user_manager_ = new FakeChromeUserManager();
194 user_manager_->AddUser(profile_->GetProfileUserName());
195 user_manager_enabler_.reset(new ScopedUserManagerEnabler(user_manager_));
196 extension_registry_.reset(new extensions::ExtensionRegistry(profile_));
197
198 service_.reset(new Service(profile_, extension_registry_.get()));
199 service_->SetFileSystemFactoryForTesting(
200 base::Bind(&FakeProvidedFileSystem::Create));
201 extension_ = CreateFakeExtension(kExtensionId);
202
203 registry_ = new FakeRegistry;
204 // Passes ownership to the service instance.
205 service_->SetRegistryForTesting(make_scoped_ptr(registry_));
206
207 fake_watcher_.entry_path = base::FilePath(FILE_PATH_LITERAL("/a/b/c"));
208 fake_watcher_.recursive = true;
209 fake_watcher_.last_tag = "hello-world";
210 }
211
212 content::TestBrowserThreadBundle thread_bundle_;
213 scoped_ptr<TestingProfileManager> profile_manager_;
214 TestingProfile* profile_;
215 FakeChromeUserManager* user_manager_;
216 scoped_ptr<ScopedUserManagerEnabler> user_manager_enabler_;
217 scoped_ptr<extensions::ExtensionRegistry> extension_registry_;
218 scoped_ptr<Service> service_;
219 scoped_refptr<extensions::Extension> extension_;
220 FakeRegistry* registry_; // Owned by Service.
221 Watcher fake_watcher_;
222 };
223
224 TEST_F(FileSystemProviderServiceTest, MountFileSystem) {
225 LoggingObserver observer;
226 service_->AddObserver(&observer);
227
228 EXPECT_EQ(base::File::FILE_OK,
229 service_->MountFileSystem(
230 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
231
232 ASSERT_EQ(1u, observer.mounts.size());
233 EXPECT_EQ(kExtensionId, observer.mounts[0].file_system_info().extension_id());
234 EXPECT_EQ(kFileSystemId,
235 observer.mounts[0].file_system_info().file_system_id());
236 base::FilePath expected_mount_path =
237 util::GetMountPath(profile_, kExtensionId, kFileSystemId);
238 EXPECT_EQ(expected_mount_path.AsUTF8Unsafe(),
239 observer.mounts[0].file_system_info().mount_path().AsUTF8Unsafe());
240 EXPECT_EQ(kDisplayName, observer.mounts[0].file_system_info().display_name());
241 EXPECT_FALSE(observer.mounts[0].file_system_info().writable());
242 EXPECT_FALSE(observer.mounts[0].file_system_info().supports_notify_tag());
243 EXPECT_EQ(base::File::FILE_OK, observer.mounts[0].error());
244 EXPECT_EQ(MOUNT_CONTEXT_USER, observer.mounts[0].context());
245 ASSERT_EQ(0u, observer.unmounts.size());
246
247 std::vector<ProvidedFileSystemInfo> file_system_info_list =
248 service_->GetProvidedFileSystemInfoList();
249 ASSERT_EQ(1u, file_system_info_list.size());
250
251 service_->RemoveObserver(&observer);
252 }
253
254 TEST_F(FileSystemProviderServiceTest,
255 MountFileSystem_WritableAndSupportsNotifyTag) {
256 LoggingObserver observer;
257 service_->AddObserver(&observer);
258
259 MountOptions options(kFileSystemId, kDisplayName);
260 options.writable = true;
261 options.supports_notify_tag = true;
262 EXPECT_EQ(base::File::FILE_OK,
263 service_->MountFileSystem(kExtensionId, options));
264
265 ASSERT_EQ(1u, observer.mounts.size());
266 EXPECT_TRUE(observer.mounts[0].file_system_info().writable());
267 EXPECT_TRUE(observer.mounts[0].file_system_info().supports_notify_tag());
268 ASSERT_EQ(0u, observer.unmounts.size());
269 std::vector<ProvidedFileSystemInfo> file_system_info_list =
270 service_->GetProvidedFileSystemInfoList();
271 ASSERT_EQ(1u, file_system_info_list.size());
272
273 service_->RemoveObserver(&observer);
274 }
275
276 TEST_F(FileSystemProviderServiceTest, MountFileSystem_UniqueIds) {
277 LoggingObserver observer;
278 service_->AddObserver(&observer);
279
280 EXPECT_EQ(base::File::FILE_OK,
281 service_->MountFileSystem(
282 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
283 EXPECT_EQ(base::File::FILE_ERROR_EXISTS,
284 service_->MountFileSystem(
285 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
286
287 ASSERT_EQ(2u, observer.mounts.size());
288 EXPECT_EQ(base::File::FILE_OK, observer.mounts[0].error());
289 EXPECT_EQ(base::File::FILE_ERROR_EXISTS, observer.mounts[1].error());
290
291 std::vector<ProvidedFileSystemInfo> file_system_info_list =
292 service_->GetProvidedFileSystemInfoList();
293 ASSERT_EQ(1u, file_system_info_list.size());
294
295 service_->RemoveObserver(&observer);
296 }
297
298 TEST_F(FileSystemProviderServiceTest, MountFileSystem_StressTest) {
299 LoggingObserver observer;
300 service_->AddObserver(&observer);
301
302 const size_t kMaxFileSystems = 16;
303 for (size_t i = 0; i < kMaxFileSystems; ++i) {
304 const std::string file_system_id =
305 std::string("test-") + base::IntToString(i);
306 EXPECT_EQ(base::File::FILE_OK,
307 service_->MountFileSystem(
308 kExtensionId, MountOptions(file_system_id, kDisplayName)));
309 }
310 ASSERT_EQ(kMaxFileSystems, observer.mounts.size());
311
312 // The next file system is out of limit, and registering it should fail.
313 EXPECT_EQ(base::File::FILE_ERROR_TOO_MANY_OPENED,
314 service_->MountFileSystem(
315 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
316
317 ASSERT_EQ(kMaxFileSystems + 1, observer.mounts.size());
318 EXPECT_EQ(base::File::FILE_ERROR_TOO_MANY_OPENED,
319 observer.mounts[kMaxFileSystems].error());
320
321 std::vector<ProvidedFileSystemInfo> file_system_info_list =
322 service_->GetProvidedFileSystemInfoList();
323 ASSERT_EQ(kMaxFileSystems, file_system_info_list.size());
324
325 service_->RemoveObserver(&observer);
326 }
327
328 TEST_F(FileSystemProviderServiceTest, UnmountFileSystem) {
329 LoggingObserver observer;
330 service_->AddObserver(&observer);
331
332 EXPECT_EQ(base::File::FILE_OK,
333 service_->MountFileSystem(
334 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
335 ASSERT_EQ(1u, observer.mounts.size());
336
337 EXPECT_EQ(base::File::FILE_OK,
338 service_->UnmountFileSystem(kExtensionId, kFileSystemId,
339 Service::UNMOUNT_REASON_USER));
340 ASSERT_EQ(1u, observer.unmounts.size());
341 EXPECT_EQ(base::File::FILE_OK, observer.unmounts[0].error());
342
343 EXPECT_EQ(kExtensionId,
344 observer.unmounts[0].file_system_info().extension_id());
345 EXPECT_EQ(kFileSystemId,
346 observer.unmounts[0].file_system_info().file_system_id());
347
348 std::vector<ProvidedFileSystemInfo> file_system_info_list =
349 service_->GetProvidedFileSystemInfoList();
350 ASSERT_EQ(0u, file_system_info_list.size());
351
352 service_->RemoveObserver(&observer);
353 }
354
355 TEST_F(FileSystemProviderServiceTest, UnmountFileSystem_OnExtensionUnload) {
356 LoggingObserver observer;
357 service_->AddObserver(&observer);
358
359 EXPECT_EQ(base::File::FILE_OK,
360 service_->MountFileSystem(
361 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
362 ASSERT_EQ(1u, observer.mounts.size());
363
364 // Directly call the observer's method.
365 service_->OnExtensionUnloaded(
366 profile_,
367 extension_.get(),
368 extensions::UnloadedExtensionInfo::REASON_DISABLE);
369
370 ASSERT_EQ(1u, observer.unmounts.size());
371 EXPECT_EQ(base::File::FILE_OK, observer.unmounts[0].error());
372
373 EXPECT_EQ(kExtensionId,
374 observer.unmounts[0].file_system_info().extension_id());
375 EXPECT_EQ(kFileSystemId,
376 observer.unmounts[0].file_system_info().file_system_id());
377
378 std::vector<ProvidedFileSystemInfo> file_system_info_list =
379 service_->GetProvidedFileSystemInfoList();
380 ASSERT_EQ(0u, file_system_info_list.size());
381
382 service_->RemoveObserver(&observer);
383 }
384
385 TEST_F(FileSystemProviderServiceTest, UnmountFileSystem_WrongExtensionId) {
386 LoggingObserver observer;
387 service_->AddObserver(&observer);
388
389 const std::string kWrongExtensionId = "helloworldhelloworldhelloworldhe";
390
391 EXPECT_EQ(base::File::FILE_OK,
392 service_->MountFileSystem(
393 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
394 ASSERT_EQ(1u, observer.mounts.size());
395 ASSERT_EQ(1u, service_->GetProvidedFileSystemInfoList().size());
396
397 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
398 service_->UnmountFileSystem(kWrongExtensionId, kFileSystemId,
399 Service::UNMOUNT_REASON_USER));
400 ASSERT_EQ(1u, observer.unmounts.size());
401 EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, observer.unmounts[0].error());
402 ASSERT_EQ(1u, service_->GetProvidedFileSystemInfoList().size());
403
404 std::vector<ProvidedFileSystemInfo> file_system_info_list =
405 service_->GetProvidedFileSystemInfoList();
406 ASSERT_EQ(1u, file_system_info_list.size());
407
408 service_->RemoveObserver(&observer);
409 }
410
411 TEST_F(FileSystemProviderServiceTest, RestoreFileSystem_OnExtensionLoad) {
412 LoggingObserver observer;
413 service_->AddObserver(&observer);
414
415 // Remember a fake file system first in order to be able to restore it.
416 MountOptions options(kFileSystemId, kDisplayName);
417 options.supports_notify_tag = true;
418 ProvidedFileSystemInfo file_system_info(
419 kExtensionId, options, base::FilePath(FILE_PATH_LITERAL("/a/b/c")),
420 false /* configurable */, extensions::SOURCE_FILE);
421 Watchers fake_watchers;
422 fake_watchers[WatcherKey(fake_watcher_.entry_path, fake_watcher_.recursive)] =
423 fake_watcher_;
424 registry_->RememberFileSystem(file_system_info, fake_watchers);
425
426 EXPECT_EQ(0u, observer.mounts.size());
427
428 // Directly call the observer's method.
429 service_->OnExtensionLoaded(profile_, extension_.get());
430
431 ASSERT_EQ(1u, observer.mounts.size());
432 EXPECT_EQ(base::File::FILE_OK, observer.mounts[0].error());
433 EXPECT_EQ(MOUNT_CONTEXT_RESTORE, observer.mounts[0].context());
434
435 EXPECT_EQ(file_system_info.extension_id(),
436 observer.mounts[0].file_system_info().extension_id());
437 EXPECT_EQ(file_system_info.file_system_id(),
438 observer.mounts[0].file_system_info().file_system_id());
439 EXPECT_EQ(file_system_info.writable(),
440 observer.mounts[0].file_system_info().writable());
441 EXPECT_EQ(file_system_info.supports_notify_tag(),
442 observer.mounts[0].file_system_info().supports_notify_tag());
443
444 std::vector<ProvidedFileSystemInfo> file_system_info_list =
445 service_->GetProvidedFileSystemInfoList();
446 ASSERT_EQ(1u, file_system_info_list.size());
447
448 ProvidedFileSystemInterface* const file_system =
449 service_->GetProvidedFileSystem(kExtensionId, kFileSystemId);
450 ASSERT_TRUE(file_system);
451
452 const Watchers* const watchers = file_system->GetWatchers();
453 ASSERT_TRUE(watchers);
454 ASSERT_EQ(1u, watchers->size());
455
456 const Watchers::const_iterator restored_watcher_it = watchers->find(
457 WatcherKey(fake_watcher_.entry_path, fake_watcher_.recursive));
458 ASSERT_NE(watchers->end(), restored_watcher_it);
459
460 EXPECT_EQ(fake_watcher_.entry_path, restored_watcher_it->second.entry_path);
461 EXPECT_EQ(fake_watcher_.recursive, restored_watcher_it->second.recursive);
462 EXPECT_EQ(fake_watcher_.last_tag, restored_watcher_it->second.last_tag);
463
464 service_->RemoveObserver(&observer);
465 }
466
467 TEST_F(FileSystemProviderServiceTest, RememberFileSystem_OnMount) {
468 LoggingObserver observer;
469 service_->AddObserver(&observer);
470
471 EXPECT_FALSE(registry_->file_system_info());
472 EXPECT_FALSE(registry_->watchers());
473
474 EXPECT_EQ(base::File::FILE_OK,
475 service_->MountFileSystem(
476 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
477 ASSERT_EQ(1u, observer.mounts.size());
478
479 ASSERT_TRUE(registry_->file_system_info());
480 EXPECT_EQ(kExtensionId, registry_->file_system_info()->extension_id());
481 EXPECT_EQ(kFileSystemId, registry_->file_system_info()->file_system_id());
482 EXPECT_EQ(kDisplayName, registry_->file_system_info()->display_name());
483 EXPECT_FALSE(registry_->file_system_info()->writable());
484 EXPECT_FALSE(registry_->file_system_info()->supports_notify_tag());
485 ASSERT_TRUE(registry_->watchers());
486
487 service_->RemoveObserver(&observer);
488 }
489
490 TEST_F(FileSystemProviderServiceTest, RememberFileSystem_OnUnmountOnShutdown) {
491 LoggingObserver observer;
492 service_->AddObserver(&observer);
493
494 {
495 EXPECT_FALSE(registry_->file_system_info());
496 EXPECT_FALSE(registry_->watchers());
497 EXPECT_EQ(base::File::FILE_OK,
498 service_->MountFileSystem(
499 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
500
501 EXPECT_EQ(1u, observer.mounts.size());
502 EXPECT_TRUE(registry_->file_system_info());
503 EXPECT_TRUE(registry_->watchers());
504 }
505
506 {
507 EXPECT_EQ(base::File::FILE_OK,
508 service_->UnmountFileSystem(kExtensionId, kFileSystemId,
509 Service::UNMOUNT_REASON_SHUTDOWN));
510
511 EXPECT_EQ(1u, observer.unmounts.size());
512 EXPECT_TRUE(registry_->file_system_info());
513 EXPECT_TRUE(registry_->watchers());
514 }
515
516 service_->RemoveObserver(&observer);
517 }
518
519 TEST_F(FileSystemProviderServiceTest, RememberFileSystem_OnUnmountByUser) {
520 LoggingObserver observer;
521 service_->AddObserver(&observer);
522
523 {
524 EXPECT_FALSE(registry_->file_system_info());
525 EXPECT_FALSE(registry_->watchers());
526 EXPECT_EQ(base::File::FILE_OK,
527 service_->MountFileSystem(
528 kExtensionId, MountOptions(kFileSystemId, kDisplayName)));
529
530 EXPECT_EQ(1u, observer.mounts.size());
531 EXPECT_TRUE(registry_->file_system_info());
532 EXPECT_TRUE(registry_->watchers());
533 }
534
535 {
536 EXPECT_EQ(base::File::FILE_OK,
537 service_->UnmountFileSystem(kExtensionId, kFileSystemId,
538 Service::UNMOUNT_REASON_USER));
539
540 EXPECT_EQ(1u, observer.unmounts.size());
541 EXPECT_FALSE(registry_->file_system_info());
542 EXPECT_FALSE(registry_->watchers());
543 }
544
545 service_->RemoveObserver(&observer);
546 }
547
548 } // namespace file_system_provider
549 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698