Changeset 9b726a8


Ignore:
Timestamp:
Apr 20, 2016 9:04:30 PM (4 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
34e3909
Parents:
a0ba623
Message:

BlockfileNamingService?: Implement new NamingService? API
methods for multiple destinations per hostname

File:
1 edited

Legend:

Unmodified
Added
Removed
  • core/java/src/net/i2p/client/naming/BlockfileNamingService.java

    ra0ba623 r9b726a8  
    2929
    3030import net.i2p.I2PAppContext;
     31import net.i2p.crypto.SigType;
    3132import net.i2p.data.DataFormatException;
    3233import net.i2p.data.DataHelper;
     
    105106    private final boolean _readOnly;
    106107    private String _version = "0";
     108    private volatile boolean _isVersion4;
    107109    private boolean _needsUpgrade;
    108110
     
    222224        _version = VERSION;
    223225        _destSerializer = _destSerializerV4;
     226        _isVersion4 = true;
    224227        try {
    225228            BlockFile rv = new BlockFile(f, true);
     
    322325                                      " Did you downgrade I2P??");
    323326            _version = version;
    324             if (VersionComparator.comp(version, "4") >= 0)
     327            if (VersionComparator.comp(version, "4") >= 0) {
    325328                _destSerializer = _destSerializerV4;
     329                _isVersion4 = true;
     330            }
    326331            _needsUpgrade = needsUpgrade(bf);
    327332            if (_needsUpgrade) {
     
    443448                }
    444449                _destSerializer = _destSerializerV4;
     450                _isVersion4 = true;
    445451                setVersion("4");
    446452            }
     
    479485
    480486    /**
     487     *  For either v1 or v4.
    481488     *  Caller must synchronize
    482489     *  @return entry or null, or throws ioe
     
    542549
    543550    /**
     551     *  Single dest version.
    544552     *  Caller must synchronize
     553     *
    545554     *  @param props may be null
    546555     *  @throws RuntimeException
     
    550559        de.dest = dest;
    551560        de.props = props;
     561        sl.put(key, de);
     562    }
     563
     564    /**
     565     *  Multiple dests version.
     566     *  DB MUST be version 4.
     567     *  Caller must synchronize
     568     *
     569     *  @param propsList may be null, or entries may be null
     570     *  @throws RuntimeException
     571     *  @since 0.9.26
     572     */
     573    private static void addEntry(SkipList<String, DestEntry> sl, String key, List<Destination> dests, List<Properties> propsList) {
     574        DestEntry de = new DestEntry();
     575        de.destList = dests;
     576        de.dest = dests.get(0);
     577        de.propsList = propsList;
     578        if (propsList != null)
     579            de.props = propsList.get(0);
    552580        sl.put(key, de);
    553581    }
     
    728756
    729757    /*
    730      * @param options If non-null and contains the key "list", lookup in
     758     * Single dest version.
     759     *
     760     * @param lookupOptions If non-null and contains the key "list", lookup in
    731761     *                that list only, otherwise all lists
    732762     */
     
    784814    }
    785815
     816    /*
     817     * Multiple dests version.
     818     * DB MUST be version 4.
     819     *
     820     * @param lookupOptions If non-null and contains the key "list", lookup in
     821     *                that list only, otherwise all lists
     822     * @since 0.9.26
     823     */
     824    private List<Destination> lookupAll2(String hostname, Properties lookupOptions, List<Properties> storedOptions) {
     825        // only use cache for b32
     826        if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p")) {
     827            Destination d = super.lookup(hostname, null, null);
     828            if (d != null) {
     829                if (storedOptions != null)
     830                    storedOptions.add(null);
     831                return Collections.singletonList(d);
     832            }
     833            // Base32 failed?
     834            return null;
     835        }
     836        String key = hostname.toLowerCase(Locale.US);
     837        synchronized(_negativeCache) {
     838            if (_negativeCache.get(key) != null)
     839                return null;
     840        }
     841        String listname = null;
     842        if (lookupOptions != null)
     843            listname = lookupOptions.getProperty("list");
     844
     845        List<Destination> rv = null;
     846        synchronized(_bf) {
     847            if (_isClosed)
     848                return null;
     849            for (String list : _lists) {
     850                if (listname != null && !list.equals(listname))
     851                    continue;
     852                try {
     853                    DestEntry de = getEntry(list, key);
     854                    if (de != null) {
     855                        int sz = de.destList.size();
     856                        // if any are invalid, assume they all are
     857                        boolean invalid = false;
     858                        for (int i = 0; i < sz; i++) {
     859                            if (!validate(key, de, listname))
     860                                invalid = true;
     861                        }
     862                        if (invalid)
     863                            continue;
     864                        rv = de.destList;
     865                        if (storedOptions != null)
     866                            storedOptions.addAll(de.propsList);
     867                        break;
     868                    }
     869                } catch (IOException ioe) {
     870                    break;
     871                }
     872            }
     873            deleteInvalid();
     874        }
     875        if (rv != null) {
     876            putCache(hostname, rv.get(0));
     877        } else {
     878            synchronized(_negativeCache) {
     879                _negativeCache.put(key, DUMMY);
     880            }
     881        }
     882        return rv;
     883    }
     884
    786885    /**
    787886     * @param options If non-null and contains the key "list", add to that list
     
    806905    }
    807906
     907    /**
     908     * Single dest version
     909     * This does not prevent adding b32. Caller must check.
     910     *
     911     * @param checkExisting if true, fail if entry already exists
     912     */
    808913    private boolean put(String hostname, Destination d, Properties options, boolean checkExisting) {
    809914        if (_readOnly) {
     
    847952                    else
    848953                        nsl.entryAdded(this, hostname, d, options);
     954                }
     955                return true;
     956            } catch (IOException ioe) {
     957                _log.error("DB add error", ioe);
     958                return false;
     959            } catch (RuntimeException re) {
     960                _log.error("DB add error", re);
     961                return false;
     962            }
     963        }
     964    }
     965
     966    /**
     967     * Multiple dests version.
     968     * DB MUST be version 4.
     969     * This does not prevent adding b32. Caller must check.
     970     *
     971     * @param propsList may be null, or entries may be null
     972     * @param checkExisting if true, fail if entry already exists
     973     * @since 0.9.26
     974     */
     975    private boolean put(String hostname, List<Destination> dests, List<Properties> propsList, boolean checkExisting) {
     976        int sz = dests.size();
     977        if (sz <= 0)
     978            throw new IllegalArgumentException();
     979        if (sz == 1)
     980            return put(hostname, dests.get(0), propsList != null ? propsList.get(0) : null, checkExisting);
     981        if (_readOnly) {
     982            _log.error("Add entry failed, read-only hosts database");
     983            return false;
     984        }
     985        String key = hostname.toLowerCase(Locale.US);
     986        synchronized(_negativeCache) {
     987            _negativeCache.remove(key);
     988        }
     989        String listname = FALLBACK_LIST;
     990        String date = Long.toString(_context.clock().now());
     991        List<Properties> outProps = new ArrayList<Properties>(propsList.size());
     992        for (Properties options : propsList) {
     993            Properties props = new Properties();
     994            props.setProperty(PROP_ADDED, date);
     995            if (options != null) {
     996                props.putAll(options);
     997                String list = options.getProperty("list");
     998                if (list != null) {
     999                    listname = list;
     1000                    props.remove("list");
     1001                }
     1002            }
     1003            outProps.add(props);
     1004        }
     1005        synchronized(_bf) {
     1006            if (_isClosed)
     1007                return false;
     1008            try {
     1009                SkipList<String, DestEntry> sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
     1010                if (sl == null)
     1011                    sl = _bf.makeIndex(listname, _stringSerializer, _destSerializer);
     1012                boolean changed =  (checkExisting || !_listeners.isEmpty()) && sl.get(key) != null;
     1013                if (changed && checkExisting)
     1014                        return false;
     1015                addEntry(sl, key, dests, outProps);
     1016                if (changed) {
     1017                    removeCache(hostname);
     1018                    // removeReverseEntry(key, oldDest) ???
     1019                }
     1020                for (int i = 0; i < dests.size(); i++) {
     1021                    Destination d = dests.get(i);
     1022                    Properties options = propsList.get(i);
     1023                    addReverseEntry(key, d);
     1024                    for (NamingServiceListener nsl : _listeners) {
     1025                        if (changed)
     1026                            nsl.entryChanged(this, hostname, d, options);
     1027                        else
     1028                            nsl.entryAdded(this, hostname, d, options);
     1029                    }
    8491030                }
    8501031                return true;
     
    12441425
    12451426    ////////// End NamingService API
     1427
     1428    //// Begin new API for multiple Destinations
     1429
     1430    /**
     1431     *  Return all of the entries found in the first list found, or in the list
     1432     *  specified in lookupOptions. Does not aggregate all destinations found
     1433     *  in all lists.
     1434     *
     1435     *  If storedOptions is non-null, it must be a List that supports null entries.
     1436     *  If the returned value (the List of Destinations) is non-null,
     1437     *  the same number of Properties objects will be added to storedOptions.
     1438     *  If no properties were found for a given Destination, the corresponding
     1439     *  entry in the storedOptions list will be null.
     1440     *
     1441     *  @param lookupOptions input parameter, NamingService-specific, may be null
     1442     *  @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null
     1443     *  @return non-empty List of Destinations, or null if nothing found
     1444     *  @since 0.9.26
     1445     */
     1446    @Override
     1447    public List<Destination> lookupAll(String hostname, Properties lookupOptions, List<Properties> storedOptions) {
     1448        if (!_isVersion4)
     1449            return super.lookupAll(hostname, lookupOptions, storedOptions);
     1450        List<Destination> rv = lookupAll2(hostname, lookupOptions, storedOptions);
     1451        if (rv == null) {
     1452            // if hostname starts with "www.", strip and try again
     1453            // but not for www.i2p
     1454            hostname = hostname.toLowerCase(Locale.US);
     1455            if (hostname.startsWith("www.") && hostname.length() > 7) {
     1456                hostname = hostname.substring(4);
     1457                rv = lookupAll2(hostname, lookupOptions, storedOptions);
     1458            }
     1459        }
     1460        // we sort the destinations in addDestionation(),
     1461        // which is a lot easier than sorting them here
     1462        return rv;
     1463    }
     1464
     1465    /**
     1466     *  Add a Destination to an existing hostname's entry in the addressbook.
     1467     *
     1468     *  This does not prevent adding b32. Caller must check.
     1469     *
     1470     *  @param options NamingService-specific, may be null
     1471     *  @return success
     1472     *  @since 0.9.26
     1473     */
     1474    @Override
     1475    public boolean addDestination(String hostname, Destination d, Properties options) {
     1476        if (!_isVersion4)
     1477            return putIfAbsent(hostname, d, options);
     1478        List<Properties> storedOptions = new ArrayList<Properties>(4);
     1479        synchronized(_bf) {
     1480            // We use lookupAll2(), not lookupAll(), because if hostname starts with www.,
     1481            // we do not want to read in from the
     1482            // non-www hostname and then copy it to a new www hostname.
     1483            List<Destination> dests = lookupAll2(hostname, options, storedOptions);
     1484            if (dests == null)
     1485                return put(hostname, d, options, false);
     1486            if (dests.contains(d))
     1487                return false;
     1488            List<Destination> newDests = new ArrayList<Destination>(dests.size() + 1);
     1489            newDests.addAll(dests);
     1490            // TODO better sort by sigtype preference.
     1491            // For now, non-DSA at the front, DSA at the end
     1492            SigType type = d.getSigningPublicKey().getType();
     1493            if (type != SigType.DSA_SHA1 && type.isAvailable()) {
     1494                dests.add(0, d);
     1495                storedOptions.add(0, options);
     1496            } else {
     1497                dests.add(d);
     1498                storedOptions.add(options);
     1499            }
     1500            return put(hostname, newDests, storedOptions, false);
     1501        }
     1502    }
     1503
     1504    /**
     1505     *  Remove a hostname's entry only if it contains the Destination d.
     1506     *  If the NamingService supports multiple Destinations per hostname,
     1507     *  and this is the only Destination, removes the entire entry.
     1508     *  If aditional Destinations remain, it only removes the
     1509     *  specified Destination from the entry.
     1510     *
     1511     *  @param options NamingService-specific, may be null
     1512     *  @return true if entry containing d was successfully removed.
     1513     *  @since 0.9.26
     1514     */
     1515    @Override
     1516    public boolean remove(String hostname, Destination d, Properties options) {
     1517        if (!_isVersion4) {
     1518            // super does a get-test-remove, so lock around that
     1519            synchronized(_bf) {
     1520                return super.remove(hostname, d, options);
     1521            }
     1522        }
     1523        List<Properties> storedOptions = new ArrayList<Properties>(4);
     1524        synchronized(_bf) {
     1525            List<Destination> dests = lookupAll(hostname, options, storedOptions);
     1526            if (dests == null)
     1527                return false;
     1528            for (int i = 0; i < dests.size(); i++) {
     1529                Destination dd = dests.get(i);
     1530                if (dd.equals(d)) {
     1531                    // Found it. Remove and return.
     1532                    if (dests.size() == 1)
     1533                        return remove(hostname, options);
     1534                    List<Destination> newDests = new ArrayList<Destination>(dests.size() - 1);
     1535                    for (int j = 0; j < dests.size(); j++) {
     1536                        if (j != i)
     1537                            newDests.add(dests.get(j));
     1538                    }
     1539                    storedOptions.remove(i);
     1540                    removeReverseEntry(hostname, d);
     1541                    return put(hostname, newDests, storedOptions, false);
     1542                }
     1543            }
     1544        }
     1545        return false;
     1546    }
     1547
     1548    //// End new API for multiple Destinations
    12461549
    12471550    /**
Note: See TracChangeset for help on using the changeset viewer.