1// Copyright 2013 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 <string>
6#include <vector>
7
8#include "base/bind.h"
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/files/scoped_temp_dir.h"
12#include "base/format_macros.h"
13#include "base/logging.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "base/run_loop.h"
17#include "base/stl_util.h"
18#include "base/strings/stringprintf.h"
19#include "chrome/browser/media_galleries/fileapi/iphoto_data_provider.h"
20#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
21#include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
22#include "chrome/test/base/in_process_browser_test.h"
23#include "content/public/browser/browser_thread.h"
24#include "url/gurl.h"
25
26using base::FilePath;
27
28namespace iphoto {
29
30class TestIPhotoDataProvider : public IPhotoDataProvider {
31 public:
32  TestIPhotoDataProvider(const base::FilePath& xml_library_path,
33                         const base::Closure& callback)
34      : IPhotoDataProvider(xml_library_path),
35        callback_(callback) {
36  }
37  virtual ~TestIPhotoDataProvider() {}
38
39 private:
40  virtual void OnLibraryChanged(const base::FilePath& path,
41                                bool error) OVERRIDE {
42    IPhotoDataProvider::OnLibraryChanged(path, error);
43    callback_.Run();
44  }
45
46  base::Closure callback_;
47
48  DISALLOW_COPY_AND_ASSIGN(TestIPhotoDataProvider);
49};
50
51class IPhotoDataProviderTest : public InProcessBrowserTest {
52 public:
53  IPhotoDataProviderTest() {}
54  virtual ~IPhotoDataProviderTest() {}
55
56 protected:
57  virtual void SetUp() OVERRIDE {
58    ASSERT_TRUE(library_dir_.CreateUniqueTempDir());
59    WriteLibraryInternal();
60    // The ImportedMediaGalleryRegistry is created on which ever thread calls
61    // GetInstance() first.  It shouldn't matter what thread creates, however
62    // in practice it is always created on the UI thread, so this calls
63    // GetInstance here to mirror those real conditions.
64    ImportedMediaGalleryRegistry::GetInstance();
65    InProcessBrowserTest::SetUp();
66  }
67
68  void RunTest() {
69    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
70    base::RunLoop loop;
71    quit_closure_ = loop.QuitClosure();
72    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
73        FROM_HERE,
74        base::Bind(&IPhotoDataProviderTest::StartTestOnMediaTaskRunner,
75                   base::Unretained(this)));
76    loop.Run();
77  }
78
79  void WriteLibrary(const base::Closure& callback) {
80    SetLibraryChangeCallback(callback);
81    WriteLibraryInternal();
82  }
83
84  void SetLibraryChangeCallback(const base::Closure& callback) {
85    EXPECT_TRUE(library_changed_callback_.is_null());
86    library_changed_callback_ = callback;
87  }
88
89  IPhotoDataProvider* data_provider() const {
90    return ImportedMediaGalleryRegistry::IPhotoDataProvider();
91  }
92
93  const base::FilePath& library_dir() const {
94    return library_dir_.path();
95  }
96
97  base::FilePath XmlFile() const {
98    return library_dir_.path().AppendASCII("library.xml");
99  }
100
101  // Start the test. The data provider is refreshed before calling StartTest
102  // and the result of the refresh is passed in.
103  virtual void StartTest(bool parse_success) = 0;
104
105  void TestDone() {
106    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
107    ImportedMediaGalleryRegistry* imported_registry =
108        ImportedMediaGalleryRegistry::GetInstance();
109    imported_registry->iphoto_data_provider_.reset();
110    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
111                                     quit_closure_);
112  }
113
114  // Override to provide a full library string.
115  virtual std::string GetLibraryString() {
116    return "<plist><dict>\n</dict></plist>\n";
117  }
118
119 private:
120  void StartTestOnMediaTaskRunner() {
121    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
122    ImportedMediaGalleryRegistry* imported_registry =
123        ImportedMediaGalleryRegistry::GetInstance();
124    imported_registry->iphoto_data_provider_.reset(
125        new TestIPhotoDataProvider(
126            XmlFile(),
127            base::Bind(&IPhotoDataProviderTest::OnLibraryChanged,
128                       base::Unretained(this))));
129    data_provider()->RefreshData(base::Bind(&IPhotoDataProviderTest::StartTest,
130                                            base::Unretained(this)));
131  };
132
133  void OnLibraryChanged() {
134    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
135    if (!library_changed_callback_.is_null()) {
136      library_changed_callback_.Run();
137      library_changed_callback_.Reset();
138    }
139  }
140
141  void WriteLibraryInternal() {
142    std::string xml = GetLibraryString();
143    ASSERT_EQ(static_cast<int>(xml.size()),
144              base::WriteFile(XmlFile(), xml.c_str(), xml.size()));
145  }
146
147  base::ScopedTempDir library_dir_;
148
149  base::Closure library_changed_callback_;
150
151  base::Closure quit_closure_;
152
153  DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderTest);
154};
155
156class IPhotoDataProviderBasicTest : public IPhotoDataProviderTest {
157 public:
158  IPhotoDataProviderBasicTest() {}
159  virtual ~IPhotoDataProviderBasicTest() {}
160
161  virtual std::string GetLibraryString() OVERRIDE {
162    return "<plist><dict>\n"
163      "<key>List of Albums</key>\n"
164      "<array>"
165      "    <dict>\n"
166      "      <key>AlbumId</key>"
167      "      <integer>14</integer>"
168      "      <key>AlbumName</key>"
169      "      <string>Album1</string>"
170      "      <key>KeyList</key>"
171      "      <array>"
172      "      <string>1</string>"
173      "      <string>3</string>"  // [3] and [4] are name dupes
174      "      <string>4</string>"
175      "      </array>"
176      "    </dict>\n"
177      "    <dict>\n"
178      "      <key>AlbumId</key>"
179      "      <integer>15</integer>"
180      "      <key>AlbumName</key>"
181      "      <string>Album2</string>"
182      "      <key>KeyList</key>"
183      "      <array>"
184      "      <string>2</string>"
185      "      </array>"
186      "    </dict>\n"
187      "    <dict>\n"
188      "      <key>AlbumId</key>"
189      "      <integer>16</integer>"
190      "      <key>AlbumName</key>"
191      "      <string>Album5</string>"
192      "      <key>KeyList</key>"
193      "      <array>"
194      "      <string>5</string>"  // A name dupe of [2], but in another album.
195      "      </array>"
196      "    </dict>\n"
197      "</array>\n"
198      "<key>Master Image List</key>\n"
199      "<dict>\n"
200      "  <key>1</key>\n"
201      "  <dict>\n"
202      "    <key>MediaType</key>"
203      "    <string>Image</string>"
204      "    <key>Caption</key>"
205      "    <string>caption</string>"
206      "    <key>GUID</key>\n"
207      "    <string>guid1</string>"
208      "    <key>ImagePath</key>"
209      "    <string>/vol/path1.jpg</string>"
210      "    <key>ThumbPath</key>"
211      "    <string>/vol/thumb1.jpg</string>"
212      "  </dict>\n"
213      "  <key>2</key>\n"
214      "  <dict>\n"
215      "    <key>MediaType</key>"
216      "    <string>Image</string>"
217      "    <key>Caption</key>"
218      "    <string>caption2</string>"
219      "    <key>GUID</key>\n"
220      "    <string>guid2</string>"
221      "    <key>ImagePath</key>"
222      "    <string>/vol/path2.jpg</string>"
223      "    <key>ThumbPath</key>"
224      "    <string>/vol/thumb2.jpg</string>"
225      "  </dict>\n"
226      "  <key>3</key>\n"
227      "  <dict>\n"
228      "    <key>MediaType</key>"
229      "    <string>Image</string>"
230      "    <key>Caption</key>"
231      "    <string>caption3</string>"
232      "    <key>GUID</key>\n"
233      "    <string>guid3</string>"
234      "    <key>ImagePath</key>"
235      "    <string>/vol/path3.jpg</string>"
236      "    <key>ThumbPath</key>"
237      "    <string>/vol/thumb3.jpg</string>"
238      "  </dict>\n"
239      "  <key>4</key>\n"  // A name duplicate of [3] in another path.
240      "  <dict>\n"
241      "    <key>MediaType</key>"
242      "    <string>Image</string>"
243      "    <key>Caption</key>"
244      "    <string>caption</string>"
245      "    <key>GUID</key>\n"
246      "    <string>guid3</string>"
247      "    <key>ImagePath</key>"
248      "    <string>/vol/dupe/path3.jpg</string>"
249      "    <key>ThumbPath</key>"
250      "    <string>/vol/dupe/thumb3.jpg</string>"
251      "  </dict>\n"
252      "  <key>5</key>\n"  // A name duplicate of [2] in another path.
253      "  <dict>\n"
254      "    <key>MediaType</key>"
255      "    <string>Image</string>"
256      "    <key>Caption</key>"
257      "    <string>caption5</string>"
258      "    <key>GUID</key>\n"
259      "    <string>guid2</string>"
260      "    <key>ImagePath</key>"
261      "    <string>/vol/dupe/path2.jpg</string>"
262      "    <key>ThumbPath</key>"
263      "    <string>/vol/dupe/thumb2.jpg</string>"
264      "    <key>OriginalPath</key>"             \
265      "    <string>/original/vol/another2.jpg</string>"  \
266      "  </dict>\n"
267      "</dict>\n"
268      "</dict></plist>\n";
269  }
270
271  virtual void StartTest(bool parse_success) OVERRIDE {
272    EXPECT_TRUE(parse_success);
273
274    std::vector<std::string> names = data_provider()->GetAlbumNames();
275    EXPECT_EQ(3U, names.size());
276    EXPECT_EQ("Album1", names[0]);
277
278    EXPECT_EQ(FilePath("/vol/path1.jpg").value(),
279              data_provider()->GetPhotoLocationInAlbum(
280                  "Album1", "path1.jpg").value());
281    EXPECT_EQ(FilePath("/vol/path3.jpg").value(),
282              data_provider()->GetPhotoLocationInAlbum(
283                  "Album1", "path3.jpg").value());
284    EXPECT_EQ(FilePath("/vol/dupe/path3.jpg").value(),
285              data_provider()->GetPhotoLocationInAlbum(
286                  "Album1", "path3(4).jpg").value());
287    EXPECT_EQ(FilePath().value(),
288              data_provider()->GetPhotoLocationInAlbum(
289                  "Album1", "path5.jpg").value());
290
291    // path2.jpg is name-duped, but in different albums, and so should not
292    // be mangled.
293    EXPECT_EQ(FilePath("/vol/dupe/path2.jpg").value(),
294              data_provider()->GetPhotoLocationInAlbum(
295                  "Album5", "path2.jpg").value());
296    EXPECT_EQ(FilePath("/vol/path2.jpg").value(),
297              data_provider()->GetPhotoLocationInAlbum(
298                  "Album2", "path2.jpg").value());
299
300    std::map<std::string, base::FilePath> photos =
301        data_provider()->GetAlbumContents("nonexistent");
302    EXPECT_EQ(0U, photos.size());
303    photos = data_provider()->GetAlbumContents("Album1");
304    EXPECT_EQ(3U, photos.size());
305    EXPECT_TRUE(ContainsKey(photos, "path1.jpg"));
306    EXPECT_FALSE(ContainsKey(photos, "path2.jpg"));
307    EXPECT_TRUE(ContainsKey(photos, "path3.jpg"));
308    EXPECT_TRUE(ContainsKey(photos, "path3(4).jpg"));
309    EXPECT_EQ(FilePath("/vol/path1.jpg").value(), photos["path1.jpg"].value());
310    EXPECT_EQ(FilePath("/vol/path3.jpg").value(),
311        photos["path3.jpg"].value());
312    EXPECT_EQ(FilePath("/vol/dupe/path3.jpg").value(),
313        photos["path3(4).jpg"].value());
314
315    photos = data_provider()->GetAlbumContents("Album2");
316    EXPECT_EQ(1U, photos.size());
317    EXPECT_TRUE(ContainsKey(photos, "path2.jpg"));
318
319    EXPECT_FALSE(data_provider()->HasOriginals("Album1"));
320    EXPECT_TRUE(data_provider()->HasOriginals("Album5"));
321    std::map<std::string, base::FilePath> originals =
322        data_provider()->GetOriginals("Album1");
323    EXPECT_EQ(0U, originals.size());
324    originals = data_provider()->GetOriginals("Album5");
325    EXPECT_EQ(1U, originals.size());
326    EXPECT_TRUE(ContainsKey(originals, "path2.jpg"));
327    EXPECT_FALSE(ContainsKey(originals, "path1.jpg"));
328    EXPECT_EQ(FilePath("/original/vol/another2.jpg").value(),
329              originals["path2.jpg"].value());
330    base::FilePath original_path =
331        data_provider()->GetOriginalPhotoLocation("Album5", "path2.jpg");
332    EXPECT_EQ(FilePath("/original/vol/another2.jpg").value(),
333              original_path.value());
334
335    TestDone();
336  }
337
338 private:
339  DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderBasicTest);
340};
341
342class IPhotoDataProviderRefreshTest : public IPhotoDataProviderTest {
343 public:
344  IPhotoDataProviderRefreshTest() {}
345  virtual ~IPhotoDataProviderRefreshTest() {}
346
347  std::string another_album;
348
349  virtual std::string GetLibraryString() OVERRIDE {
350    return "<plist><dict>\n"
351      "<key>List of Albums</key>\n"
352      "<array>"
353      "    <dict>"
354      "      <key>AlbumId</key>"
355      "      <integer>14</integer>"
356      "      <key>AlbumName</key>"
357      "      <string>Album1</string>"
358      "      <key>KeyList</key>"
359      "      <array>"
360      "      <string>1</string>"
361      "      </array>"
362      "    </dict>\n" +
363      another_album +
364      "</array>\n"
365      "<key>Master Image List</key>\n"
366      "<dict>\n"
367      "  <key>1</key>\n"
368      "  <dict>\n"
369      "    <key>MediaType</key>"
370      "    <string>Image</string>"
371      "    <key>Caption1</key>"
372      "    <string>caption</string>"
373      "    <key>GUID</key>\n"
374      "    <string>guid1</string>"
375      "    <key>ImagePath</key>"
376      "    <string>/vol/path1.jpg</string>"
377      "    <key>ThumbPath</key>"
378      "    <string>/vol/thumb1.jpg</string>"
379      "  </dict>\n"
380      "</dict>\n"
381      "</dict></plist>\n";
382  }
383
384  virtual void StartTest(bool parse_success) OVERRIDE {
385    EXPECT_TRUE(parse_success);
386
387    EXPECT_EQ(FilePath("/vol/path1.jpg"),
388              data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg"));
389    std::vector<std::string> names = data_provider()->GetAlbumNames();
390    EXPECT_EQ(1U, names.size());
391    EXPECT_EQ("Album1", names[0]);
392
393    another_album =
394      "    <dict>"
395      "      <key>AlbumId</key>"
396      "      <integer>14</integer>"
397      "      <key>AlbumName</key>"
398      "      <string>Another Album</string>"
399      "      <key>KeyList</key>"
400      "      <array>"
401      "      <string>1</string>"
402      "      </array>"
403      "    </dict>\n";
404
405    WriteLibrary(base::Bind(&IPhotoDataProviderRefreshTest::CheckAfterWrite,
406                            base::Unretained(this)));
407  }
408
409  void CheckAfterWrite() {
410    // No change -- data has not been parsed.
411    EXPECT_EQ(FilePath("/vol/path1.jpg"),
412              data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg"));
413    std::vector<std::string> names = data_provider()->GetAlbumNames();
414    EXPECT_EQ(1U, names.size());
415    EXPECT_EQ("Album1", names[0]);
416
417    data_provider()->RefreshData(
418        base::Bind(&IPhotoDataProviderRefreshTest::CheckRefresh,
419                   base::Unretained(this)));
420  }
421
422  void CheckRefresh(bool is_valid) {
423    EXPECT_TRUE(is_valid);
424
425    EXPECT_EQ(FilePath("/vol/path1.jpg"),
426              data_provider()->GetPhotoLocationInAlbum("Album1", "path1.jpg"));
427    std::vector<std::string> names = data_provider()->GetAlbumNames();
428    EXPECT_EQ(2U, names.size());
429    if (names.size() == 2U) {
430      EXPECT_EQ("Album1", names[0]);
431      EXPECT_EQ("Another Album", names[1]);
432    }
433
434    TestDone();
435  }
436
437 private:
438  DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderRefreshTest);
439};
440
441class IPhotoDataProviderInvalidTest : public IPhotoDataProviderTest {
442 public:
443  IPhotoDataProviderInvalidTest() {}
444  virtual ~IPhotoDataProviderInvalidTest() {}
445
446  virtual void StartTest(bool parse_success) OVERRIDE {
447    EXPECT_TRUE(parse_success);
448
449    SetLibraryChangeCallback(
450        base::Bind(&IPhotoDataProvider::RefreshData,
451                   base::Unretained(data_provider()),
452                   base::Bind(&IPhotoDataProviderInvalidTest::CheckInvalid,
453                              base::Unretained(this))));
454    EXPECT_EQ(1L, base::WriteFile(XmlFile(), " ", 1));
455  }
456
457  void CheckInvalid(bool is_valid) {
458    EXPECT_FALSE(is_valid);
459    TestDone();
460  }
461
462 private:
463  DISALLOW_COPY_AND_ASSIGN(IPhotoDataProviderInvalidTest);
464};
465
466IN_PROC_BROWSER_TEST_F(IPhotoDataProviderBasicTest, BasicTest) {
467  RunTest();
468}
469
470IN_PROC_BROWSER_TEST_F(IPhotoDataProviderRefreshTest, RefreshTest) {
471  RunTest();
472}
473
474IN_PROC_BROWSER_TEST_F(IPhotoDataProviderInvalidTest, InvalidTest) {
475  RunTest();
476}
477
478}  // namespace iphoto
479