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 "chrome/browser/media_galleries/fileapi/picasa_data_provider.h"
6
7#include <utility>
8
9#include "base/basictypes.h"
10#include "base/bind_helpers.h"
11#include "base/callback.h"
12#include "base/files/file_util.h"
13#include "base/strings/stringprintf.h"
14#include "chrome/browser/media_galleries/fileapi/file_path_watcher_util.h"
15#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
16#include "chrome/browser/media_galleries/fileapi/safe_picasa_album_table_reader.h"
17#include "chrome/browser/media_galleries/fileapi/safe_picasa_albums_indexer.h"
18#include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
19#include "storage/browser/fileapi/file_system_operation_context.h"
20#include "storage/browser/fileapi/file_system_url.h"
21
22namespace picasa {
23
24namespace {
25
26void RunAllCallbacks(
27    std::vector<PicasaDataProvider::ReadyCallback>* ready_callbacks,
28    bool success) {
29  for (std::vector<PicasaDataProvider::ReadyCallback>::const_iterator it =
30           ready_callbacks->begin();
31       it != ready_callbacks->end();
32       ++it) {
33    it->Run(success);
34  }
35  ready_callbacks->clear();
36}
37
38}  // namespace
39
40PicasaDataProvider::PicasaDataProvider(const base::FilePath& database_path)
41    : database_path_(database_path),
42      state_(STALE_DATA_STATE),
43      weak_factory_(this) {
44  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
45
46  StartFilePathWatchOnMediaTaskRunner(
47      database_path_.DirName().AppendASCII(kPicasaTempDirName),
48      base::Bind(&PicasaDataProvider::OnTempDirWatchStarted,
49                 weak_factory_.GetWeakPtr()),
50      base::Bind(&PicasaDataProvider::OnTempDirChanged,
51                 weak_factory_.GetWeakPtr()));
52}
53
54PicasaDataProvider::~PicasaDataProvider() {}
55
56void PicasaDataProvider::RefreshData(DataType needed_data,
57                                     const ReadyCallback& ready_callback) {
58  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
59  // TODO(tommycli): Need to watch the database_path_ folder and handle
60  // rereading the data when it changes.
61
62  if (state_ == INVALID_DATA_STATE) {
63    ready_callback.Run(false /* success */);
64    return;
65  }
66
67  if (needed_data == LIST_OF_ALBUMS_AND_FOLDERS_DATA) {
68    if (state_ == LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE ||
69        state_ == ALBUMS_IMAGES_FRESH_STATE) {
70      ready_callback.Run(true /* success */);
71      return;
72    }
73    album_list_ready_callbacks_.push_back(ready_callback);
74  } else {
75    if (state_ == ALBUMS_IMAGES_FRESH_STATE) {
76      ready_callback.Run(true /* success */);
77      return;
78    }
79    albums_index_ready_callbacks_.push_back(ready_callback);
80  }
81  DoRefreshIfNecessary();
82}
83
84scoped_ptr<AlbumMap> PicasaDataProvider::GetFolders() {
85  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
86  DCHECK(state_ == LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE ||
87         state_ == ALBUMS_IMAGES_FRESH_STATE);
88  return make_scoped_ptr(new AlbumMap(folder_map_));
89}
90
91scoped_ptr<AlbumMap> PicasaDataProvider::GetAlbums() {
92  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
93  DCHECK(state_ == LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE ||
94         state_ == ALBUMS_IMAGES_FRESH_STATE);
95  return make_scoped_ptr(new AlbumMap(album_map_));
96}
97
98scoped_ptr<AlbumImages> PicasaDataProvider::FindAlbumImages(
99    const std::string& key,
100    base::File::Error* error) {
101  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
102  DCHECK(state_ == ALBUMS_IMAGES_FRESH_STATE);
103  DCHECK(error);
104
105  AlbumImagesMap::const_iterator it = albums_images_.find(key);
106
107  if (it == albums_images_.end()) {
108    *error = base::File::FILE_ERROR_NOT_FOUND;
109    return scoped_ptr<AlbumImages>();
110  }
111
112  *error = base::File::FILE_OK;
113  return make_scoped_ptr(new AlbumImages(it->second));
114}
115
116void PicasaDataProvider::InvalidateData() {
117  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
118
119  // Set data state to stale and ignore responses from any in-flight processes.
120  // TODO(tommycli): Implement and call Cancel function for these
121  // UtilityProcessHostClients to actually kill the in-flight processes.
122  state_ = STALE_DATA_STATE;
123  album_table_reader_ = NULL;
124  albums_indexer_ = NULL;
125
126  DoRefreshIfNecessary();
127}
128
129void PicasaDataProvider::OnTempDirWatchStarted(
130    scoped_ptr<base::FilePathWatcher> temp_dir_watcher) {
131  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
132  temp_dir_watcher_.reset(temp_dir_watcher.release());
133}
134
135void PicasaDataProvider::OnTempDirChanged(const base::FilePath& temp_dir_path,
136                                          bool error) {
137  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
138  if (base::IsDirectoryEmpty(temp_dir_path))
139    InvalidateData();
140}
141
142void PicasaDataProvider::DoRefreshIfNecessary() {
143  DCHECK(state_ != INVALID_DATA_STATE);
144  DCHECK(state_ != ALBUMS_IMAGES_FRESH_STATE);
145  DCHECK(!(album_table_reader_.get() && albums_indexer_.get()));
146
147  if (album_list_ready_callbacks_.empty() &&
148      albums_index_ready_callbacks_.empty()) {
149    return;
150  }
151
152  if (state_ == STALE_DATA_STATE) {
153    if (album_table_reader_.get())
154      return;
155    album_table_reader_ =
156        new SafePicasaAlbumTableReader(AlbumTableFiles(database_path_));
157    album_table_reader_->Start(
158        base::Bind(&PicasaDataProvider::OnAlbumTableReaderDone,
159                   weak_factory_.GetWeakPtr(),
160                   album_table_reader_));
161  } else {
162    DCHECK(state_ == LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE);
163    if (albums_indexer_.get())
164      return;
165    albums_indexer_ = new SafePicasaAlbumsIndexer(album_map_, folder_map_);
166    albums_indexer_->Start(base::Bind(&PicasaDataProvider::OnAlbumsIndexerDone,
167                                      weak_factory_.GetWeakPtr(),
168                                      albums_indexer_));
169  }
170}
171
172void PicasaDataProvider::OnAlbumTableReaderDone(
173    scoped_refptr<SafePicasaAlbumTableReader> reader,
174    bool parse_success,
175    const std::vector<AlbumInfo>& albums,
176    const std::vector<AlbumInfo>& folders) {
177  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
178  // If the reader has already been deemed stale, ignore the result.
179  if (reader.get() != album_table_reader_.get())
180    return;
181  album_table_reader_ = NULL;
182
183  DCHECK(state_ == STALE_DATA_STATE);
184
185  if (!parse_success) {
186    // If we didn't get the list successfully, fail all those waiting for
187    // the albums indexer also.
188    state_ = INVALID_DATA_STATE;
189    RunAllCallbacks(&album_list_ready_callbacks_, false /* success */);
190    RunAllCallbacks(&albums_index_ready_callbacks_, false /* success */);
191    return;
192  }
193
194  album_map_.clear();
195  folder_map_.clear();
196  UniquifyNames(albums, &album_map_);
197  UniquifyNames(folders, &folder_map_);
198
199  state_ = LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE;
200  RunAllCallbacks(&album_list_ready_callbacks_, parse_success);
201
202  // Chain from this process onto refreshing the albums images if necessary.
203  DoRefreshIfNecessary();
204}
205
206void PicasaDataProvider::OnAlbumsIndexerDone(
207    scoped_refptr<SafePicasaAlbumsIndexer> indexer,
208    bool success,
209    const picasa::AlbumImagesMap& albums_images) {
210  DCHECK(MediaFileSystemBackend::CurrentlyOnMediaTaskRunnerThread());
211  // If the indexer has already been deemed stale, ignore the result.
212  if (indexer.get() != albums_indexer_.get())
213    return;
214  albums_indexer_ = NULL;
215
216  DCHECK(state_ == LIST_OF_ALBUMS_AND_FOLDERS_FRESH_STATE);
217
218  if (success) {
219    state_ = ALBUMS_IMAGES_FRESH_STATE;
220
221    albums_images_ = albums_images;
222  }
223
224  RunAllCallbacks(&albums_index_ready_callbacks_, success);
225}
226
227// static
228std::string PicasaDataProvider::DateToPathString(const base::Time& time) {
229  base::Time::Exploded exploded_time;
230  time.LocalExplode(&exploded_time);
231
232  // TODO(tommycli): Investigate better localization and persisting which locale
233  // we use to generate these unique names.
234  return base::StringPrintf("%04d-%02d-%02d", exploded_time.year,
235                            exploded_time.month, exploded_time.day_of_month);
236}
237
238// static
239void PicasaDataProvider::UniquifyNames(const std::vector<AlbumInfo>& info_list,
240                                       AlbumMap* result_map) {
241  // TODO(tommycli): We should persist the uniquified names.
242  std::vector<std::string> desired_names;
243
244  std::map<std::string, int> total_counts;
245  std::map<std::string, int> current_counts;
246
247  for (std::vector<AlbumInfo>::const_iterator it = info_list.begin();
248       it != info_list.end(); ++it) {
249    std::string desired_name =
250        it->name + " " + DateToPathString(it->timestamp);
251    desired_names.push_back(desired_name);
252    ++total_counts[desired_name];
253  }
254
255  for (unsigned int i = 0; i < info_list.size(); i++) {
256    std::string name = desired_names[i];
257
258    if (total_counts[name] != 1) {
259      name = base::StringPrintf("%s (%d)", name.c_str(),
260                                ++current_counts[name]);
261    }
262
263    result_map->insert(std::pair<std::string, AlbumInfo>(name, info_list[i]));
264  }
265}
266
267}  // namespace picasa
268