Imported Upstream version 1.0beta3
[platform/upstream/syncevolution.git] / src / backends / file / FileSyncSource.cpp
1 /*
2  * Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #include "config.h"
22
23 #ifdef ENABLE_FILE
24
25 #include "FileSyncSource.h"
26
27 // SyncEvolution includes a copy of Boost header files.
28 // They are safe to use without creating additional
29 // build dependencies. boost::filesystem requires a
30 // library and therefore is avoided here. Some
31 // utility functions from SyncEvolution are used
32 // instead, plus standard C/Posix functions.
33 #include <boost/algorithm/string/case_conv.hpp>
34
35 #include <errno.h>
36 #include <unistd.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <dirent.h>
40
41 #include <syncevo/util.h>
42
43 #include <sstream>
44 #include <fstream>
45
46 #include <syncevo/SyncContext.h>
47 #include <syncevo/declarations.h>
48 SE_BEGIN_CXX
49
50 FileSyncSource::FileSyncSource(const SyncSourceParams &params,
51                                const string &dataformat) :
52     TrackingSyncSource(params),
53     m_entryCounter(0)
54 {
55     if (dataformat.empty()) {
56         throwError("a data format must be specified");
57     }
58     size_t sep = dataformat.find(':');
59     if (sep == dataformat.npos) {
60         throwError(string("data format not specified as <mime type>:<mime version>: " + dataformat));
61     }
62     m_mimeType.assign(dataformat, 0, sep);
63     m_mimeVersion = dataformat.substr(sep + 1);
64     m_supportedTypes = dataformat;
65 }
66
67 const char *FileSyncSource::getMimeType() const
68 {
69     return m_mimeType.c_str();
70 }
71
72 const char *FileSyncSource::getMimeVersion() const
73 {
74     return m_mimeVersion.c_str();
75 }
76
77 void FileSyncSource::open()
78 {
79     const string &database = getDatabaseID();
80     const string prefix("file://");
81     string basedir;
82     bool createDir = false;
83
84     // file:// is optional. It indicates that the
85     // directory is to be created.
86     if (boost::starts_with(database, prefix)) {
87         basedir = database.substr(prefix.size());
88         createDir = true;
89     } else {
90         basedir = database;
91     }
92
93     // check and, if allowed and necessary, create it
94     if (!isDir(basedir)) {
95         if (errno == ENOENT && createDir) {
96             mkdir_p(basedir.c_str());
97         } else {
98             throwError(basedir, errno);
99         }
100     }
101
102     // success!
103     m_basedir = basedir;
104 }
105
106 bool FileSyncSource::isEmpty()
107 {
108     DIR *dir = NULL;
109     bool empty = true;
110
111     try {
112         dir = opendir(m_basedir.c_str());
113         if (!dir) {
114             SyncContext::throwError(m_basedir, errno);
115         }
116         errno = 0;
117         struct dirent *entry = readdir(dir);
118         while (entry) {
119             if (strcmp(entry->d_name, ".") &&
120                 strcmp(entry->d_name, "..")) {
121                 empty = false;
122                 break;
123             }
124             entry = readdir(dir);
125         }
126         if (errno) {
127             SyncContext::throwError(m_basedir, errno);
128         }
129     } catch(...) {
130         if (dir) {
131             closedir(dir);
132         }
133         throw;
134     }
135
136     closedir(dir);
137     return empty;
138 }
139
140 void FileSyncSource::close()
141 {
142     m_basedir.clear();
143 }
144
145 FileSyncSource::Databases FileSyncSource::getDatabases()
146 {
147     Databases result;
148
149     result.push_back(Database("select database via directory path",
150                               "[file://]<path>"));
151     return result;
152 }
153
154 void FileSyncSource::listAllItems(RevisionMap_t &revisions)
155 {
156     ReadDir dirContent(m_basedir);
157
158     BOOST_FOREACH(const string &entry, dirContent) {
159         string filename = createFilename(entry);
160         string revision = getATimeString(filename);
161         long entrynum = atoll(entry.c_str());
162         if (entrynum >= m_entryCounter) {
163             m_entryCounter = entrynum + 1;
164         }
165         revisions[entry] = revision;
166     }
167 }
168
169 void FileSyncSource::readItem(const string &uid, std::string &item, bool raw)
170 {
171     string filename = createFilename(uid);
172
173     if (!ReadFile(filename, item)) {
174         throwError(filename + ": reading failed", errno);
175     }
176 }
177
178 TrackingSyncSource::InsertItemResult FileSyncSource::insertItem(const string &uid, const std::string &item, bool raw)
179 {
180     string newuid = uid;
181     string creationTime;
182     string filename;
183
184     // Inserting a new and updating an existing item often uses
185     // very similar code. In this case only the code for determining
186     // the filename differs.
187     //
188     // In other sync sources the database might also have limitations
189     // for the content of different items, for example, only one
190     // VCALENDAR:EVENT with a certain UID. If the server does not
191     // recognize that and sends a new item which collides with an
192     // existing one, then the existing one should be updated.
193
194     if (uid.size()) {
195         // valid local ID: update that file
196         filename = createFilename(uid);
197     } else {
198         // no local ID: create new file
199         while (true) {
200             ostringstream buff;
201             buff << m_entryCounter;
202             filename = createFilename(buff.str());
203
204             // only create and truncate if file does not
205             // exist yet, otherwise retry with next counter
206             struct stat dummy;
207             if (stat(filename.c_str(), &dummy)) {
208                 if (errno == ENOENT) {
209                     newuid = buff.str();
210                     break;
211                 } else {
212                     throwError(filename, errno);
213                 }
214             }
215
216             m_entryCounter++;
217         }
218     }
219
220     ofstream out;
221     out.open(filename.c_str());
222     out.write(item.c_str(), item.size());
223     out.close();
224     if (!out.good()) {
225         throwError(filename + ": writing failed", errno);
226     }
227
228     return InsertItemResult(newuid,
229                             getATimeString(filename),
230                             false /* true if adding item was turned into update */);
231 }
232
233
234 void FileSyncSource::removeItem(const string &uid)
235 {
236     string filename = createFilename(uid);
237
238     if (unlink(filename.c_str()) &&
239         errno != ENOENT) {
240         throwError(filename, errno);
241     }
242 }
243
244 string FileSyncSource::getATimeString(const string &filename)
245 {
246     struct stat buf;
247     if (stat(filename.c_str(), &buf)) {
248         throwError(filename, errno);
249     }
250     time_t mtime = buf.st_mtime;
251
252     ostringstream revision;
253     revision << mtime;
254
255     return revision.str();
256 }
257
258 string FileSyncSource::createFilename(const string &entry)
259 {
260     string filename = m_basedir + "/" + entry;
261     return filename;
262 }
263
264 SE_END_CXX
265
266 #endif /* ENABLE_FILE */
267
268 #ifdef ENABLE_MODULES
269 # include "FileSyncSourceRegister.cpp"
270 #endif