DirectoryRosterStore.java

  1. /**
  2.  *
  3.  * Copyright 2013-2015 the original author or authors, 2020 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.roster.rosterstore;

  18. import java.io.File;
  19. import java.io.FileNotFoundException;
  20. import java.io.FileReader;
  21. import java.io.IOException;
  22. import java.io.Reader;
  23. import java.util.ArrayList;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.List;
  27. import java.util.logging.Level;
  28. import java.util.logging.Logger;

  29. import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
  30. import org.jivesoftware.smack.roster.provider.RosterPacketProvider;
  31. import org.jivesoftware.smack.util.FileUtils;
  32. import org.jivesoftware.smack.util.PacketParserUtils;
  33. import org.jivesoftware.smack.util.stringencoder.Base32;
  34. import org.jivesoftware.smack.xml.XmlPullParser;
  35. import org.jivesoftware.smack.xml.XmlPullParserException;

  36. import org.jxmpp.jid.Jid;

  37. /**
  38.  * Stores roster entries as specified by RFC 6121 for roster versioning
  39.  * in a set of files.
  40.  *
  41.  * @author Lars Noschinski
  42.  * @author Fabian Schuetz
  43.  * @author Florian Schmaus
  44.  */
  45. public final class DirectoryRosterStore implements RosterStore {

  46.     private final File fileDir;

  47.     private static final String ENTRY_PREFIX = "entry-";
  48.     private static final String VERSION_FILE_NAME = "__version__";
  49.     private static final String STORE_ID = "DEFAULT_ROSTER_STORE";
  50.     private static final Logger LOGGER = Logger.getLogger(DirectoryRosterStore.class.getName());

  51.     private static boolean rosterDirFilter(File file) {
  52.         String name = file.getName();
  53.         return name.startsWith(ENTRY_PREFIX);
  54.     }

  55.     /**
  56.      * @param baseDir TODO javadoc me please
  57.      *            will be the directory where all roster entries are stored. One
  58.      *            file for each entry, such that file.name = entry.username.
  59.      *            There is also one special file '__version__' that contains the
  60.      *            current version string.
  61.      */
  62.     private DirectoryRosterStore(final File baseDir) {
  63.         this.fileDir = baseDir;
  64.     }

  65.     /**
  66.      * Creates a new roster store on disk.
  67.      *
  68.      * @param baseDir TODO javadoc me please
  69.      *            The directory to create the store in. The directory should
  70.      *            be empty
  71.      * @return A {@link DirectoryRosterStore} instance if successful,
  72.      *         <code>null</code> else.
  73.      */
  74.     public static DirectoryRosterStore init(final File baseDir) {
  75.         DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
  76.         if (store.setRosterVersion("")) {
  77.             return store;
  78.         }
  79.         else {
  80.             return null;
  81.         }
  82.     }

  83.     /**
  84.      * Opens a roster store.
  85.      * @param baseDir TODO javadoc me please
  86.      *            The directory containing the roster store.
  87.      * @return A {@link DirectoryRosterStore} instance if successful,
  88.      *         <code>null</code> else.
  89.      */
  90.     public static DirectoryRosterStore open(final File baseDir) {
  91.         DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
  92.         String s = FileUtils.readFile(store.getVersionFile());
  93.         if (s != null && s.startsWith(STORE_ID + "\n")) {
  94.             return store;
  95.         }
  96.         else {
  97.             return null;
  98.         }
  99.     }

  100.     private File getVersionFile() {
  101.         return new File(fileDir, VERSION_FILE_NAME);
  102.     }

  103.     @Override
  104.     public List<Item> getEntries() {
  105.         List<Item> entries = new ArrayList<>();

  106.         for (File file : fileDir.listFiles(DirectoryRosterStore::rosterDirFilter)) {
  107.             Item entry = readEntry(file);
  108.             if (entry == null) {
  109.                 // Roster directory store corrupt. Abort and signal this by returning null.
  110.                 return null;
  111.             }
  112.             entries.add(entry);
  113.         }

  114.         return entries;
  115.     }

  116.     @Override
  117.     public Item getEntry(Jid bareJid) {
  118.         return readEntry(getBareJidFile(bareJid));
  119.     }

  120.     @Override
  121.     public String getRosterVersion() {
  122.         String s = FileUtils.readFile(getVersionFile());
  123.         if (s == null) {
  124.             return null;
  125.         }
  126.         String[] lines = s.split("\n", 2);
  127.         if (lines.length < 2) {
  128.             return null;
  129.         }
  130.         return lines[1];
  131.     }

  132.     private boolean setRosterVersion(String version) {
  133.         return FileUtils.writeFile(getVersionFile(), STORE_ID + "\n" + version);
  134.     }

  135.     @Override
  136.     public boolean addEntry(Item item, String version) {
  137.         return addEntryRaw(item) && setRosterVersion(version);
  138.     }

  139.     @Override
  140.     public boolean removeEntry(Jid bareJid, String version) {
  141.         return getBareJidFile(bareJid).delete() && setRosterVersion(version);
  142.     }

  143.     @Override
  144.     public boolean resetEntries(Collection<Item> items, String version) {
  145.         for (File file : fileDir.listFiles(DirectoryRosterStore::rosterDirFilter)) {
  146.             file.delete();
  147.         }
  148.         for (Item item : items) {
  149.             if (!addEntryRaw(item)) {
  150.                 return false;
  151.             }
  152.         }
  153.         return setRosterVersion(version);
  154.     }


  155.     @Override
  156.     public void resetStore() {
  157.         resetEntries(Collections.<Item>emptyList(), "");
  158.     }

  159.     @SuppressWarnings("DefaultCharset")
  160.     private static Item readEntry(File file) {
  161.         Reader reader;
  162.         try {
  163.             // TODO: Use Files.newBufferedReader() once Smack's minimum Android API level is 26 or higher.
  164.             reader = new FileReader(file);
  165.         } catch (FileNotFoundException e) {
  166.             LOGGER.log(Level.FINE, "Roster entry file not found", e);
  167.             return null;
  168.         }

  169.         try {
  170.             XmlPullParser parser = PacketParserUtils.getParserFor(reader);
  171.             Item item = RosterPacketProvider.parseItem(parser);
  172.             reader.close();
  173.             return item;
  174.         } catch (XmlPullParserException | IOException | IllegalArgumentException e) {
  175.             boolean deleted = file.delete();
  176.             String message = "Exception while parsing roster entry.";
  177.             if (deleted) {
  178.                 message += " File was deleted.";
  179.             }
  180.             LOGGER.log(Level.SEVERE, message, e);
  181.             return null;
  182.         }
  183.     }

  184.     private boolean addEntryRaw (Item item) {
  185.         return FileUtils.writeFile(getBareJidFile(item.getJid()), item.toXML());
  186.     }

  187.     private File getBareJidFile(Jid bareJid) {
  188.         String encodedJid = Base32.encode(bareJid.toString());
  189.         return new File(fileDir, ENTRY_PREFIX + encodedJid);
  190.     }

  191. }