/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011-2014, FrostWire(R). All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.frostwire.search.domainalias;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.frostwire.search.SearchPerformer;
/**
* Simply responsible for maintaining the list of domain aliases and their states for
* a single domain.
*
* @author gubatron
* @author aldenml
*
*/
public final class DomainAliasManager {
private final String defaultDomain;
private DomainAlias currentDomainAlias;
private boolean defaultDomainOnline;
private final AtomicReference<List<DomainAlias>> aliases;
public DomainAliasManager(String defaultDomain) {
this(defaultDomain, Collections.<DomainAlias> emptyList());
}
public DomainAliasManager(String defaultDomain, List<DomainAlias> aliases) {
this.defaultDomain = defaultDomain;
this.currentDomainAlias = null;
this.aliases = new AtomicReference<List<DomainAlias>>();
this.aliases.set(Collections.synchronizedList(aliases));
this.defaultDomainOnline = true;
}
public String getDefaultDomain() {
return defaultDomain;
}
/**
* Adds new Domain Aliases, keeps the old DomainAlias objects as they were.
* @param aliasNames
*/
public void updateAliases(final List<String> aliasNames) {
List<DomainAlias> newAliasList = new ArrayList<DomainAlias>();
if (aliasNames != null && aliasNames.size() > 0) {
for (String alias : aliasNames) {
DomainAlias domainAlias = new DomainAlias(defaultDomain, alias);
if (!aliases.get().contains(domainAlias)) {
newAliasList.add(domainAlias);
} else {
//we keep the old alias, so we don't forget how many times it might have failed,
//when it was checked, etc.
newAliasList.add(aliases.get().get(aliases.get().indexOf(domainAlias)));
}
}
Collections.shuffle(newAliasList);
if (newAliasList.size() > 0) {
aliases.set(Collections.synchronizedList(newAliasList));
}
}
}
/**
* Resets the Domain Aliases list.
* @param aliasNames
*/
public void setAliases(final List<String> aliasNames) {
List<DomainAlias> newAliasList = new ArrayList<DomainAlias>();
if (aliasNames != null && aliasNames.size() > 0) {
for (String alias : aliasNames) {
DomainAlias domainAlias = new DomainAlias(defaultDomain, alias);
newAliasList.add(domainAlias);
}
Collections.shuffle(newAliasList);
if (newAliasList.size() > 0) {
aliases.set(Collections.synchronizedList(newAliasList));
}
}
}
public void markDomainOffline(String offlineDomain) {
if (offlineDomain.equals(defaultDomain)) {
defaultDomainOnline = false;
} else {
for (DomainAlias domainAlias : aliases.get()) {
if (domainAlias.alias.equals(offlineDomain)) {
domainAlias.markOffline();
return;
}
}
}
}
public DomainAlias getCurrentDomainAlias() {
return currentDomainAlias;
}
/**
* Until it doesn't know the default domain name is not accesible
* it will keep returning the next domain cosidered to be online.
* @return
*/
public String getDomainNameToUse() {
String result = defaultDomain;
if (!defaultDomainOnline) {
if (getCurrentDomainAlias() == null) {
getNextOnlineDomainAlias();
if (getCurrentDomainAlias() != null) {
result = getCurrentDomainAlias().getAlias();
}
} else {
result = getCurrentDomainAlias().alias;
}
}
return result;
}
public void setDomainNameToUse(String alias) {
List<DomainAlias> aliasList = aliases.get();
synchronized (aliasList) {
for (DomainAlias domainAlias : aliasList) {
if (domainAlias.getAlias().equals(alias)) {
currentDomainAlias = domainAlias;
return;
}
}
}
}
/**
* Returns the next domain considered as online on the manager's list.
* null if the current list is empty, null or nobody is online.
*
* This method will update the currentDomainAlias to be used.
*
* This method will not check, checks must have been done in advance
*
* @return
*/
private DomainAlias getNextOnlineDomainAlias() {
List<DomainAlias> aliasesList = aliases.get();
DomainAlias result = null;
if (currentDomainAlias == null) {
currentDomainAlias = aliasesList.get(0);
result = currentDomainAlias;
} else {
int currentIndex = aliasesList.indexOf(currentDomainAlias);
int startingIndex = (currentIndex + 1) % aliasesList.size();
for (int i = startingIndex; i < aliasesList.size(); i++) {
DomainAlias alias = aliasesList.get(i);
if (!alias.equals(currentDomainAlias) && alias.getState() == DomainAliasState.ONLINE) {
currentDomainAlias = alias;
result = currentDomainAlias;
break;
}
}
}
return result;
}
/**
* Will try to ping all DomainAliases that have not been pinged recently to update
* their statuses.
*/
public void checkStatuses(SearchPerformer performer) {
if (aliases != null && !aliases.get().isEmpty()) {
List<DomainAlias> toRemove = new ArrayList<DomainAlias>();
final DomainAliasPongListener pongListener = createPongListener(performer);
for (DomainAlias alias : aliases.get()) {
if (alias.getFailedAttempts() <= 3) {
alias.checkStatus(pongListener);
} else {
System.out.println("Removing alias " + alias.alias);
toRemove.add(alias);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
if (!toRemove.isEmpty()) {
aliases.get().removeAll(toRemove);
}
} else {
//be borne again.
resetAliases();
}
}
private DomainAliasPongListener createPongListener(final SearchPerformer performer) {
final DomainAliasPongListener pongListener = new DomainAliasPongListener() {
private AtomicBoolean firstDomainReportedPong = new AtomicBoolean(false);
@Override
public void onDomainAliasPong(DomainAlias domainAlias) {
//as soon as the first one of the aliases reports he's online
//we'll try to update our active/current domain alias.
if (domainAlias.getState() == DomainAliasState.ONLINE && firstDomainReportedPong.compareAndSet(false, true)) {
System.out.println("DomainAliasManager.DomainAliasPongListener.onDomainAliasPong(): got pong from " + domainAlias.alias);
currentDomainAlias = domainAlias; //the magic moment
performer.perform();
System.out.println("DomainAliasManager.DomainAliasPongListener.onDomainAliasPong(): We've selected a new domain alias: New " + getCurrentDomainAlias().alias + " for " + getDefaultDomain() + " (STATE: " + getCurrentDomainAlias().getState() + ")");
}
}
@Override
public void onDomainAliasPingFailed(DomainAlias domainAlias) {
DomainAliasManager.this.markDomainOffline(domainAlias.getAlias());
}
};
return pongListener;
}
private void resetAliases() {
defaultDomainOnline = true;
if (aliases != null && aliases.get().size() > 0) {
for (DomainAlias alias : aliases.get()) {
alias.reset();
}
}
}
}