1/*
2 * Copyright (C) 2012 Research In Motion Limited. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19#include "config.h"
20
21#include "AutofillBackingStore.h"
22
23#include "FileSystem.h"
24#include "SQLiteStatement.h"
25#include <BlackBerryPlatformSettings.h>
26
27#define HANDLE_SQL_EXEC_FAILURE(statement, returnValue, ...) \
28    if (statement) { \
29        LOG_ERROR(__VA_ARGS__); \
30        return returnValue; \
31    }
32
33namespace WebCore {
34
35AutofillBackingStore& autofillBackingStore()
36{
37    DEFINE_STATIC_LOCAL(AutofillBackingStore, backingStore, ());
38    if (!backingStore.m_database.isOpen())
39        backingStore.open(pathByAppendingComponent(BlackBerry::Platform::Settings::instance()->applicationDataDirectory().c_str(), "/autofill.db"));
40    return backingStore;
41}
42
43AutofillBackingStore::AutofillBackingStore()
44    : m_addStatement(0)
45    , m_updateStatement(0)
46    , m_containsStatement(0)
47    , m_getStatement(0)
48{
49}
50
51AutofillBackingStore::~AutofillBackingStore()
52{
53    delete m_addStatement;
54    m_addStatement = 0;
55    delete m_updateStatement;
56    m_updateStatement = 0;
57    delete m_containsStatement;
58    m_containsStatement = 0;
59    delete m_getStatement;
60    m_getStatement = 0;
61
62    if (m_database.isOpen())
63        m_database.close();
64}
65
66bool AutofillBackingStore::open(const String& dbPath)
67{
68    ASSERT(!m_database.isOpen());
69
70    HANDLE_SQL_EXEC_FAILURE(!m_database.open(dbPath), false,
71        "Failed to open database file %s for autofill database", dbPath.utf8().data());
72
73    if (!m_database.tableExists("autofill")) {
74        HANDLE_SQL_EXEC_FAILURE(!m_database.executeCommand("CREATE TABLE autofill (id INTEGER PRIMARY KEY, name VARCHAR NOT NULL, value VARCHAR NOT NULL, count INTEGER DEFAULT 1)"),
75            false, "Failed to create table autofill for autofill database");
76
77        // Create index for table autofill.
78        HANDLE_SQL_EXEC_FAILURE(!m_database.executeCommand("CREATE INDEX autofill_name ON autofill (name)"),
79            false, "Failed to create autofill_name index for table autofill");
80    }
81
82    // Prepare the statements.
83    m_addStatement = new SQLiteStatement(m_database, "INSERT INTO autofill (name, value) VALUES (?, ?)");
84    HANDLE_SQL_EXEC_FAILURE(m_addStatement->prepare() != SQLResultOk,
85        false, "Failed to prepare add statement");
86
87    m_updateStatement = new SQLiteStatement(m_database, "UPDATE autofill SET count = (SELECT count + 1 from autofill WHERE name = ? AND value = ?) WHERE name = ? AND value = ?");
88    HANDLE_SQL_EXEC_FAILURE(m_updateStatement->prepare() != SQLResultOk,
89        false, "Failed to prepare update statement");
90
91    m_containsStatement = new SQLiteStatement(m_database, "SELECT COUNT(*) FROM autofill WHERE name = ? AND value = ?");
92    HANDLE_SQL_EXEC_FAILURE(m_containsStatement->prepare() != SQLResultOk,
93        false, "Failed to prepare contains statement");
94
95    m_getStatement = new SQLiteStatement(m_database, "SELECT value FROM autofill WHERE name = ? and value like ? ORDER BY count DESC");
96    HANDLE_SQL_EXEC_FAILURE(m_getStatement->prepare() != SQLResultOk,
97        false, "Failed to prepare get statement");
98
99    return true;
100}
101
102bool AutofillBackingStore::add(const String& name, const String& value)
103{
104    if (name.isEmpty() || value.isEmpty())
105        return false;
106
107    ASSERT(m_database.isOpen());
108    ASSERT(m_database.tableExists("autofill"));
109
110    if (contains(name, value))
111        return update(name, value);
112
113    if (!m_addStatement)
114        return false;
115
116    m_addStatement->bindText(1, name);
117    m_addStatement->bindText(2, value);
118
119    int result = m_addStatement->step();
120    m_addStatement->reset();
121    HANDLE_SQL_EXEC_FAILURE(result != SQLResultDone, false,
122        "Failed to add autofill item into table autofill - %i", result);
123
124    return true;
125}
126
127bool AutofillBackingStore::update(const String& name, const String& value)
128{
129    if (!m_updateStatement)
130        return false;
131
132    m_updateStatement->bindText(1, name);
133    m_updateStatement->bindText(2, value);
134    m_updateStatement->bindText(3, name);
135    m_updateStatement->bindText(4, value);
136
137    int result = m_updateStatement->step();
138    m_updateStatement->reset();
139    HANDLE_SQL_EXEC_FAILURE(result != SQLResultDone, false,
140        "Failed to update autofill item in table autofill - %i", result);
141
142    return true;
143}
144
145bool AutofillBackingStore::contains(const String& name, const String& value) const
146{
147    if (!m_containsStatement)
148        return false;
149
150    m_containsStatement->bindText(1, name);
151    m_containsStatement->bindText(2, value);
152
153    int result = m_containsStatement->step();
154    int numberOfRows = m_containsStatement->getColumnInt(0);
155    m_containsStatement->reset();
156    HANDLE_SQL_EXEC_FAILURE(result != SQLResultRow, false,
157        "Failed to execute select autofill item from table autofill in contains - %i", result);
158
159    return numberOfRows;
160}
161
162Vector<String> AutofillBackingStore::get(const String& name, const String& valueHint)
163{
164    ASSERT(m_database.isOpen());
165    ASSERT(m_database.tableExists("autofill"));
166
167    Vector<String> candidates;
168    if (name.isEmpty() || valueHint.isEmpty() || !m_getStatement)
169        return candidates;
170
171    String value = valueHint + "%";
172    m_getStatement->bindText(1, name);
173    m_getStatement->bindText(2, value);
174
175    int result;
176    while ((result = m_getStatement->step()) == SQLResultRow)
177        candidates.append(m_getStatement->getColumnText(0));
178    m_getStatement->reset();
179    HANDLE_SQL_EXEC_FAILURE(result != SQLResultDone, candidates,
180        "Failed to execute select autofill item from table autofill in get - %i", result);
181
182    return candidates;
183}
184
185bool AutofillBackingStore::clear()
186{
187    ASSERT(m_database.isOpen());
188    ASSERT(m_database.tableExists("autofill"));
189
190    HANDLE_SQL_EXEC_FAILURE(!m_database.executeCommand("DELETE FROM autofill"),
191        false, "Failed to clear table autofill");
192
193    return true;
194}
195
196} // namespace WebCore
197