1/**
2 *  Copyright 2011 Florian Schmaus
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17package org.jivesoftware.smackx.entitycaps.cache;
18
19import java.io.DataInputStream;
20import java.io.DataOutputStream;
21import java.io.File;
22import java.io.FileInputStream;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.Reader;
26import java.io.StringReader;
27
28import org.jivesoftware.smack.packet.IQ;
29import org.jivesoftware.smack.provider.IQProvider;
30import org.jivesoftware.smack.util.Base32Encoder;
31import org.jivesoftware.smack.util.Base64Encoder;
32import org.jivesoftware.smack.util.StringEncoder;
33import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
34import org.jivesoftware.smackx.packet.DiscoverInfo;
35import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
36import org.xmlpull.v1.XmlPullParserFactory;
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40/**
41 * Simple implementation of an EntityCapsPersistentCache that uses a directory
42 * to store the Caps information for every known node. Every node is represented
43 * by an file.
44 *
45 * @author Florian Schmaus
46 *
47 */
48public class SimpleDirectoryPersistentCache implements EntityCapsPersistentCache {
49
50    private File cacheDir;
51    private StringEncoder filenameEncoder;
52
53    /**
54     * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the
55     * cacheDir exists and that it's an directory.
56     * <p>
57     * Default filename encoder {@link Base32Encoder}, as this will work on all
58     * filesystems, both case sensitive and case insensitive.  It does however
59     * produce longer filenames.
60     *
61     * @param cacheDir
62     */
63    public SimpleDirectoryPersistentCache(File cacheDir) {
64        this(cacheDir, Base32Encoder.getInstance());
65    }
66
67    /**
68     * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the
69     * cacheDir exists and that it's an directory.
70     *
71     * If your cacheDir is case insensitive then make sure to set the
72     * StringEncoder to {@link Base32Encoder} (which is the default).
73     *
74     * @param cacheDir The directory where the cache will be stored.
75     * @param filenameEncoder Encodes the node string into a filename.
76     */
77    public SimpleDirectoryPersistentCache(File cacheDir, StringEncoder filenameEncoder) {
78        if (!cacheDir.exists())
79            throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist");
80        if (!cacheDir.isDirectory())
81            throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory");
82
83        this.cacheDir = cacheDir;
84        this.filenameEncoder = filenameEncoder;
85    }
86
87    @Override
88    public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) {
89        String filename = filenameEncoder.encode(node);
90        File nodeFile = new File(cacheDir, filename);
91        try {
92            if (nodeFile.createNewFile())
93                writeInfoToFile(nodeFile, info);
94        } catch (IOException e) {
95            e.printStackTrace();
96        }
97    }
98
99    @Override
100    public void replay() throws IOException {
101        File[] files = cacheDir.listFiles();
102        for (File f : files) {
103            String node = filenameEncoder.decode(f.getName());
104            DiscoverInfo info = restoreInfoFromFile(f);
105            if (info == null)
106                continue;
107
108            EntityCapsManager.addDiscoverInfoByNode(node, info);
109        }
110    }
111
112    public void emptyCache() {
113        File[] files = cacheDir.listFiles();
114        for (File f : files) {
115            f.delete();
116        }
117    }
118
119    /**
120     * Writes the DiscoverInfo packet to an file
121     *
122     * @param file
123     * @param info
124     * @throws IOException
125     */
126    private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException {
127        DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
128        try {
129            dos.writeUTF(info.toXML());
130        } finally {
131            dos.close();
132        }
133    }
134
135    /**
136     * Tries to restore an DiscoverInfo packet from a file.
137     *
138     * @param file
139     * @return
140     * @throws IOException
141     */
142    private static DiscoverInfo restoreInfoFromFile(File file) throws IOException {
143        DataInputStream dis = new DataInputStream(new FileInputStream(file));
144        String fileContent = null;
145        String id;
146        String from;
147        String to;
148
149        try {
150            fileContent = dis.readUTF();
151        } finally {
152            dis.close();
153        }
154        if (fileContent == null)
155            return null;
156
157        Reader reader = new StringReader(fileContent);
158        XmlPullParser parser;
159        try {
160            parser = XmlPullParserFactory.newInstance().newPullParser();
161            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
162            parser.setInput(reader);
163        } catch (XmlPullParserException xppe) {
164            xppe.printStackTrace();
165            return null;
166        }
167
168        DiscoverInfo iqPacket;
169        IQProvider provider = new DiscoverInfoProvider();
170
171        // Parse the IQ, we only need the id
172        try {
173            parser.next();
174            id = parser.getAttributeValue("", "id");
175            from = parser.getAttributeValue("", "from");
176            to = parser.getAttributeValue("", "to");
177            parser.next();
178        } catch (XmlPullParserException e1) {
179            return null;
180        }
181
182        try {
183            iqPacket = (DiscoverInfo) provider.parseIQ(parser);
184        } catch (Exception e) {
185            return null;
186        }
187
188        iqPacket.setPacketID(id);
189        iqPacket.setFrom(from);
190        iqPacket.setTo(to);
191        iqPacket.setType(IQ.Type.RESULT);
192        return iqPacket;
193    }
194}
195