001/** 002 * 003 * Copyright © 2011 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.caps.cache; 018 019import java.io.DataInputStream; 020import java.io.DataOutputStream; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.Reader; 026import java.io.StringReader; 027import java.util.logging.Level; 028import java.util.logging.Logger; 029 030import org.jivesoftware.smack.packet.IQ; 031import org.jivesoftware.smack.provider.IQProvider; 032import org.jivesoftware.smack.util.Base32Encoder; 033import org.jivesoftware.smack.util.PacketParserUtils; 034import org.jivesoftware.smack.util.StringEncoder; 035import org.jivesoftware.smackx.caps.EntityCapsManager; 036import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 037import org.jivesoftware.smackx.disco.provider.DiscoverInfoProvider; 038import org.xmlpull.v1.XmlPullParser; 039import org.xmlpull.v1.XmlPullParserException; 040 041/** 042 * Simple implementation of an EntityCapsPersistentCache that uses a directory 043 * to store the Caps information for every known node. Every node is represented 044 * by a file. 045 * 046 * @author Florian Schmaus 047 * 048 */ 049public class SimpleDirectoryPersistentCache implements EntityCapsPersistentCache { 050 private static final Logger LOGGER = Logger.getLogger(SimpleDirectoryPersistentCache.class.getName()); 051 052 private File cacheDir; 053 private StringEncoder filenameEncoder; 054 055 /** 056 * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the 057 * cacheDir exists and that it's an directory. 058 * <p> 059 * Default filename encoder {@link Base32Encoder}, as this will work on all 060 * file systems, both case sensitive and case insensitive. It does however 061 * produce longer filenames. 062 * 063 * @param cacheDir 064 */ 065 public SimpleDirectoryPersistentCache(File cacheDir) { 066 this(cacheDir, Base32Encoder.getInstance()); 067 } 068 069 /** 070 * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the 071 * cacheDir exists and that it's an directory. 072 * 073 * If your cacheDir is case insensitive then make sure to set the 074 * StringEncoder to {@link Base32Encoder} (which is the default). 075 * 076 * @param cacheDir The directory where the cache will be stored. 077 * @param filenameEncoder Encodes the node string into a filename. 078 */ 079 public SimpleDirectoryPersistentCache(File cacheDir, StringEncoder filenameEncoder) { 080 if (!cacheDir.exists()) 081 throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist"); 082 if (!cacheDir.isDirectory()) 083 throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory"); 084 085 this.cacheDir = cacheDir; 086 this.filenameEncoder = filenameEncoder; 087 } 088 089 @Override 090 public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) { 091 String filename = filenameEncoder.encode(node); 092 File nodeFile = new File(cacheDir, filename); 093 try { 094 if (nodeFile.createNewFile()) 095 writeInfoToFile(nodeFile, info); 096 } catch (IOException e) { 097 LOGGER.log(Level.SEVERE, "Failed to write disco info to file", e); 098 } 099 } 100 101 @Override 102 public void replay() throws IOException { 103 File[] files = cacheDir.listFiles(); 104 for (File f : files) { 105 String node = filenameEncoder.decode(f.getName()); 106 DiscoverInfo info = restoreInfoFromFile(f); 107 if (info == null) 108 continue; 109 110 EntityCapsManager.addDiscoverInfoByNode(node, info); 111 } 112 } 113 114 public void emptyCache() { 115 File[] files = cacheDir.listFiles(); 116 for (File f : files) { 117 f.delete(); 118 } 119 } 120 121 /** 122 * Writes the DiscoverInfo packet to an file 123 * 124 * @param file 125 * @param info 126 * @throws IOException 127 */ 128 private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException { 129 DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); 130 try { 131 dos.writeUTF(info.toXML().toString()); 132 } finally { 133 dos.close(); 134 } 135 } 136 137 /** 138 * Tries to restore an DiscoverInfo packet from a file. 139 * 140 * @param file 141 * @return the restored DiscoverInfo 142 * @throws IOException 143 */ 144 private static DiscoverInfo restoreInfoFromFile(File file) throws IOException { 145 DataInputStream dis = new DataInputStream(new FileInputStream(file)); 146 String fileContent = null; 147 String id; 148 String from; 149 String to; 150 151 try { 152 fileContent = dis.readUTF(); 153 } finally { 154 dis.close(); 155 } 156 if (fileContent == null) 157 return null; 158 159 Reader reader = new StringReader(fileContent); 160 XmlPullParser parser; 161 try { 162 parser = PacketParserUtils.newXmppParser(); 163 parser.setInput(reader); 164 } catch (XmlPullParserException xppe) { 165 LOGGER.log(Level.SEVERE, "Exception initializing parser", xppe); 166 return null; 167 } 168 169 DiscoverInfo iqPacket; 170 IQProvider provider = new DiscoverInfoProvider(); 171 172 // Parse the IQ, we only need the id 173 try { 174 parser.next(); 175 id = parser.getAttributeValue("", "id"); 176 from = parser.getAttributeValue("", "from"); 177 to = parser.getAttributeValue("", "to"); 178 parser.next(); 179 } catch (XmlPullParserException e1) { 180 return null; 181 } 182 183 try { 184 iqPacket = (DiscoverInfo) provider.parseIQ(parser); 185 } catch (Exception e) { 186 return null; 187 } 188 189 iqPacket.setPacketID(id); 190 iqPacket.setFrom(from); 191 iqPacket.setTo(to); 192 iqPacket.setType(IQ.Type.RESULT); 193 return iqPacket; 194 } 195}