001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.database.dao;
019
020import java.sql.Connection;
021import java.sql.PreparedStatement;
022import java.sql.ResultSet;
023import java.sql.SQLException;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.logging.Level;
027import java.util.logging.Logger;
028
029import org.syncany.database.VectorClock;
030import org.syncany.plugins.transfer.files.DatabaseRemoteFile;
031
032/**
033 * The application data access object (DAO) writes and queries the SQL database for
034 * general information about the application.
035 *
036 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
037 */
038public class ApplicationSqlDao extends AbstractSqlDao {
039        protected static final Logger logger = Logger.getLogger(ApplicationSqlDao.class.getSimpleName());
040
041        public ApplicationSqlDao(Connection connection) {
042                super(connection);
043        }
044
045        /**
046         * Writes a list of {@link DatabaseRemoteFile}s to the database using the given connection.
047         * <p><b>Note:</b> This method executes, but does not commit the query.
048         *
049         * @param remoteDatabases List of remote databases to write to the database
050         * @throws SQLException If the SQL statement fails
051         */
052        public void writeKnownRemoteDatabases(List<DatabaseRemoteFile> remoteDatabases) throws SQLException {
053                if (remoteDatabases.size() == 0) {
054                        // Nothing to write
055                        return;
056                }
057                PreparedStatement preparedStatement = getStatement("application.insert.all.persistNewKnownRemoteDatabases.sql");
058
059                for (DatabaseRemoteFile databaseRemoteFile : remoteDatabases) {
060                        preparedStatement.setString(1, databaseRemoteFile.getClientName());
061                        preparedStatement.setInt(2, (int) databaseRemoteFile.getClientVersion());
062                        
063                        preparedStatement.addBatch();
064                }
065
066                preparedStatement.executeBatch();
067        }
068
069        public VectorClock getHighestKnownDatabaseFilenameNumbers() {
070                VectorClock highestKnownDatabaseFilenameNumbers = new VectorClock();
071
072                try (PreparedStatement preparedStatement = getStatement("application.select.all.getHighestKnownDatabaseFilenameNumbers.sql")) {
073                        try (ResultSet resultSet = preparedStatement.executeQuery()) {
074                                while (resultSet.next()) {
075                                        String clientName = resultSet.getString("client");
076                                        int fileNumber = resultSet.getInt("filenumber");
077
078                                        highestKnownDatabaseFilenameNumbers.put(clientName, (long) fileNumber);
079                                }
080
081                                return highestKnownDatabaseFilenameNumbers;
082                        }
083                }
084                catch (Exception e) {
085                        throw new RuntimeException(e);
086                }
087        }
088
089        /**
090         * Queries the database for already known {@link DatabaseRemoteFile}s and returns a
091         * list of all of them.
092         *
093         * @return Returns a list of all known/processed remote databases
094         */
095        public List<DatabaseRemoteFile> getKnownDatabases() {
096                List<DatabaseRemoteFile> knownDatabases = new ArrayList<DatabaseRemoteFile>();
097
098                try (PreparedStatement preparedStatement = getStatement("application.select.all.getKnownDatabases.sql")) {
099                        try (ResultSet resultSet = preparedStatement.executeQuery()) {
100                                while (resultSet.next()) {
101                                        String clientName = resultSet.getString("client");
102                                        int fileNumber = resultSet.getInt("filenumber");
103                                        
104                                        knownDatabases.add(new DatabaseRemoteFile(clientName, fileNumber));
105                                }
106
107                                return knownDatabases;
108                        }
109                }
110                catch (Exception e) {
111                        throw new RuntimeException(e);
112                }
113        }
114
115        public void removeKnownDatabases() {
116                try (PreparedStatement preparedStatement = getStatement("application.delete.all.removeKnownDatabases.sql")) {
117                        preparedStatement.execute();
118                }
119                catch (Exception e) {
120                        throw new RuntimeException(e);
121                }
122        }
123
124        /**
125         * Deletes all metadata, including known databases.
126         */
127        public void deleteAll() {
128                try {
129                        runScript("script.delete.all.sql");
130                }
131                catch (Exception e) {
132                        throw new RuntimeException(e);
133                }
134        }
135
136        /**
137         * Shuts down the HSQL database, i.e. persists all data, closes all connections
138         * and unlocks the database for other processes.
139         *
140         * <p>The command sends the <b><code>SHUTDOWN</code></b> SQL command.
141         */
142        public void shutdown() {
143                try {
144                        connection.prepareStatement("shutdown").execute();
145                }
146                catch (SQLException e) {
147                        logger.log(Level.FINE, "Could not shutdown the connection", e);
148                }
149                finally {
150                        try {
151                                connection.close();
152                        }
153                        catch (SQLException e) {
154                                logger.log(Level.FINE, "Could not close the connection", e);
155                        }
156                }
157        }
158
159        public Long getCleanupNumber() {
160                return readSettingAsLong("cleanupNumber");
161        }
162
163        public Long getCleanupTime() {
164                return readSettingAsLong("cleanupTime");
165        }
166
167        public void writeCleanupNumber(long cleanupNumber) {
168                writeSetting("cleanupNumber", "" + cleanupNumber);
169        }
170
171        public void writeCleanupTime(long cleanupTime) {
172                writeSetting("cleanupTime", "" + cleanupTime);
173        }
174
175        public Long readSettingAsLong(String key) {
176                try {
177                        String strValue = readSetting(key);
178
179                        if (strValue != null) {
180                                return Long.parseLong(strValue);
181                        }
182                        else {
183                                return null;
184                        }
185                }
186                catch (Exception e) {
187                        throw new RuntimeException(e);
188                }
189        }
190
191        public String readSetting(String key) {
192                try (PreparedStatement preparedStatement = getStatement("application.select.all.readGeneralSettings.sql")) {
193                        preparedStatement.setString(1, key);
194
195                        try (ResultSet resultSet = preparedStatement.executeQuery()) {
196                                if (resultSet.next()) {
197                                        String value = resultSet.getString("value");
198                                        logger.log(Level.INFO, "SQL (general_settings): Read " + key + "  = " + value);
199
200                                        return value;
201                                }
202                                else {
203                                        logger.log(Level.INFO, "SQL (general_settings): Read " + key + "  = (not set)");
204                                        return null;
205                                }
206                        }
207                }
208                catch (Exception e) {
209                        throw new RuntimeException(e);
210                }
211        }
212
213        public void writeSetting(String key, String value) {
214                logger.log(Level.INFO, "SQL (general_settings): Writing " + key + "  = " + value);
215
216                try (PreparedStatement preparedStatement = getStatement("application.insert.all.writeGeneralSettings.sql")) {
217                        preparedStatement.setString(1, key);
218                        preparedStatement.setString(2, value);
219
220                        preparedStatement.execute();
221                }
222                catch (Exception e) {
223                        throw new RuntimeException(e);
224                }
225        }
226}