001/** 002 * 003 * Copyright © 2011-2019 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.util.logging.Level; 026import java.util.logging.Logger; 027 028import org.jivesoftware.smack.util.PacketParserUtils; 029import org.jivesoftware.smack.util.stringencoder.Base32; 030import org.jivesoftware.smack.util.stringencoder.StringEncoder; 031 032import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 033 034/** 035 * Simple implementation of an EntityCapsPersistentCache that uses a directory 036 * to store the Caps information for every known node. Every node is represented 037 * by a file. 038 * 039 * @author Florian Schmaus 040 * 041 */ 042public class SimpleDirectoryPersistentCache implements EntityCapsPersistentCache { 043 private static final Logger LOGGER = Logger.getLogger(SimpleDirectoryPersistentCache.class.getName()); 044 045 private final File cacheDir; 046 private final StringEncoder<String> filenameEncoder; 047 048 /** 049 * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the 050 * cacheDir exists and that it's an directory. 051 * <p> 052 * Default filename encoder {@link Base32}, as this will work on all 053 * file systems, both case sensitive and case insensitive. It does however 054 * produce longer filenames. 055 * 056 * @param cacheDir TODO javadoc me please 057 */ 058 public SimpleDirectoryPersistentCache(File cacheDir) { 059 this(cacheDir, Base32.getStringEncoder()); 060 } 061 062 /** 063 * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the 064 * cacheDir exists and that it's an directory. 065 * 066 * If your cacheDir is case insensitive then make sure to set the 067 * StringEncoder to {@link Base32} (which is the default). 068 * 069 * @param cacheDir The directory where the cache will be stored. 070 * @param filenameEncoder Encodes the node string into a filename. 071 */ 072 public SimpleDirectoryPersistentCache(File cacheDir, StringEncoder<String> filenameEncoder) { 073 if (!cacheDir.exists()) 074 throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist"); 075 if (!cacheDir.isDirectory()) 076 throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory"); 077 078 this.cacheDir = cacheDir; 079 this.filenameEncoder = filenameEncoder; 080 } 081 082 @Override 083 public void addDiscoverInfoByNodePersistent(String nodeVer, DiscoverInfo info) { 084 File nodeFile = getFileFor(nodeVer); 085 try { 086 if (nodeFile.createNewFile()) 087 writeInfoToFile(nodeFile, info); 088 } catch (IOException e) { 089 LOGGER.log(Level.SEVERE, "Failed to write disco info to file", e); 090 } 091 } 092 093 @Override 094 public DiscoverInfo lookup(String nodeVer) { 095 File nodeFile = getFileFor(nodeVer); 096 if (!nodeFile.isFile()) { 097 return null; 098 } 099 DiscoverInfo info = null; 100 try { 101 info = restoreInfoFromFile(nodeFile); 102 } 103 catch (Exception e) { 104 LOGGER.log(Level.WARNING, "Coud not restore info from file", e); 105 } 106 return info; 107 } 108 109 private File getFileFor(String nodeVer) { 110 String filename = filenameEncoder.encode(nodeVer); 111 return new File(cacheDir, filename); 112 } 113 114 @Override 115 public void emptyCache() { 116 File[] files = cacheDir.listFiles(); 117 if (files == null) { 118 return; 119 } 120 for (File f : files) { 121 f.delete(); 122 } 123 } 124 125 /** 126 * Writes the DiscoverInfo stanza to an file 127 * 128 * @param file TODO javadoc me please 129 * @param info TODO javadoc me please 130 * @throws IOException if an I/O error occurred. 131 */ 132 private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException { 133 try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(file))) { 134 dos.writeUTF(info.toXML().toString()); 135 } 136 } 137 138 /** 139 * Tries to restore an DiscoverInfo stanza from a file. 140 * 141 * @param file TODO javadoc me please 142 * @return the restored DiscoverInfo 143 * @throws Exception if an exception occurs. 144 */ 145 private static DiscoverInfo restoreInfoFromFile(File file) throws Exception { 146 String fileContent; 147 try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) { 148 fileContent = dis.readUTF(); 149 } 150 if (fileContent == null) { 151 return null; 152 } 153 return PacketParserUtils.parseStanza(fileContent); 154 } 155}