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.
5 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
7 #include "base/callback.h"
8 #include "base/command_line.h"
9 #include "base/files/file_path.h"
10 #include "base/json/json_reader.h"
11 #include "base/json/json_string_value_serializer.h"
12 #include "base/logging.h"
13 #include "base/strings/string16.h"
14 #include "base/strings/stringprintf.h"
15 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
16 #include "chrome/browser/extensions/activity_log/activity_database.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/chrome_constants.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/extensions/dom_action_types.h"
21 #include "extensions/common/extension.h"
22 #include "sql/error_delegate_util.h"
23 #include "sql/transaction.h"
29 using base::Unretained
;
30 using content::BrowserThread
;
32 namespace constants
= activity_log_constants
;
34 namespace extensions
{
36 const char* FullStreamUIPolicy::kTableName
= "activitylog_full";
37 const char* FullStreamUIPolicy::kTableContentFields
[] = {
38 "extension_id", "time", "action_type", "api_name", "args", "page_url",
39 "page_title", "arg_url", "other"
41 const char* FullStreamUIPolicy::kTableFieldTypes
[] = {
42 "LONGVARCHAR NOT NULL", "INTEGER", "INTEGER", "LONGVARCHAR", "LONGVARCHAR",
43 "LONGVARCHAR", "LONGVARCHAR", "LONGVARCHAR", "LONGVARCHAR"
45 const int FullStreamUIPolicy::kTableFieldCount
=
46 arraysize(FullStreamUIPolicy::kTableContentFields
);
48 FullStreamUIPolicy::FullStreamUIPolicy(Profile
* profile
)
49 : ActivityLogDatabasePolicy(
51 FilePath(chrome::kExtensionActivityLogFilename
)) {}
53 FullStreamUIPolicy::~FullStreamUIPolicy() {}
55 bool FullStreamUIPolicy::InitDatabase(sql::Connection
* db
) {
56 if (!Util::DropObsoleteTables(db
))
59 // Create the unified activity log entry table.
60 return ActivityDatabase::InitializeTable(db
,
64 arraysize(kTableContentFields
));
67 bool FullStreamUIPolicy::FlushDatabase(sql::Connection
* db
) {
68 if (queued_actions_
.empty())
71 sql::Transaction
transaction(db
);
72 if (!transaction
.Begin())
76 "INSERT INTO " + std::string(FullStreamUIPolicy::kTableName
) +
77 " (extension_id, time, action_type, api_name, args, "
78 "page_url, page_title, arg_url, other) VALUES (?,?,?,?,?,?,?,?,?)";
79 sql::Statement
statement(db
->GetCachedStatement(
80 sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
82 Action::ActionVector::size_type i
;
83 for (i
= 0; i
!= queued_actions_
.size(); ++i
) {
84 const Action
& action
= *queued_actions_
[i
];
85 statement
.Reset(true);
86 statement
.BindString(0, action
.extension_id());
87 statement
.BindInt64(1, action
.time().ToInternalValue());
88 statement
.BindInt(2, static_cast<int>(action
.action_type()));
89 statement
.BindString(3, action
.api_name());
91 statement
.BindString(4, Util::Serialize(action
.args()));
93 std::string page_url_string
= action
.SerializePageUrl();
94 if (!page_url_string
.empty()) {
95 statement
.BindString(5, page_url_string
);
97 if (!action
.page_title().empty()) {
98 statement
.BindString(6, action
.page_title());
100 std::string arg_url_string
= action
.SerializeArgUrl();
101 if (!arg_url_string
.empty()) {
102 statement
.BindString(7, arg_url_string
);
104 if (action
.other()) {
105 statement
.BindString(8, Util::Serialize(action
.other()));
108 if (!statement
.Run()) {
109 LOG(ERROR
) << "Activity log database I/O failed: " << sql_str
;
114 if (!transaction
.Commit())
117 queued_actions_
.clear();
121 scoped_ptr
<Action::ActionVector
> FullStreamUIPolicy::DoReadFilteredData(
122 const std::string
& extension_id
,
123 const Action::ActionType type
,
124 const std::string
& api_name
,
125 const std::string
& page_url
,
126 const std::string
& arg_url
,
127 const int days_ago
) {
128 // Ensure data is flushed to the database first so that we query over all
130 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately
);
131 scoped_ptr
<Action::ActionVector
> actions(new Action::ActionVector());
133 sql::Connection
* db
= GetDatabaseConnection();
135 return actions
.Pass();
138 // Build up the query based on which parameters were specified.
139 std::string where_str
= "";
140 std::string where_next
= "";
141 if (!extension_id
.empty()) {
142 where_str
+= "extension_id=?";
143 where_next
= " AND ";
145 if (!api_name
.empty()) {
146 where_str
+= where_next
+ "api_name=?";
147 where_next
= " AND ";
149 if (type
!= Action::ACTION_ANY
) {
150 where_str
+= where_next
+ "action_type=?";
151 where_next
= " AND ";
153 if (!page_url
.empty()) {
154 where_str
+= where_next
+ "page_url LIKE ?";
155 where_next
= " AND ";
157 if (!arg_url
.empty()) {
158 where_str
+= where_next
+ "arg_url LIKE ?";
161 where_str
+= where_next
+ "time BETWEEN ? AND ?";
162 std::string query_str
= base::StringPrintf(
163 "SELECT extension_id,time,action_type,api_name,args,page_url,page_title,"
164 "arg_url,other FROM %s %s %s ORDER BY time DESC LIMIT 300",
166 where_str
.empty() ? "" : "WHERE",
168 sql::Statement
query(db
->GetUniqueStatement(query_str
.c_str()));
170 if (!extension_id
.empty())
171 query
.BindString(++i
, extension_id
);
172 if (!api_name
.empty())
173 query
.BindString(++i
, api_name
);
174 if (type
!= Action::ACTION_ANY
)
175 query
.BindInt(++i
, static_cast<int>(type
));
176 if (!page_url
.empty())
177 query
.BindString(++i
, page_url
+ "%");
178 if (!arg_url
.empty())
179 query
.BindString(++i
, arg_url
+ "%");
183 Util::ComputeDatabaseTimeBounds(Now(), days_ago
, &early_bound
, &late_bound
);
184 query
.BindInt64(++i
, early_bound
);
185 query
.BindInt64(++i
, late_bound
);
188 // Execute the query and get results.
189 while (query
.is_valid() && query
.Step()) {
190 scoped_refptr
<Action
> action
=
191 new Action(query
.ColumnString(0),
192 base::Time::FromInternalValue(query
.ColumnInt64(1)),
193 static_cast<Action::ActionType
>(query
.ColumnInt(2)),
194 query
.ColumnString(3));
196 if (query
.ColumnType(4) != sql::COLUMN_TYPE_NULL
) {
197 scoped_ptr
<base::Value
> parsed_value(
198 base::JSONReader::Read(query
.ColumnString(4)));
199 if (parsed_value
&& parsed_value
->IsType(base::Value::TYPE_LIST
)) {
200 action
->set_args(make_scoped_ptr(
201 static_cast<base::ListValue
*>(parsed_value
.release())));
205 action
->ParsePageUrl(query
.ColumnString(5));
206 action
->set_page_title(query
.ColumnString(6));
207 action
->ParseArgUrl(query
.ColumnString(7));
209 if (query
.ColumnType(8) != sql::COLUMN_TYPE_NULL
) {
210 scoped_ptr
<base::Value
> parsed_value(
211 base::JSONReader::Read(query
.ColumnString(8)));
212 if (parsed_value
&& parsed_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
213 action
->set_other(make_scoped_ptr(
214 static_cast<base::DictionaryValue
*>(parsed_value
.release())));
217 actions
->push_back(action
);
220 return actions
.Pass();
223 void FullStreamUIPolicy::DoRemoveURLs(const std::vector
<GURL
>& restrict_urls
) {
224 sql::Connection
* db
= GetDatabaseConnection();
226 LOG(ERROR
) << "Unable to connect to database";
230 // Make sure any queued in memory are sent to the database before cleaning.
231 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately
);
233 // If no restrictions then then all URLs need to be removed.
234 if (restrict_urls
.empty()) {
235 sql::Statement statement
;
236 std::string sql_str
= base::StringPrintf(
237 "UPDATE %s SET page_url=NULL,page_title=NULL,arg_url=NULL",
239 statement
.Assign(db
->GetCachedStatement(
240 sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
242 if (!statement
.Run()) {
243 LOG(ERROR
) << "Removing URLs from database failed: "
244 << statement
.GetSQLStatement();
249 // If URLs are specified then restrict to only those URLs.
250 for (size_t i
= 0; i
< restrict_urls
.size(); ++i
) {
251 if (!restrict_urls
[i
].is_valid()) {
255 // Remove any matching page url info.
256 sql::Statement statement
;
257 std::string sql_str
= base::StringPrintf(
258 "UPDATE %s SET page_url=NULL,page_title=NULL WHERE page_url=?",
260 statement
.Assign(db
->GetCachedStatement(
261 sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
262 statement
.BindString(0, restrict_urls
[i
].spec());
264 if (!statement
.Run()) {
265 LOG(ERROR
) << "Removing page URL from database failed: "
266 << statement
.GetSQLStatement();
270 // Remove any matching arg urls.
271 sql_str
= base::StringPrintf("UPDATE %s SET arg_url=NULL WHERE arg_url=?",
273 statement
.Assign(db
->GetCachedStatement(
274 sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
275 statement
.BindString(0, restrict_urls
[i
].spec());
277 if (!statement
.Run()) {
278 LOG(ERROR
) << "Removing arg URL from database failed: "
279 << statement
.GetSQLStatement();
285 void FullStreamUIPolicy::DoRemoveExtensionData(
286 const std::string
& extension_id
) {
287 if (extension_id
.empty())
290 sql::Connection
* db
= GetDatabaseConnection();
292 LOG(ERROR
) << "Unable to connect to database";
296 // Make sure any queued in memory are sent to the database before cleaning.
297 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately
);
299 std::string sql_str
= base::StringPrintf(
300 "DELETE FROM %s WHERE extension_id=?", kTableName
);
301 sql::Statement statement
;
303 db
->GetCachedStatement(sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
304 statement
.BindString(0, extension_id
);
305 if (!statement
.Run()) {
306 LOG(ERROR
) << "Removing URLs for extension "
307 << extension_id
<< "from database failed: "
308 << statement
.GetSQLStatement();
312 void FullStreamUIPolicy::DoDeleteDatabase() {
313 sql::Connection
* db
= GetDatabaseConnection();
315 LOG(ERROR
) << "Unable to connect to database";
319 queued_actions_
.clear();
321 // Not wrapped in a transaction because the deletion should happen even if
322 // the vacuuming fails.
323 std::string sql_str
= base::StringPrintf("DELETE FROM %s;", kTableName
);
324 sql::Statement
statement(db
->GetCachedStatement(
325 sql::StatementID(SQL_FROM_HERE
), sql_str
.c_str()));
326 if (!statement
.Run()) {
327 LOG(ERROR
) << "Deleting the database failed: "
328 << statement
.GetSQLStatement();
332 statement
.Assign(db
->GetCachedStatement(sql::StatementID(SQL_FROM_HERE
),
334 if (!statement
.Run()) {
335 LOG(ERROR
) << "Vacuuming the database failed: "
336 << statement
.GetSQLStatement();
340 void FullStreamUIPolicy::OnDatabaseFailure() {
341 queued_actions_
.clear();
344 void FullStreamUIPolicy::OnDatabaseClose() {
348 void FullStreamUIPolicy::Close() {
349 // The policy object should have never been created if there's no DB thread.
350 DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB
));
351 ScheduleAndForget(activity_database(), &ActivityDatabase::Close
);
354 void FullStreamUIPolicy::ReadFilteredData(
355 const std::string
& extension_id
,
356 const Action::ActionType type
,
357 const std::string
& api_name
,
358 const std::string
& page_url
,
359 const std::string
& arg_url
,
362 <void(scoped_ptr
<Action::ActionVector
>)>& callback
) {
363 BrowserThread::PostTaskAndReplyWithResult(
366 base::Bind(&FullStreamUIPolicy::DoReadFilteredData
,
367 base::Unretained(this),
377 void FullStreamUIPolicy::RemoveURLs(const std::vector
<GURL
>& restrict_urls
) {
378 ScheduleAndForget(this, &FullStreamUIPolicy::DoRemoveURLs
, restrict_urls
);
381 void FullStreamUIPolicy::RemoveExtensionData(const std::string
& extension_id
) {
383 this, &FullStreamUIPolicy::DoRemoveExtensionData
, extension_id
);
386 void FullStreamUIPolicy::DeleteDatabase() {
387 ScheduleAndForget(this, &FullStreamUIPolicy::DoDeleteDatabase
);
390 scoped_refptr
<Action
> FullStreamUIPolicy::ProcessArguments(
391 scoped_refptr
<Action
> action
) const {
395 void FullStreamUIPolicy::ProcessAction(scoped_refptr
<Action
> action
) {
396 // TODO(mvrable): Right now this argument stripping updates the Action object
397 // in place, which isn't good if there are other users of the object. When
398 // database writing is moved to policy class, the modifications should be
400 action
= ProcessArguments(action
);
401 ScheduleAndForget(this, &FullStreamUIPolicy::QueueAction
, action
);
404 void FullStreamUIPolicy::QueueAction(scoped_refptr
<Action
> action
) {
405 if (activity_database()->is_db_valid()) {
406 queued_actions_
.push_back(action
);
407 activity_database()->AdviseFlush(queued_actions_
.size());
411 } // namespace extensions