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 17 package org.jivesoftware.smackx.entitycaps.cache; 18 19 import java.io.DataInputStream; 20 import java.io.DataOutputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.Reader; 26 import java.io.StringReader; 27 28 import org.jivesoftware.smack.packet.IQ; 29 import org.jivesoftware.smack.provider.IQProvider; 30 import org.jivesoftware.smack.util.Base32Encoder; 31 import org.jivesoftware.smack.util.Base64Encoder; 32 import org.jivesoftware.smack.util.StringEncoder; 33 import org.jivesoftware.smackx.entitycaps.EntityCapsManager; 34 import org.jivesoftware.smackx.packet.DiscoverInfo; 35 import org.jivesoftware.smackx.provider.DiscoverInfoProvider; 36 import org.xmlpull.v1.XmlPullParserFactory; 37 import org.xmlpull.v1.XmlPullParser; 38 import 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 */ 48 public 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