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/strings/stringprintf.h"
18#include "chrome/browser/media_galleries/fileapi/itunes_data_provider.h"
19#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
20#include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
21#include "chrome/test/base/in_process_browser_test.h"
22#include "content/public/browser/browser_thread.h"
23#include "url/gurl.h"
24
25namespace itunes {
26
27namespace {
28
29struct LibraryEntry {
30  LibraryEntry(const std::string& artist, const std::string& album,
31               const base::FilePath& location)
32      : artist(artist),
33        album(album),
34        location(location) {
35  }
36  std::string artist;
37  std::string album;
38  base::FilePath location;
39};
40
41// 'c' with combinding cedilla.
42const char kDeNormalizedName[] = {
43    'c', static_cast<unsigned char>(0xCC), static_cast<unsigned char>(0xA7), 0};
44// 'c' with cedilla.
45const char kNormalizedName[] = {
46    static_cast<unsigned char>(0xC3), static_cast<unsigned char>(0xA7), 0};
47
48}  // namespace
49
50class TestITunesDataProvider : public ITunesDataProvider {
51 public:
52  TestITunesDataProvider(const base::FilePath& xml_library_path,
53                         const base::Closure& callback)
54      : ITunesDataProvider(xml_library_path),
55        callback_(callback) {
56  }
57  virtual ~TestITunesDataProvider() {}
58
59 private:
60  virtual void OnLibraryChanged(const base::FilePath& path,
61                                bool error) OVERRIDE {
62    ITunesDataProvider::OnLibraryChanged(path, error);
63    callback_.Run();
64  }
65
66  base::Closure callback_;
67
68  DISALLOW_COPY_AND_ASSIGN(TestITunesDataProvider);
69};
70
71class ITunesDataProviderTest : public InProcessBrowserTest {
72 public:
73  ITunesDataProviderTest() {}
74  virtual ~ITunesDataProviderTest() {}
75
76 protected:
77  virtual void SetUp() OVERRIDE {
78    ASSERT_TRUE(library_dir_.CreateUniqueTempDir());
79    WriteLibraryInternal(SetUpLibrary());
80    // The ImportedMediaGalleryRegistry is created on which ever thread calls
81    // GetInstance() first.  It shouldn't matter what thread creates, however
82    // in practice it is always created on the UI thread, so this calls
83    // GetInstance here to mirror those real conditions.
84    ImportedMediaGalleryRegistry::GetInstance();
85    InProcessBrowserTest::SetUp();
86  }
87
88  void RunTest() {
89    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
90    base::RunLoop loop;
91    quit_closure_ = loop.QuitClosure();
92    MediaFileSystemBackend::MediaTaskRunner()->PostTask(
93        FROM_HERE,
94        base::Bind(&ITunesDataProviderTest::StartTestOnMediaTaskRunner,
95                   base::Unretained(this)));
96    loop.Run();
97  }
98
99  void WriteLibrary(const std::vector<LibraryEntry>& entries,
100                    const base::Closure& callback) {
101    SetLibraryChangeCallback(callback);
102    WriteLibraryInternal(entries);
103  }
104
105  void SetLibraryChangeCallback(const base::Closure& callback) {
106    EXPECT_TRUE(library_changed_callback_.is_null());
107    library_changed_callback_ = callback;
108  }
109
110  ITunesDataProvider* data_provider() const {
111    return ImportedMediaGalleryRegistry::ITunesDataProvider();
112  }
113
114  const base::FilePath& library_dir() const {
115    return library_dir_.path();
116  }
117
118  base::FilePath XmlFile() const {
119    return library_dir_.path().AppendASCII("library.xml");
120  }
121
122  void ExpectTrackLocation(const std::string& artist, const std::string& album,
123                           const std::string& track_name) {
124    base::FilePath track =
125        library_dir().AppendASCII(track_name).NormalizePathSeparators();
126    EXPECT_EQ(track.value(),
127              data_provider()->GetTrackLocation(
128                  artist, album, track_name).NormalizePathSeparators().value());
129  }
130
131  void ExpectNoTrack(const std::string& artist, const std::string& album,
132                     const std::string& track_name) {
133    EXPECT_TRUE(data_provider()->GetTrackLocation(
134          artist, album, track_name).empty()) << track_name;
135  }
136
137
138  // Get the initial set of library entries, called by SetUp.  If no entries
139  // are returned the xml file is not created.
140  virtual std::vector<LibraryEntry> SetUpLibrary() {
141    return std::vector<LibraryEntry>();
142  }
143
144  // Start the test. The data provider is refreshed before calling StartTest
145  // and the result of the refresh is passed in.
146  virtual void StartTest(bool parse_success) = 0;
147
148  void TestDone() {
149    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
150    ImportedMediaGalleryRegistry* imported_registry =
151        ImportedMediaGalleryRegistry::GetInstance();
152    imported_registry->itunes_data_provider_.reset();
153    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
154                                     quit_closure_);
155  }
156
157 private:
158  void StartTestOnMediaTaskRunner() {
159    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
160    ImportedMediaGalleryRegistry* imported_registry =
161        ImportedMediaGalleryRegistry::GetInstance();
162    imported_registry->itunes_data_provider_.reset(
163        new TestITunesDataProvider(
164            XmlFile(),
165            base::Bind(&ITunesDataProviderTest::OnLibraryChanged,
166                       base::Unretained(this))));
167    data_provider()->RefreshData(base::Bind(&ITunesDataProviderTest::StartTest,
168                                            base::Unretained(this)));
169  };
170
171  void OnLibraryChanged() {
172    DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
173    if (!library_changed_callback_.is_null()) {
174      library_changed_callback_.Run();
175      library_changed_callback_.Reset();
176    }
177  }
178
179  void WriteLibraryInternal(const std::vector<LibraryEntry>& entries) {
180    if (!entries.size())
181      return;
182    std::string xml = "<plist><dict><key>Tracks</key><dict>\n";
183    for (size_t i = 0; i < entries.size(); ++i) {
184      std::string separator;
185#if defined(OS_WIN)
186      separator = "/";
187#endif
188      GURL location("file://localhost" + separator +
189                    entries[i].location.AsUTF8Unsafe());
190      std::string entry_string = base::StringPrintf(
191          "<key>%" PRIuS "</key><dict>\n"
192          "  <key>Track ID</key><integer>%" PRIuS "</integer>\n"
193          "  <key>Location</key><string>%s</string>\n"
194          "  <key>Artist</key><string>%s</string>\n"
195          "  <key>Album</key><string>%s</string>\n"
196          "</dict>\n",
197          i + 1, i + 1, location.spec().c_str(), entries[i].artist.c_str(),
198          entries[i].album.c_str());
199      xml += entry_string;
200    }
201    xml += "</dict></dict></plist>\n";
202    ASSERT_EQ(static_cast<int>(xml.size()),
203              base::WriteFile(XmlFile(), xml.c_str(), xml.size()));
204  }
205
206  base::ScopedTempDir library_dir_;
207
208  base::Closure library_changed_callback_;
209
210  base::Closure quit_closure_;
211
212  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderTest);
213};
214
215class ITunesDataProviderBasicTest : public ITunesDataProviderTest {
216 public:
217  ITunesDataProviderBasicTest() {}
218  virtual ~ITunesDataProviderBasicTest() {}
219
220  virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE {
221    base::FilePath track = library_dir().AppendASCII("Track.mp3");
222    std::vector<LibraryEntry> entries;
223    entries.push_back(LibraryEntry("Artist", "Album", track));
224    return entries;
225  }
226
227  virtual void StartTest(bool parse_success) OVERRIDE {
228    EXPECT_TRUE(parse_success);
229
230    // KnownArtist
231    EXPECT_TRUE(data_provider()->KnownArtist("Artist"));
232    EXPECT_FALSE(data_provider()->KnownArtist("Artist2"));
233
234    // KnownAlbum
235    EXPECT_TRUE(data_provider()->KnownAlbum("Artist", "Album"));
236    EXPECT_FALSE(data_provider()->KnownAlbum("Artist", "Album2"));
237    EXPECT_FALSE(data_provider()->KnownAlbum("Artist2", "Album"));
238
239    // GetTrackLocation
240    ExpectTrackLocation("Artist", "Album", "Track.mp3");
241    ExpectNoTrack("Artist", "Album", "Track2.mp3");
242    ExpectNoTrack("Artist", "Album2", "Track.mp3");
243    ExpectNoTrack("Artist2", "Album", "Track.mp3");
244
245    // GetArtistNames
246    std::set<ITunesDataProvider::ArtistName> artists =
247      data_provider()->GetArtistNames();
248    ASSERT_EQ(1U, artists.size());
249    EXPECT_EQ("Artist", *artists.begin());
250
251    // GetAlbumNames
252    std::set<ITunesDataProvider::AlbumName> albums =
253        data_provider()->GetAlbumNames("Artist");
254    ASSERT_EQ(1U, albums.size());
255    EXPECT_EQ("Album", *albums.begin());
256
257    albums = data_provider()->GetAlbumNames("Artist2");
258    EXPECT_EQ(0U, albums.size());
259
260    // GetAlbum
261    base::FilePath track =
262        library_dir().AppendASCII("Track.mp3").NormalizePathSeparators();
263    ITunesDataProvider::Album album =
264        data_provider()->GetAlbum("Artist", "Album");
265    ASSERT_EQ(1U, album.size());
266    EXPECT_EQ(track.BaseName().AsUTF8Unsafe(), album.begin()->first);
267    EXPECT_EQ(track.value(),
268              album.begin()->second.NormalizePathSeparators().value());
269
270    album = data_provider()->GetAlbum("Artist", "Album2");
271    EXPECT_EQ(0U, album.size());
272
273    album = data_provider()->GetAlbum("Artist2", "Album");
274    EXPECT_EQ(0U, album.size());
275
276    TestDone();
277  }
278
279 private:
280  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderBasicTest);
281};
282
283class ITunesDataProviderRefreshTest : public ITunesDataProviderTest {
284 public:
285  ITunesDataProviderRefreshTest() {}
286  virtual ~ITunesDataProviderRefreshTest() {}
287
288  virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE {
289    base::FilePath track = library_dir().AppendASCII("Track.mp3");
290    std::vector<LibraryEntry> entries;
291    entries.push_back(LibraryEntry("Artist", "Album", track));
292    return entries;
293  }
294
295  virtual void StartTest(bool parse_success) OVERRIDE {
296    EXPECT_TRUE(parse_success);
297
298    // Initial contents.
299    ExpectTrackLocation("Artist", "Album", "Track.mp3");
300    ExpectNoTrack("Artist2", "Album2", "Track2.mp3");
301
302    // New file.
303    base::FilePath track2 = library_dir().AppendASCII("Track2.mp3");
304    std::vector<LibraryEntry> entries;
305    entries.push_back(LibraryEntry("Artist2", "Album2", track2));
306    WriteLibrary(entries,
307                 base::Bind(&ITunesDataProviderRefreshTest::CheckAfterWrite,
308                            base::Unretained(this)));
309  }
310
311  void CheckAfterWrite() {
312    // Content the same.
313    ExpectTrackLocation("Artist", "Album", "Track.mp3");
314    ExpectNoTrack("Artist2", "Album2", "Track2.mp3");
315
316    data_provider()->RefreshData(
317        base::Bind(&ITunesDataProviderRefreshTest::CheckRefresh,
318                   base::Unretained(this)));
319  }
320
321  void CheckRefresh(bool is_valid) {
322    EXPECT_TRUE(is_valid);
323
324    ExpectTrackLocation("Artist2", "Album2", "Track2.mp3");
325    ExpectNoTrack("Artist", "Album", "Track.mp3");
326    TestDone();
327  }
328
329 private:
330  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderRefreshTest);
331};
332
333class ITunesDataProviderInvalidTest : public ITunesDataProviderTest {
334 public:
335  ITunesDataProviderInvalidTest() {}
336  virtual ~ITunesDataProviderInvalidTest() {}
337
338  virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE {
339    base::FilePath track = library_dir().AppendASCII("Track.mp3");
340    std::vector<LibraryEntry> entries;
341    entries.push_back(LibraryEntry("Artist", "Album", track));
342    return entries;
343  }
344
345  virtual void StartTest(bool parse_success) OVERRIDE {
346    EXPECT_TRUE(parse_success);
347
348    SetLibraryChangeCallback(
349        base::Bind(&ITunesDataProvider::RefreshData,
350                   base::Unretained(data_provider()),
351                   base::Bind(&ITunesDataProviderInvalidTest::CheckInvalid,
352                              base::Unretained(this))));
353    ASSERT_EQ(1L, base::WriteFile(XmlFile(), " ", 1));
354  }
355
356  void CheckInvalid(bool is_valid) {
357    EXPECT_FALSE(is_valid);
358    TestDone();
359  }
360
361 private:
362  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderInvalidTest);
363};
364
365class ITunesDataProviderUniqueNameTest : public ITunesDataProviderTest {
366 public:
367  ITunesDataProviderUniqueNameTest() {}
368  virtual ~ITunesDataProviderUniqueNameTest() {}
369
370  virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE {
371    base::FilePath track = library_dir().AppendASCII("Track.mp3");
372    std::vector<LibraryEntry> entries;
373    // Dupe album names should get uniquified with the track id, which in the
374    // test framework is the vector index.
375    entries.push_back(LibraryEntry("Artist", "Album", track));
376    entries.push_back(LibraryEntry("Artist", "Album", track));
377    entries.push_back(LibraryEntry("Artist", "Album2", track));
378    return entries;
379  }
380
381  virtual void StartTest(bool parse_success) OVERRIDE {
382    EXPECT_TRUE(parse_success);
383
384    base::FilePath track =
385        library_dir().AppendASCII("Track.mp3").NormalizePathSeparators();
386    EXPECT_EQ(track.value(),
387              data_provider()->GetTrackLocation(
388                  "Artist", "Album",
389                  "Track (1).mp3").NormalizePathSeparators().value());
390    EXPECT_EQ(track.value(),
391              data_provider()->GetTrackLocation(
392                  "Artist", "Album",
393                  "Track (2).mp3").NormalizePathSeparators().value());
394    EXPECT_EQ(track.value(),
395              data_provider()->GetTrackLocation(
396                  "Artist", "Album2",
397                  "Track.mp3").NormalizePathSeparators().value());
398
399    TestDone();
400  }
401
402 private:
403  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderUniqueNameTest);
404};
405
406class ITunesDataProviderEscapeTest : public ITunesDataProviderTest {
407 // Albums and tracks that aren't the same, but become the same after
408 // replacing bad characters are not handled properly, but that case should
409 // never happen in practice.
410 public:
411  ITunesDataProviderEscapeTest() {}
412  virtual ~ITunesDataProviderEscapeTest() {}
413
414  virtual std::vector<LibraryEntry> SetUpLibrary() OVERRIDE {
415    base::FilePath track = library_dir().AppendASCII("Track:1.mp3");
416    std::vector<LibraryEntry> entries;
417    entries.push_back(LibraryEntry("Artist:/name", "Album:name/", track));
418    entries.push_back(LibraryEntry("Artist/name", "Album:name", track));
419    entries.push_back(LibraryEntry("Artist/name", "Album:name", track));
420    entries.push_back(LibraryEntry(kDeNormalizedName, kNormalizedName, track));
421    return entries;
422  }
423
424  virtual void StartTest(bool parse_success) OVERRIDE {
425    EXPECT_TRUE(parse_success);
426
427    base::FilePath track =
428        library_dir().AppendASCII("Track:1.mp3").NormalizePathSeparators();
429    EXPECT_EQ(track.value(),
430              data_provider()->GetTrackLocation(
431                  "Artist__name", "Album_name_",
432                  "Track_1.mp3").NormalizePathSeparators().value());
433    EXPECT_EQ(track.value(),
434              data_provider()->GetTrackLocation(
435                  "Artist_name", "Album_name",
436                  "Track_1 (2).mp3").NormalizePathSeparators().value());
437    EXPECT_EQ(track.value(),
438              data_provider()->GetTrackLocation(
439                  "Artist_name", "Album_name",
440                  "Track_1 (3).mp3").NormalizePathSeparators().value());
441    EXPECT_EQ(track.value(),
442              data_provider()->GetTrackLocation(
443                  kNormalizedName, kNormalizedName,
444                  "Track_1.mp3").NormalizePathSeparators().value());
445
446    TestDone();
447  }
448
449 private:
450  DISALLOW_COPY_AND_ASSIGN(ITunesDataProviderEscapeTest);
451};
452
453IN_PROC_BROWSER_TEST_F(ITunesDataProviderBasicTest, BasicTest) {
454  RunTest();
455}
456
457IN_PROC_BROWSER_TEST_F(ITunesDataProviderRefreshTest, RefreshTest) {
458  RunTest();
459}
460
461IN_PROC_BROWSER_TEST_F(ITunesDataProviderInvalidTest, InvalidTest) {
462  RunTest();
463}
464
465IN_PROC_BROWSER_TEST_F(ITunesDataProviderUniqueNameTest, UniqueNameTest) {
466  RunTest();
467}
468
469IN_PROC_BROWSER_TEST_F(ITunesDataProviderEscapeTest, EscapeTest) {
470  RunTest();
471}
472
473}  // namespace itunes
474