Package com.salas.bb.service.sync

Source Code of com.salas.bb.service.sync.SyncIn

// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: SyncIn.java,v 1.96 2008/02/28 15:59:50 spyromus Exp $
//

package com.salas.bb.service.sync;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.uif.AbstractDialog;
import com.jgoodies.uif.application.Application;
import com.salas.bb.core.DeletedObjectsRepository;
import com.salas.bb.core.FeedDisplayModeManager;
import com.salas.bb.core.GlobalController;
import com.salas.bb.core.GlobalModel;
import com.salas.bb.domain.*;
import com.salas.bb.domain.prefs.StarzPreferences;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.domain.querytypes.QueryType;
import com.salas.bb.domain.utils.FeedCheckBox;
import com.salas.bb.domain.utils.ReadingListCheckBox;
import com.salas.bb.imageblocker.ImageBlocker;
import com.salas.bb.plugins.Manager;
import com.salas.bb.sentiments.SentimentsConfig;
import com.salas.bb.service.ServerService;
import com.salas.bb.service.ServerServiceException;
import com.salas.bb.service.ServicePreferences;
import com.salas.bb.twitter.TwitterPreferences;
import com.salas.bb.utils.DateUtils;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.opml.Helper;
import com.salas.bb.utils.uif.BBFormBuilder;
import com.salas.bb.utils.uif.CheckBoxList;
import com.salas.bb.utils.uif.ComponentsFactory;
import com.salas.bb.views.settings.FeedRenderingSettings;
import com.salas.bb.views.settings.RenderingSettingsNames;
import com.salas.bbutilities.opml.Importer;
import com.salas.bbutilities.opml.ImporterException;
import com.salas.bbutilities.opml.objects.OPMLGuideSet;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Ingoing synchronization module.
*/
public class SyncIn extends AbstractSynchronization
{
    private static final Logger LOG = Logger.getLogger(SyncIn.class.getName());

    private static final String MSG_ERROR_DURING_SYNC_IN = Strings.error("sync.error.during.sync.in");

    /** <code>TRUE</code> to copy the layout of the server side. */
    private boolean copyServiceLayout;

    /**
     * Creates synchronization modules for the model.
     *
     * @param aModel model.
     * @param aCopyServiceSide <code>TRUE</code> to copy the layout of the server side.
     */
    public SyncIn(GlobalModel aModel, boolean aCopyServiceSide)
    {
        super(aModel);
        copyServiceLayout = aCopyServiceSide;
    }

    /**
     * Performs the step-by-step synchronization and collects stats.
     *
     * @param progress listener to notify.
     * @param aEmail            email of user account.
     * @param aPassword         password of user account.
     *
     * @return statistics.
     */
    protected Stats doSynchronization(IProgressListener progress, String aEmail,
                                      String aPassword)
    {
        SyncInStats stats = new SyncInStats();

        try
        {
            if (servicePreferences.isSyncFeeds())
            {
                if (progress != null) progress.processStep(Strings.message("service.sync.in.loading.guides.and.feeds"));
                loadFeeds(aEmail, aPassword, stats);
                if (progress != null) progress.processStepCompleted();
            }

            if (servicePreferences.isSyncPreferences())
            {
                if (progress != null) progress.processStep(Strings.message("service.sync.in.loading.preferences"));
                loadPreferences(aEmail, aPassword, stats);
                if (progress != null) progress.processStepCompleted();
            }

            servicePreferences.setLastSyncInStatus(ServicePreferences.SYNC_STATUS_SUCCESS);
        } catch (ServerServiceException e1)
        {
            // If the cause of service exception is another exception then log it
            if (e1.getCause() != null)
            {
                LOG.log(Level.SEVERE, MSG_ERROR_DURING_SYNC_IN, e1);
                stats.registerFailure(null);
            } else
            {
                stats.registerFailure(e1.getMessage());
            }
        } catch (ImporterException e1)
        {
            LOG.log(Level.SEVERE, MSG_ERROR_DURING_SYNC_IN, e1);
            stats.registerFailure(null);
        }

        if (stats.hasFailed()) servicePreferences.setLastSyncInStatus(ServicePreferences.SYNC_STATUS_FAILURE);

        // update last sync out date
        servicePreferences.setLastSyncInDate(new Date());

        return stats;
    }

    /**
     * Returns the message to be reported on synchronization start.
     *
     * @return message.
     */
    protected String getProcessStartMessage()
    {
        return prepareProcessStartMessage(
            Strings.message("service.sync.message.loading"),
            Strings.message("service.sync.message.preferences"),
            Strings.message("service.sync.message.guides.and.feeds"),
            Strings.message("service.sync.message.from.blogbridge.service"));
    }

    /**
     * Loads preferences from service.
     *
     * @param email     email of user account.
     * @param password  password of user account.
     * @param stats     stats to fill.
     *
     * @throws ServerServiceException in case of service failure.
     */
    private void loadPreferences(String email, String password, SyncInStats stats)
        throws ServerServiceException
    {
        UserPreferences up = model.getUserPreferences();

        Map prefs = ServerService.syncRestorePrefs(email, password);

        Date lastUpdateTimeO = up.getLastUpdateTime();
        long localUpdateTime = lastUpdateTimeO == null ? -1 : lastUpdateTimeO.getTime();
        long remoteUpdateTime = getLong(prefs, "timestamp", -1);

        int loaded;

        // If local changes happened after saving preferences to the service, do not update them
        if (copyServiceLayout || localUpdateTime < remoteUpdateTime)
        {
            // Image blocker list
            String ibExpressions = getPreferenceValue(prefs, ImageBlocker.KEY);
            ImageBlocker.setExpressions(ibExpressions);

            SentimentsConfig.syncIn(prefs);

            loadUserPreferences(prefs);
            loaded = prefs.size();
        } else loaded = 0;

        // Loading What's Hot preferences blog with independent change timestamp
        long local = DateUtils.localToUTC(up.getWhSettingsChangeTime());
        long remote = getLong(prefs, UserPreferences.PROP_WH_SETTINGS_CHANGE_TIME, 0);
        if (copyServiceLayout || remote > local)
        {
            // If the settings were changed remotely, copy them
            up.setWhIgnore(getString(prefs, UserPreferences.PROP_WH_IGNORE,
                UserPreferences.DEFAULT_WH_IGNORE));
            up.setWhNoSelfLinks(getBoolean(prefs, UserPreferences.PROP_WH_NOSELFLINKS,
                UserPreferences.DEFAULT_WH_NOSELFLINKS));
            up.setWhSuppressSameSourceLinks(getBoolean(prefs, UserPreferences.PROP_WH_SUPPRESS_SAME_SOURCE_LINKS,
                UserPreferences.DEFAULT_WH_SUPPRESS_SAME_SOURCE_LINKS));
            up.setWhTargetGuide(getString(prefs, UserPreferences.PROP_WH_TARGET_GUIDE,
                UserPreferences.DEFAULT_WH_TARGET_GUIDE));
            up.setWhSettingsChangeTime(remote);
        }

        stats.loadedPreferences = loaded;
    }

    private static String getPreferenceValue(Map preferences, String key)
    {
        return StringUtils.fromUTF8((byte[])preferences.get(key));
    }

    /**
     * Synchronizes feeds (loads from service and merges them with local list).
     *
     * @param email     email of user.
     * @param password  password of user.
     * @param stats     stats to fill.
     *
     * @throws ServerServiceException   in case of error with service.
     * @throws ImporterException        in case of problems with OPML.
     */
    private void loadFeeds(String email, String password, final SyncInStats stats)
        throws ServerServiceException, ImporterException
    {
        String opml = ServerService.syncRestore(email, password);

        // parse output from server w/ possible empty guides
        final Importer im = new Importer();
        im.setAllowEmptyGuides(true);

        OPMLGuideSet set = im.processFromString(opml, false);

        GuidesSet remoteSet = Helper.createGuidesSet(null, set);
        final GuidesSet localSet = model.getGuidesSet();
        final Changes changes = evaluateChanges(localSet, remoteSet, copyServiceLayout);

        // EDT
        try
        {
            SwingUtilities.invokeAndWait(new Runnable()
            {
                public void run()
                {
                    if (adjustChangesByUser(changes))
                    {
                        IGuide selectedGuide = model.getSelectedGuide();
                        IFeed selectedFeed = model.getSelectedFeed();

                        performChanges(localSet, changes, stats);
                        restoreSelection(selectedGuide, selectedFeed);

                        // Update synchronization times
                        localSet.onSyncInCompletion();
                    }
                }
            });
        } catch (Throwable e)
        {
            if (e instanceof InvocationTargetException) e = e.getCause();
            LOG.log(Level.SEVERE, MSG_ERROR_DURING_SYNC_IN, e.getCause());
        }
    }

    /**
     * Shows alert and allows user to adjust his.
     *
     * @param aChanges changes we are going to make.
     *
     * @return <code>TRUE</code> if user has accepted the changes.
     */
    static boolean adjustChangesByUser(Changes aChanges)
    {
        boolean accepted = true;

        List<IFeed> newFeeds = aChanges.getAddFeeds();

        if (newFeeds.size() > 0 || aChanges.getAddReadingLists().size() > 0)
        {
            ChangesConfirmationDialog dialog =
                new ChangesConfirmationDialog(Application.getDefaultParentFrame(),
                    aChanges.getAddReadingLists(), newFeeds);

            dialog.open();

            accepted = !dialog.hasBeenCanceled();
        }

        return accepted;
    }

    /**
     * Restores selected feed and guide.
     *
     * @param aSelectedGuide    selected guide.
     * @param aSelectedFeed     selected feed.
     */
    private static void restoreSelection(IGuide aSelectedGuide, IFeed aSelectedFeed)
    {
        if (aSelectedFeed == null || aSelectedGuide == null) return;

        GlobalController controller = GlobalController.SINGLETON;
        if (controller != null)
        {
            // EDT !!!
            controller.selectGuide(aSelectedGuide, false);
            controller.selectFeed(aSelectedFeed);
        }
    }

    /**
     * Performs changes.
     *
     * @param set       set to update.
     * @param changes   changes.
     * @param stats     stats to fill in.
     */
    static void performChanges(GuidesSet set, Changes changes, SyncInStats stats)
    {
        int oldGuidesCount = set.getGuidesCount();

        performChangesAddFeeds(set, changes.getAddFeeds(), stats);
        performChangesAddReadingLists(set, changes.getAddReadingLists(), stats);
        performChangesRemoveReadingLists(changes.getRemoveReadingLists());
        performChangesRemoveFeeds(changes.getRemoveFeeds());

        performChangesUpdateFeeds(changes);
        performChangesUpdateGuides(changes.getUpdateGuides());

        performChangesRemoveEmptyGuides(set);

        if (stats != null)
        {
            stats.createdGuides = set.getGuidesCount() - oldGuidesCount;
            if (stats.createdGuides < 0) stats.createdGuides = 0;
        }
    }

    /**
     * Applies changes to the guides.
     *
     * @param guidePairs guide pairs.
     */
    private static void performChangesUpdateGuides(List<GuidePair> guidePairs)
    {
        for (GuidePair pair : guidePairs)
        {
            IGuide localGuide = pair.local;
            IGuide remoteGuide = pair.remote;

            transferGuideProperties(localGuide, remoteGuide);
        }
    }

    /**
     * Transfers properties from one guide to another.
     *
     * @param guide     target guide.
     * @param pattern   pattern guide.
     */
    static void transferGuideProperties(IGuide guide, IGuide pattern)
    {
        guide.setIconKey(pattern.getIconKey());
        guide.setPublishingEnabled(pattern.isPublishingEnabled());
        guide.setPublishingTitle(pattern.getPublishingTitle());
        guide.setPublishingTags(pattern.getPublishingTags());
        guide.setPublishingPublic(pattern.isPublishingPublic());
        guide.setPublishingRating(pattern.getPublishingRating());
        guide.setAutoFeedsDiscovery(pattern.isAutoFeedsDiscovery());
        guide.setNotificationsAllowed(pattern.isNotificationsAllowed());
        guide.setMobile(pattern.isMobile());
    }

    /**
     * Applies read articles match keys to feeds.
     *
     * @param changes set of changes.
     */
    private static void performChangesUpdateFeeds(Changes changes)
    {
        Map<DataFeed, FeedPair> aFeeds = changes.getUpdateFeedsKeys();
        for (FeedPair pair : aFeeds.values())
        {
            DataFeed local = (DataFeed)pair.local;
            DataFeed remote = (DataFeed)pair.remote;

            local.setReadArticlesKeys(remote.getReadArticlesKeys());
            local.setPinnedArticlesKeys(remote.getPinnedArticlesKeys());
        }

        Collection<FeedPair> updateFeeds = changes.getUpdateFeeds();
        for (FeedPair pair : updateFeeds)
        {
            IFeed localFeed = pair.local;
            IFeed remoteFeed = pair.remote;

            // Move properties from remote feed to local
            localFeed.setCustomViewMode(remoteFeed.getCustomViewMode());
            localFeed.setCustomViewModeEnabled(remoteFeed.isCustomViewModeEnabled());
            localFeed.setRating(remoteFeed.getRating());
            localFeed.setType(remoteFeed.getType());
            localFeed.setHandlingType(remoteFeed.getHandlingType());

            // Feed type specific operations
            if (localFeed instanceof DirectFeed)
            {
                DirectFeed localDFeed = (DirectFeed)localFeed;
                DirectFeed remoteDFeed = (DirectFeed)remoteFeed;

                localDFeed.setCustomAuthor(remoteDFeed.getCustomAuthor());
                localDFeed.setCustomDescription(remoteDFeed.getCustomDescription());
                localDFeed.setCustomTitle(remoteDFeed.getCustomTitle());
                localDFeed.setDisabled(remoteDFeed.isDisabled());
                localDFeed.setPurgeLimit(remoteDFeed.getPurgeLimit());

                localDFeed.setUserTags(remoteDFeed.getUserTags());
                localDFeed.setTagsDescription(remoteDFeed.getTagsDescription());
                localDFeed.setTagsExtended(remoteDFeed.getTagsExtended());
            } else if (localFeed instanceof QueryFeed)
            {
                QueryFeed localQFeed = (QueryFeed)localFeed;
                QueryFeed remoteQFeed = (QueryFeed)remoteFeed;

                localQFeed.setPurgeLimit(remoteQFeed.getPurgeLimit());
                localQFeed.setQueryType(remoteQFeed.getQueryType());
                localQFeed.setParameter(remoteQFeed.getParameter());
            } else
            {
                SearchFeed localSFeed = (SearchFeed)localFeed;
                SearchFeed remoteSFeed = (SearchFeed)remoteFeed;

                localSFeed.setQuery(remoteSFeed.getQuery());
            }

            if (localFeed instanceof DataFeed)
            {
                DataFeed localDFeed = (DataFeed)localFeed;
                DataFeed removeDFeed = (DataFeed)remoteFeed;

                localDFeed.setUpdatePeriod(removeDFeed.getUpdatePeriod());
            }
        }
    }

    /**
     * Removes all reading lists schedule for removal and leaves feeds unassociated.
     *
     * @param aReadingLists reading lists.
     */
    private static void performChangesRemoveReadingLists(List<ReadingList> aReadingLists)
    {
        for (ReadingList list : aReadingLists)
        {
            StandardGuide guide = list.getParentGuide();
            guide.remove(list, true);
        }
    }

    /**
     * Adds reading lists and connects required feeds in the guide.
     *
     * @param aSet          guides set.
     * @param aReadingLists reading lists.
     * @param aStats        statistics.
     */
    private static void performChangesAddReadingLists(GuidesSet aSet, List<ReadingList> aReadingLists,
                                                      SyncInStats aStats)
    {
        for (ReadingList list : aReadingLists)
        {
            performChangesAddReadingList(aSet, list, aStats);
        }
    }

    /**
     * Adds reading list to the guide and associates all required feeds.
     *
     * @param aSet      guides set.
     * @param aList     reading list.
     * @param aStats    statistics.
     */
    private static void performChangesAddReadingList(GuidesSet aSet, ReadingList aList,
                                                     SyncInStats aStats)
    {
        StandardGuide guide = findOrCreateGuide(aSet, aList.getParentGuide());

        ReadingList newList = new ReadingList(aList.getURL());
        newList.setTitle(aList.getTitle());

        guide.add(newList);

        DirectFeed[] feedsToConnect = aList.getFeeds();
        for (DirectFeed feed : feedsToConnect)
        {
            DirectFeed existingFeed = aSet.findDirectFeed(feed.getXmlURL());
            boolean existing = true;
            if (existingFeed == null)
            {
                existingFeed = feed;

                feed.removeParentGuide(feed.getParentGuides()[0]);
                feed.removeAllReadingLists();

                if (aStats != null) aStats.addedFeeds++;
                existing = false;
            }

            newList.add(existingFeed);

            if (!existing) GlobalController.SINGLETON.getPoller().update(existingFeed, false);
        }
    }

    /**
     * Removes empty guides.
     *
     * @param set guides set.
     */
    private static void performChangesRemoveEmptyGuides(GuidesSet set)
    {
        StandardGuide[] guides = set.getStandardGuides(null);
        for (StandardGuide guide : guides)
        {
            if (guide.getFeedsCount() == 0) set.remove(guide);
        }
    }

    /**
     * Moves feeds marked for addition to the guides.
     *
     * @param aSet      local set.
     * @param aFeeds    feeds list.
     * @param aStats    stats to fill in.
     */
    private static void performChangesAddFeeds(GuidesSet aSet, List<IFeed> aFeeds,
                                               SyncInStats aStats)
    {
        for (IFeed feed : aFeeds)
        {
            IGuide guide = feed.getParentGuides()[0];
            if (guide.hasDirectLinkWith(feed))
            {
                StandardGuide localGuide = findOrCreateGuide(aSet, guide);

                // Disconnect feed from its old parent guide
                feed.removeParentGuide(guide);
                if (feed instanceof DirectFeed) ((DirectFeed)feed).removeAllReadingLists();

                // Find existing feed
                IFeed existingFeed = aSet.findFeed(feed);
                if (existingFeed != null) feed = existingFeed;

                localGuide.add(feed);
                StandardGuide.FeedLinkInfo info = localGuide.getFeedLinkInfo(feed);
                info.setLastSyncTime(System.currentTimeMillis());

                if (aStats != null) aStats.addedFeeds++;
            }
        }
    }

    /**
     * Finds a guide in the set of local guides or create a new one.
     *
     * @param aSet          set.
     * @param aPatternGuide pattern-guide.
     *
     * @return guide.
     */
    static StandardGuide findOrCreateGuide(GuidesSet aSet, IGuide aPatternGuide)
    {
        String title = aPatternGuide.getTitle();

        StandardGuide guide = findGuideByName(aSet.getStandardGuides(null), title);
        if (guide == null)
        {
            guide = new StandardGuide();
            guide.setTitle(title);
            transferGuideProperties(guide, aPatternGuide);

            aSet.add(guide);
        }

        return guide;
    }

    /**
     * Removes feed mentioned in the list.
     *
     * @param guideFeeds feeds list.
     */
    private static void performChangesRemoveFeeds(List<GuideFeedPair> guideFeeds)
    {
        for (GuideFeedPair guideFeed : guideFeeds)
        {
            IGuide localGuide = guideFeed.guide;
            IFeed localFeed = guideFeed.feed;

            // To be removed from some guide feed should be unassigned from the
            // reading lists in that guide first and then removed from the guide
            // itself

            if (localFeed instanceof DirectFeed)
            {
                DirectFeed dfeed = (DirectFeed)localFeed;
                ReadingList[] lists = dfeed.getReadingLists();
                for (ReadingList list : lists)
                {
                    if (list.getParentGuide() == localGuide) list.remove(dfeed);
                }
            }

            localGuide.remove(localFeed);
        }
    }

    /**
     * Compares two sets of guides and evaluates necessary changes.
     * This is the core of synchronization logic.
     *
     * @param aLocalSet     local set of guides.
     * @param aRemoteSet    remote set of guides.
     * @param copyServiceLayout <code>TRUE</code> to make a complete copy of the service side.
     *
     * @return changes.
     */
    static Changes evaluateChanges(GuidesSet aLocalSet, GuidesSet aRemoteSet,
                                   boolean copyServiceLayout)
    {
        Changes changes = new Changes();

        StandardGuide[] localGuides = aLocalSet.getStandardGuides(null);
        StandardGuide[] remoteGuides = aRemoteSet.getStandardGuides(null);

        DeletedObjectsRepository dfr = GlobalController.SINGLETON.getDeletedFeedsRepository();

        // Scan through remote guides and verify each guide user has.
        // If user has no some guide then put the guide in the list
        // of guides to add. If user has the guide, verify its contents.
        // Later we could scan the lists of guides for addition and
        // guides for removal in order to detect the renaming.
        for (StandardGuide remoteGuide : remoteGuides)
        {
            String remoteGuideTitle = remoteGuide.getTitle();

            StandardGuide localGuide = findGuideByName(localGuides, remoteGuideTitle);

            if (localGuide == null)
            {
                // There's no guide with such name locally -- add feeds that weren't removed
                for (int j = 0; j < remoteGuide.getFeedsCount(); j++)
                {
                    IFeed feed = remoteGuide.getFeedAt(j);
                    if (remoteGuide.hasDirectLinkWith(feed) &&
                        (copyServiceLayout || !dfr.wasDeleted(remoteGuideTitle, feed.getMatchKey())))
                            changes.addFeed(feed);
                }

                // Reading lists
                ReadingList[] readingLists = remoteGuide.getReadingLists();
                for (ReadingList rl : readingLists)
                {
                    if (copyServiceLayout || !dfr.wasDeleted(remoteGuideTitle, rl.getURL().toString()))
                        changes.addReadingList(rl);
                }
            } else
            {
                if (localGuide.getLastUpdateTime() < remoteGuide.getLastUpdateTime())
                {
                    changes.updateGuide(localGuide, remoteGuide);
                }

                evaluateChangesInGuide(localGuide, remoteGuide, changes, copyServiceLayout);
            }
        }

        // Scan through the list of local guides and mark for removal those not
        // mentioned in the list of remote.
        for (StandardGuide localGuide : localGuides)
        {
            StandardGuide remoteGuide = findGuideByName(remoteGuides, localGuide.getTitle());

            if (remoteGuide == null)
            {
                ReadingList[] readingLists = localGuide.getReadingLists();
                for (ReadingList readingList : readingLists)
                {
                    if (copyServiceLayout || readingList.getLastSyncTime() != -1)
                    {
                        changes.removeReadingList(readingList);
                    }
                }

                // Remove whole guide
                for (int j = 0; j < localGuide.getFeedsCount(); j++)
                {
                    IFeed feed = localGuide.getFeedAt(j);
                    StandardGuide.FeedLinkInfo info;

                    if (copyServiceLayout || ((info = localGuide.getFeedLinkInfo(feed)) != null &&
                        info.getLastSyncTime() != -1))
                    {
                        changes.removeFeed(localGuide, feed);
                    }
                }
            }
        }

        // Find changes in feeds
        FeedsList localFeeds = aLocalSet.getFeedsList();

        for (int i = 0; i < localFeeds.getFeedsCount(); i++)
        {
            IFeed localFeed = localFeeds.getFeedAt(i);
            IFeed remoteFeed = aRemoteSet.findFeed(localFeed);
            if (remoteFeed != null && (copyServiceLayout ||
                localFeed.getLastUpdateTime() < remoteFeed.getLastUpdateTime()))
            {
                changes.updateFeed(localFeed, remoteFeed);
            }
        }

        return changes;
    }

    /**
     * Looks for a guide with the given name.
     *
     * @param aGuides   guides list.
     * @param aName     name.
     *
     * @return target guide.
     */
    private static StandardGuide findGuideByName(StandardGuide[] aGuides, String aName)
    {
        StandardGuide guide = null;

        for (int i = 0; guide == null && i < aGuides.length; i++)
        {
            StandardGuide standardGuide = aGuides[i];
            if (aName.equals(standardGuide.getTitle())) guide = standardGuide;
        }

        return guide;
    }

    /**
     * Evaluates changes within single guide.
     *
     * @param aLocalGuide   local guide.
     * @param aRemoteGuide  remote guide.
     * @param aChanges      changes.
     * @param aClearNew     <code>TRUE</code> to remove any local feeds which aren't on the service.
     */
    static void evaluateChangesInGuide(StandardGuide aLocalGuide,
                                       StandardGuide aRemoteGuide, Changes aChanges, boolean aClearNew)
    {
        evaluateChangesInReadingLists(aLocalGuide, aRemoteGuide, aChanges, aClearNew);
        evaluateChangesInFeeds(aLocalGuide, aRemoteGuide, aChanges, aClearNew);
    }

    /**
     * Evaluates changes within single guide reading lists.
     *
     * @param aLocalGuide   local guide.
     * @param aRemoteGuide  remote guide.
     * @param aChanges      changes.
     * @param aClearNew     <code>TRUE</code> to remove any local feeds which aren't on the service.
     */
    static void evaluateChangesInReadingLists(StandardGuide aLocalGuide,
                                              StandardGuide aRemoteGuide, Changes aChanges, boolean aClearNew)
    {
        // Add new reading lists from the remote source
        ReadingList[] listsR = aRemoteGuide.getReadingLists();
        for (ReadingList listR : listsR)
        {
            ReadingList listL = findReadingList(aLocalGuide, listR);

            // A reading list has been added remotely
            if (listL == null) aChanges.addReadingList(listR);
        }

        // Remove local reading lists
        ReadingList[] listsL = aLocalGuide.getReadingLists();
        for (ReadingList listL : listsL)
        {
            ReadingList listR = findReadingList(aRemoteGuide, listL);

            // A reading list has been removed remotely or has not been sent to the service yet
            if (listR == null && (aClearNew || listL.getLastSyncTime() != -1))
            {
                aChanges.removeReadingList(listL);
            }
        }
    }

    /**
     * Looks for the same list in the guide.
     *
     * @param aGuide    guide.
     * @param aList     target list.
     *
     * @return list object or <code>NULL</code>.
     */
    private static ReadingList findReadingList(StandardGuide aGuide, ReadingList aList)
    {
        ReadingList[] lists = aGuide.getReadingLists();
        return findReadingList(lists, aList);
    }

    /**
     * Looks for the same list in the list.
     *
     * @param aLists        list of lists.
     * @param targetList    target list.
     *
     * @return list object or <code>NULL</code>.
     */
    private static ReadingList findReadingList(ReadingList[] aLists, ReadingList targetList)
    {
        ReadingList list = null;

        URL urlT = targetList.getURL();
        if (urlT != null)
        {
            String urlTS = urlT.toString();

            for (int i = 0; list == null && i < aLists.length; i++)
            {
                ReadingList readingList = aLists[i];
                URL url = readingList.getURL();
                if (url != null && url.toString().equals(urlTS)) list = readingList;
            }
        }

        return list;
    }

    /**
     * Evaluates changes within single guide.
     *
     * @param aLocalGuide   local guide.
     * @param aRemoteGuide  remote guide.
     * @param aChanges      changes.
     * @param aClearNew     <code>TRUE</code> to remove any local feeds which aren't on the service.
     */
    static void evaluateChangesInFeeds(StandardGuide aLocalGuide,
                                       StandardGuide aRemoteGuide, Changes aChanges, boolean aClearNew)
    {
        DeletedObjectsRepository dfr = GlobalController.SINGLETON.getDeletedFeedsRepository();

        // Add new feeds from remote source
        for (int i = 0; i < aRemoteGuide.getFeedsCount(); i++)
        {
            IFeed remoteFeed = aRemoteGuide.getFeedAt(i);
            if (!aRemoteGuide.hasDirectLinkWith(remoteFeed)) continue;

            IFeed localFeed = findFeed(aLocalGuide, remoteFeed);
            if (!aLocalGuide.hasDirectLinkWith(localFeed)) localFeed = null;

            // A feed has been added remotely
            if (localFeed == null)
            {
                if (aClearNew || !dfr.wasDeleted(aLocalGuide.getTitle(), remoteFeed.getMatchKey()))
                {
                    aChanges.addFeed(remoteFeed);
                }
            } else
            {
                if (remoteFeed instanceof DataFeed)
                {
                    aChanges.addUpdateFeedsKeys((DataFeed)localFeed, (DataFeed)remoteFeed);
                }
            }
        }

        // Remove local feeds
        for (int i = 0; i < aLocalGuide.getFeedsCount(); i++)
        {
            IFeed localFeed = aLocalGuide.getFeedAt(i);
            if (!aLocalGuide.hasDirectLinkWith(localFeed)) continue;

            IFeed remoteFeed = findFeed(aRemoteGuide, localFeed);
            if (!aRemoteGuide.hasDirectLinkWith(remoteFeed)) remoteFeed = null;

            StandardGuide.FeedLinkInfo info;

            // A feed has been removed remotely or has not been sent to service yet
            if (remoteFeed == null &&
                (aClearNew || ((info = aLocalGuide.getFeedLinkInfo(localFeed)) != null &&
                                info.getLastSyncTime() != -1)))
            {
                aChanges.removeFeed(aLocalGuide, localFeed);
            }
        }
    }

    /**
     * Finds a feed in the guide which is the same as the pattern-feed.
     * The similarity depends on the type of a feed.
     *
     * @param aGuide        guide.
     * @param aPatternFeed  pattern-feed.
     *
     * @return similar feed or <code>NULL</code>.
     */
    private static IFeed findFeed(StandardGuide aGuide, IFeed aPatternFeed)
    {
        IFeed targetFeed = null;
        for (int i = 0; targetFeed == null && i < aGuide.getFeedsCount(); i++)
        {
            IFeed feed = aGuide.getFeedAt(i);
            if (feedsAreTheSame(feed, aPatternFeed)) targetFeed = feed;
        }

        return targetFeed;
    }

    /**
     * Finds all feeds in the guide which is the same as the pattern-feed.
     * The similarity depends on the type of a feed.
     *
     * @param aGuide        guide.
     * @param aPatternFeed  pattern-feed.
     *
     * @return similar feeds.
     */
//    private static List findFeeds(StandardGuide aGuide, IFeed aPatternFeed)
//    {
//        List feeds = new ArrayList();
//        for (int i = 0; i < aGuide.getFeedsCount(); i++)
//        {
//            IFeed feed = aGuide.getFeedAt(i);
//            if (feedsAreTheSame(feed, aPatternFeed)) feeds.add(feed);
//        }
//
//        return feeds;
//    }

    /**
     * Compares two feeds and says if they are similar by their properties.
     *
     * @param aFeed         first feed.
     * @param aPatternFeed  second feed.
     *
     * @return <code>TRUE</code> if similar.
     */
    private static boolean feedsAreTheSame(IFeed aFeed, IFeed aPatternFeed)
    {
        boolean similar = false;

        if (aPatternFeed instanceof DirectFeed)
        {
            if (aFeed instanceof DirectFeed)
            {
                similar = feedsAreTheSameDirect((DirectFeed)aPatternFeed, (DirectFeed)aFeed);
            }
        } else if (aPatternFeed instanceof QueryFeed)
        {
            if (aFeed instanceof QueryFeed)
            {
                QueryFeed patFeed = (QueryFeed)aPatternFeed;
                QueryFeed matFeed = (QueryFeed)aFeed;

                QueryType patType = patFeed.getQueryType();
                QueryType matType = matFeed.getQueryType();

                String patParam = patFeed.getParameter();
                String matParam = matFeed.getParameter();

                similar = patType == matType && patParam.equals(matParam);
            }
        } else if (aPatternFeed instanceof SearchFeed)
        {
            if (aFeed instanceof SearchFeed)
            {
                String patQuery = ((SearchFeed)aPatternFeed).getQuery().serializeToString();
                String matQuery = ((SearchFeed)aFeed).getQuery().serializeToString();

                similar = patQuery.equals(matQuery);
            }
        }

        return similar;
    }

    /**
     * Checks if two direct feeds are the same.
     *
     * @param aPatternFeed  the feed to match against the other.
     * @param aMatchFeed    the target feed.
     *
     * @return <code>TRUE</code> if matches.
     */
    static boolean feedsAreTheSameDirect(DirectFeed aPatternFeed, DirectFeed aMatchFeed)
    {
        URL patXmlURL = aPatternFeed.getXmlURL();
        URL matXmlURL = aMatchFeed.getXmlURL();

        return patXmlURL != null && matXmlURL != null &&
            (aPatternFeed.calcSyncHash() == aMatchFeed.getSyncHash() ||
             matXmlURL.toString().equalsIgnoreCase(patXmlURL.toString()));
    }

    /**
     * Simple holder of stats.
     */
    private static class SyncInStats extends Stats
    {
        private int createdGuides;
        private int addedFeeds;
        private int loadedPreferences;

        /**
         * Returns custom text to be told if not failed.
         *
         * @return text.
         */
        protected String getCustomText()
        {
            StringBuffer message = new StringBuffer();

            if (createdGuides > 0) message.append(MessageFormat.format(
                Strings.message("service.sync.in.status.guides.created"),
                createdGuides));
            if (addedFeeds > 0) message.append(MessageFormat.format(
                Strings.message("service.sync.in.status.feeds.added"),
                addedFeeds));
            if (loadedPreferences > 0) message.append(MessageFormat.format(
                Strings.message("service.sync.in.status.preferences.loaded"),
                loadedPreferences));

            return message.toString();
        }
    }

    /** Changes to apply to local set of guides. */
    public static class Changes
    {
        private List<IFeed> addFeeds = new ArrayList<IFeed>();
        private Map<IFeed, FeedPair> updateFeeds = new IdentityHashMap<IFeed, FeedPair>();
        private List<GuidePair> updateGuides = new ArrayList<GuidePair>();
        private List<GuideFeedPair> removeFeeds = new ArrayList<GuideFeedPair>();
        private Map<DataFeed, FeedPair> updateFeedsKeys = new IdentityHashMap<DataFeed, FeedPair>();

        private List<ReadingList> addReadingLists = new ArrayList<ReadingList>();
        private List<ReadingList> removeReadingLists = new ArrayList<ReadingList>();

        /**
         * Puts a feed into the additions list.
         *
         * @param feed feed.
         */
        public void addFeed(IFeed feed)
        {
            addFeeds.add(feed);
        }

        /**
         * Puts a feed into the removals list.
         *
         * @param guide guide to remove from.
         * @param feed  feed.
         */
        public void removeFeed(IGuide guide, IFeed feed)
        {
            removeFeeds.add(new GuideFeedPair(guide, feed));
        }

        /**
         * Puts a feed into the updates list.
         *
         * @param local     local feed.
         * @param remote    remote feed.
         */
        public void updateFeed(IFeed local, IFeed remote)
        {
            if (!updateFeeds.containsKey(local))
            {
                updateFeeds.put(local, new FeedPair(local, remote));
            }
        }

        /**
         * Upts a guide into the updates list.
         *
         * @param local     local guide.
         * @param remote    remote guide.
         */
        public void updateGuide(IGuide local, IGuide remote)
        {
            updateGuides.add(new GuidePair(local, remote));
        }

        /**
         * Puts a reading list into the additions list.
         *
         * @param list list.
         */
        public void addReadingList(ReadingList list)
        {
            addReadingLists.add(list);
        }

        /**
         * Puts a reading list into the removals list.
         *
         * @param list list.
         */
        public void removeReadingList(ReadingList list)
        {
            removeReadingLists.add(list);
        }

        /**
         * Returns the list of feeds to be added.
         *
         * @return added feeds list.
         */
        public List<IFeed> getAddFeeds()
        {
            return addFeeds;
        }

        /**
         * Returns the list of guide-feeds pairs to be removed.
         *
         * @return removed guide-feeds list.
         *
         * @see GuideFeedPair
         */
        public List<GuideFeedPair> getRemoveFeeds()
        {
            return removeFeeds;
        }

        /**
         * Returns the list of feeds to update.
         *
         * @return feeds.
         */
        public Collection<FeedPair> getUpdateFeeds()
        {
            return updateFeeds.values();
        }

        /**
         * Returns the list of guides to update.
         *
         * @return guides.
         */
        public List<GuidePair> getUpdateGuides()
        {
            return updateGuides;
        }

        /**
         * Returns the list of reading lists to be added.
         *
         * @return added reading lists list.
         */
        public List<ReadingList> getAddReadingLists()
        {
            return addReadingLists;
        }

        /**
         * Returns the list of reading lists to be removed.
         *
         * @return removed reading lists list.
         */
        public List<ReadingList> getRemoveReadingLists()
        {
            return removeReadingLists;
        }

        /**
         * Registers read keys for a feed.
         *
         * @param localFeed feed.
         * @param remoteFeed remote feed.
         */
        public void addUpdateFeedsKeys(DataFeed localFeed, DataFeed remoteFeed)
        {
            updateFeedsKeys.put(localFeed, new FeedPair(localFeed, remoteFeed));
        }

        /**
         * Returns the map of feeds to read articles keys.
         *
         * @return map of feeds to keys.
         */
        public Map<DataFeed, FeedPair> getUpdateFeedsKeys()
        {
            return updateFeedsKeys;
        }
    }

    /**
     * Guide - Feed pair holder.
     */
    static class GuideFeedPair
    {
        final IGuide    guide;
        final IFeed     feed;

        /**
         * Creates object.
         *
         * @param aGuide    guide.
         * @param aFeed     feed.
         */
        public GuideFeedPair(IGuide aGuide, IFeed aFeed)
        {
            guide = aGuide;
            feed = aFeed;
        }
    }

    /**
     * Simple pair of guides.
     */
    static class GuidePair
    {
        final IGuide    local;
        final IGuide    remote;

        /**
         * Creates holder.
         *
         * @param aLocal    local guide.
         * @param aRemote   remote guide.
         */
        public GuidePair(IGuide aLocal, IGuide aRemote)
        {
            local = aLocal;
            remote = aRemote;
        }
    }

    /**
     * Simple feed pair holder.
     */
    static class FeedPair
    {
        final IFeed local;
        final IFeed remote;

        /**
         * Creates holder.
         *
         * @param aLocal    local feed.
         * @param aRemote   remote feed.
         */
        public FeedPair(IFeed aLocal, IFeed aRemote)
        {
            local = aLocal;
            remote = aRemote;
        }
    }

    /**
     * The dialog for accepting / rejecting changes and modifying the list
     * of feeds to add.
     */
    private static class ChangesConfirmationDialog extends AbstractDialog
    {
        private final java.util.List<IFeed> addFeeds;
        private final java.util.List<ReadingList> addReadingLists;

        private final CheckBoxList lstAddFeeds;
        private final CheckBoxList lstAddReadingLists;

        /**
         * Creates alert.
         *
         * @param frame     parent frame.
         * @param newLists  new reading lists
         * @param newFeeds  new feeds.
         */
        public ChangesConfirmationDialog(Frame frame, List<ReadingList> newLists, List<IFeed> newFeeds)
        {
            super(frame);

            addFeeds = newFeeds;
            lstAddFeeds = new CheckBoxList();
            lstAddFeeds.setListData(FeedCheckBox.wrap(addFeeds));

            addReadingLists = newLists;
            lstAddReadingLists = new CheckBoxList();
            lstAddReadingLists.setListData(ReadingListCheckBox.wrap(addReadingLists));

            enableEvents(AWTEvent.WINDOW_EVENT_MASK);
        }

        /**
         * Builds dialog content.
         *
         * @return content.
         */
        protected JComponent buildContent()
        {
            JPanel panel = new JPanel(new BorderLayout());

            panel.add(buildBody(), BorderLayout.CENTER);
            panel.add(buildButtonBarWithOKCancel(), BorderLayout.SOUTH);

            return panel;
        }

        /**
         * Creates body part.
         *
         * @return body part.
         */
        private Component buildBody()
        {
            BBFormBuilder builder = new BBFormBuilder("p, 4dlu, 100dlu, 0, p");

            JComponent wording = ComponentsFactory.createWrappedMultilineLabel(
                Strings.message("service.sync.in.wording"));

            builder.append(wording, 5);
            builder.appendUnrelatedComponentsGapRow(2);

            boolean feeds = addFeeds.size() > 0;
            boolean lists = addReadingLists.size() > 0;

            if (feeds)
            {
                builder.append(Strings.message("service.sync.in.confirmation.feeds.to.add"), 3,
                    CheckBoxList.createAllNonePanel(lstAddFeeds), 1);
                builder.appendRow("50dlu:grow");
                builder.append(new JScrollPane(lstAddFeeds), 5,
                    CellConstraints.FILL, CellConstraints.FILL);

                if (lists) builder.appendUnrelatedComponentsGapRow(2);
            }

            if (lists)
            {
                builder.append(Strings.message("service.sync.in.confirmation.readinglists.to.add"), 3,
                    CheckBoxList.createAllNonePanel(lstAddReadingLists), 1);
                builder.appendRow("50dlu:grow");
                builder.append(new JScrollPane(lstAddReadingLists), 5,
                    CellConstraints.FILL, CellConstraints.FILL);

                builder.appendUnrelatedComponentsGapRow(2);
            }

            return builder.getPanel();
        }

        /**
         * Handles window events depending on the state of the <code>defaultCloseOperation</code>
         * property.
         */
        protected void processWindowEvent(WindowEvent e)
        {
            super.processWindowEvent(e);

            if (e.getID() == WindowEvent.WINDOW_OPENED)
            {
                SwingUtilities.invokeLater(new Runnable()
                {
                    public void run()
                    {
                        pack();
                    }
                });
            }
        }

        /**
         * Shows dialog and updates the list of feeds to add.
         */
        public void open()
        {
            super.open();

            if (!hasBeenCanceled())
            {
                addFeeds.clear();

                ListModel model = lstAddFeeds.getModel();
                for (int i = 0; i < model.getSize(); i++)
                {
                    FeedCheckBox fcb = (FeedCheckBox)model.getElementAt(i);
                    if (fcb.isSelected()) addFeeds.add(fcb.getFeed());
                }

                addReadingLists.clear();

                model = lstAddReadingLists.getModel();
                for (int i = 0; i < model.getSize(); i++)
                {
                    ReadingListCheckBox rlcb = (ReadingListCheckBox)model.getElementAt(i);
                    if (rlcb.isSelected()) addReadingLists.add(rlcb.getList());
                }
            }
        }
    }

    // ---------------------------------------------------------------------------------------------
    // Loading preferences
    // ---------------------------------------------------------------------------------------------

    /**
     * Restores user preferences.
     *
     * @param prefs prefs.
     */
    private void loadUserPreferences(Map<String, Object> prefs)
    {
        loadGeneralPreferences(prefs);
        loadGuidesPreferences(prefs);
        loadFeedsPreferences(prefs);
        loadArticlesPreferences(prefs);
        loadTagsPreferences(prefs);
        loadReadingListsPrefereneces(prefs);
        loadAdvancedPreferences(prefs);
        loadTwitterPreferences(prefs);
        Manager.restoreState(prefs);
    }

    /**
     * Loads general preferences into the model.
     *
     * @param prefs preferences map to take info from.
     */
    private void loadGeneralPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
//        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        up.setCheckingForUpdatesOnStartup(getBoolean(prefs,
            UserPreferences.PROP_CHECKING_FOR_UPDATES_ON_STARTUP,
            up.isCheckingForUpdatesOnStartup()));
// Disabled as we don't like what happens when synchronizing fonts across platforms
//        frs.setMainContentFont(getFont(prefs,
//            RenderingSettingsNames.MAIN_CONTENT_FONT,
//            frs.getMainContentFont()));
        up.setShowToolbar(getBoolean(prefs,
            UserPreferences.PROP_SHOW_TOOLBAR,
            up.isShowToolbar()));

        // Behavior
        up.setMarkReadWhenChangingChannels(getBoolean(prefs,
            UserPreferences.PROP_MARK_READ_WHEN_CHANGING_CHANNELS,
            up.isMarkReadWhenChangingChannels()));
        up.setMarkReadWhenChangingGuides(getBoolean(prefs,
            UserPreferences.PROP_MARK_READ_WHEN_CHANGING_GUIDES,
            up.isMarkReadWhenChangingGuides()));
        up.setMarkReadAfterDelay(getBoolean(prefs,
            UserPreferences.PROP_MARK_READ_AFTER_DELAY,
            up.isMarkReadAfterDelay()));
        up.setMarkReadAfterSeconds(getInt(prefs,
            UserPreferences.PROP_MARK_READ_AFTER_SECONDS,
            up.getMarkReadAfterSeconds()));

        // Updates and Cleanups
        up.setRssPollInterval(getInt(prefs,
            UserPreferences.PROP_RSS_POLL_MIN,
            up.getRssPollInterval()));
        up.setPurgeCount(getInt(prefs,
            UserPreferences.PROP_PURGE_COUNT,
            up.getPurgeCount()));
        up.setPreserveUnread(getBoolean(prefs,
            UserPreferences.PROP_PRESERVE_UNREAD,
            up.isPreserveUnread()));
    }

    /**
     * Loads guides preferences from the map.
     *
     * @param prefs preferences map.
     */
    private void loadGuidesPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        up.setPingOnReadingListPublication(getBoolean(prefs,
            UserPreferences.PROP_PING_ON_RL_PUBLICATION,
            up.isPingOnReadingListPublication()));
        up.setPingOnReadingListPublicationURL(getString(prefs,
            UserPreferences.PROP_PING_ON_RL_PUBLICATION_URL,
            up.getPingOnReadingListPublicationURL()));

        frs.setBigIconInGuides(getBoolean(prefs,
            RenderingSettingsNames.IS_BIG_ICON_IN_GUIDES,
            frs.isBigIconInGuides()));
        frs.setShowUnreadInGuides(getBoolean(prefs,
            "showUnreadInGuides",
            frs.isShowUnreadInGuides()));
        frs.setShowIconInGuides(getBoolean(prefs,
            RenderingSettingsNames.IS_ICON_IN_GUIDES_SHOWING,
            frs.isShowIconInGuides()));
        frs.setShowTextInGuides(getBoolean(prefs,
            RenderingSettingsNames.IS_TEXT_IN_GUIDES_SHOWING,
            frs.isShowTextInGuides()));

        up.setGuideSelectionMode(getInt(prefs,
            UserPreferences.PROP_GUIDE_SELECTION_MODE,
            up.getGuideSelectionMode()));
    }

    /**
     * Loads feeds preferences from the map.
     *
     * @param prefs prefrences map.
     */
    private void loadFeedsPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        frs.setShowStarz(getBoolean(prefs, "showStarz", frs.isShowStarz()));
        frs.setShowUnreadInFeeds(getBoolean(prefs, "showUnreadInFeeds", frs.isShowUnreadInFeeds()));
        frs.setShowActivityChart(getBoolean(prefs, "showActivityChart", frs.isShowActivityChart()));

        getFilterColor(prefs, FeedClass.DISABLED);
        getFilterColor(prefs, FeedClass.INVALID);
        getFilterColor(prefs, FeedClass.LOW_RATED);
        getFilterColor(prefs, FeedClass.READ);
        getFilterColor(prefs, FeedClass.UNDISCOVERED);

        up.setSortingEnabled(getBoolean(prefs,
            UserPreferences.PROP_SORTING_ENABLED,
            up.isSortingEnabled()));
        up.setSortByClass1(getInt(prefs,
            UserPreferences.PROP_SORT_BY_CLASS_1,
            up.getSortByClass1()));
        up.setSortByClass2(getInt(prefs,
            UserPreferences.PROP_SORT_BY_CLASS_2,
            up.getSortByClass2()));
        up.setReversedSortByClass1(getBoolean(prefs,
            UserPreferences.PROP_REVERSED_SORT_BY_CLASS_1,
            up.isReversedSortByClass1()));
        up.setReversedSortByClass2(getBoolean(prefs,
            UserPreferences.PROP_REVERSED_SORT_BY_CLASS_2,
            up.isReversedSortByClass2()));
    }

    /**
     * Loads articles preferences.
     *
     * @param prefs prefs.
     */
    private void loadArticlesPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        frs.setGroupingEnabled(getBoolean(prefs, "groupingEnabled", frs.isGroupingEnabled()));
        frs.setSuppressingOlderThan(getBoolean(prefs,
            "suppressingOlderThan", frs.isSuppressingOlderThan()));
        frs.setDisplayingFullTitles(getBoolean(prefs,
            "displayingFullTitles", frs.isDisplayingFullTitles()));
        frs.setSortingAscending(getBoolean(prefs,
            "sortingAscending", frs.isSortingAscending()));
        frs.setSuppressOlderThan(getInt(prefs,
            "suppressOlderThan", frs.getSuppressOlderThan()));

        up.setCopyLinksInHrefFormat(getBoolean(prefs,
            UserPreferences.PROP_COPY_LINKS_IN_HREF_FORMAT, up.isCopyLinksInHrefFormat()));
        frs.setShowEmptyGroups(getBoolean(prefs, "showEmptyGroups", frs.isShowEmptyGroups()));
        up.setBrowseOnDblClick(getBoolean(prefs,
            UserPreferences.PROP_BROWSE_ON_DBL_CLICK, up.isBrowseOnDblClick()));

        up.getViewModePreferences().restore(prefs);

        up.setAutoExpandMini(getBoolean(prefs, UserPreferences.PROP_AUTO_EXPAND_MINI, up.isAutoExpandMini()));
    }

    /**
     * Loads tags preferences from the map.
     *
     * @param prefs prefs.
     */
    private void loadTagsPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();

        up.setTagsStorage(getInt(prefs, UserPreferences.PROP_TAGS_STORAGE, up.getTagsStorage()));
        up.setTagsDeliciousUser(getString(prefs,
            UserPreferences.PROP_TAGS_DELICIOUS_USER, up.getTagsDeliciousUser()));
        up.setTagsDeliciousPassword(getString(prefs,
            UserPreferences.PROP_TAGS_DELICIOUS_PASSWORD, up.getTagsDeliciousPassword()));
        up.setTagsAutoFetch(getBoolean(prefs,
            UserPreferences.PROP_TAGS_AUTOFETCH, up.isTagsAutoFetch()));

        up.setPinTagging(getBoolean(prefs,
            UserPreferences.PROP_PIN_TAGGING, up.isPinTagging()));
        up.setPinTags(getString(prefs,
            UserPreferences.PROP_PIN_TAGS, up.getPinTags()));
    }

    /**
     * Loads reading lists preferences from the map.
     *
     * @param prefs preferences.
     */
    private void loadReadingListsPrefereneces(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();

        up.setReadingListUpdatePeriod(getLong(prefs,
            UserPreferences.PROP_READING_LIST_UPDATE_PERIOD, up.getReadingListUpdatePeriod()));
        up.setOnReadingListUpdateActions(getInt(prefs,
            UserPreferences.PROP_ON_READING_LIST_UPDATE_ACTIONS,
            up.getOnReadingListUpdateActions()));
        up.setUpdateFeeds(getBoolean(prefs, UserPreferences.PROP_UPDATE_FEEDS, up.isUpdateFeeds()));
        up.setUpdateReadingLists(getBoolean(prefs,
            UserPreferences.PROP_UPDATE_READING_LISTS, up.isUpdateReadingLists()));
    }

    /**
     * Loads advanced preferences from the map.
     *
     * @param prefs preferences.
     */
    private void loadAdvancedPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        StarzPreferences sp = model.getStarzPreferences();

        up.setFeedSelectionDelay(getInt(prefs,
            UserPreferences.PROP_FEED_SELECTION_DELAY, up.getFeedSelectionDelay()));
        up.setAntiAliasText(getBoolean(prefs,
            UserPreferences.PROP_AA_TEXT, up.isAntiAliasText()));

        sp.setTopActivity(getInt(prefs,
            StarzPreferences.PROP_TOP_ACTIVITY, sp.getTopActivity()));
        sp.setTopHighlights(getInt(prefs,
            StarzPreferences.PROP_TOP_HIGHLIGHTS, sp.getTopHighlights()));

        up.setShowToolbarLabels(getBoolean(prefs,
            UserPreferences.PROP_SHOW_TOOLBAR_LABELS, up.isShowToolbarLabels()));
        up.setShowUnreadButtonMenu(getBoolean(prefs,
            UserPreferences.PROP_SHOW_UNREAD_BUTTON_MENU, up.isShowUnreadButtonMenu()));
        up.setFeedImportLimit(getInt(prefs,
            UserPreferences.PROP_FEED_IMPORT_LIMIT, up.getFeedImportLimit()));
    }

    /**
     * Loads twitter preferences from the map.
     *
     * @param prefs preferences.
     */
    private void loadTwitterPreferences(Map prefs)
    {
        TwitterPreferences tp = model.getUserPreferences().getTwitterPreferences();

        tp.setEnabled(getBoolean(prefs, TwitterPreferences.PROP_TWITTER_ENABLED, tp.isEnabled()));
        tp.setScreenName(getString(prefs, TwitterPreferences.PROP_TWITTER_SCREEN_NAME, tp.getScreenName()));
        tp.setAccessToken(getString(prefs, TwitterPreferences.PROP_TWITTER_ACCESS_TOKEN, tp.getAccessToken()));
        tp.setTokenSecret(getString(prefs, TwitterPreferences.PROP_TWITTER_TOKEN_SECRET, tp.getTokenSecret()));
        tp.setProfilePics(getBoolean(prefs, TwitterPreferences.PROP_TWITTER_PROFILE_PICS, tp.isProfilePics()));
        tp.setPasteLink(getBoolean(prefs, TwitterPreferences.PROP_TWITTER_PASTE_LINK, tp.isPasteLink()));
    }

    /**
     * Returns string value taken from the properties map.
     *
     * @param prefs     preferences map.
     * @param name      name of the property.
     * @param def       default value.
     *
     * @return value.
     */
    public static String getString(Map prefs, String name, String def)
    {
        byte[] bytes = (byte[])prefs.get(name);
        return bytes == null ? def : StringUtils.fromUTF8(bytes);
    }

    /**
     * Reads the value of color for a given feed class into the feed display mode manager.
     *
     * @param prefs     preferences map.
     * @param feedClass feed class.
     */
    private static void getFilterColor(Map prefs, int feedClass)
    {
        String str = getString(prefs, "cdmm." + feedClass, null);
        if (str != null)
        {
            Color color = null;
            if (!StringUtils.isEmpty(str)) color = Color.decode(str);
            FeedDisplayModeManager.getInstance().setColor(feedClass, color);
        }
    }

    /**
     * Returns boolean value taken from the properties map.
     *
     * @param prefs     preferences map.
     * @param name      name of the property.
     * @param def       default value.
     *
     * @return value.
     */
    public static boolean getBoolean(Map prefs, String name, boolean def)
    {
        String obj = getString(prefs, name, null);
        return obj == null ? def : "true".equals(obj);
    }

    /**
     * Returns int value taken from the properties map.
     *
     * @param prefs     preferences map.
     * @param name      name of the property.
     * @param def       default value.
     *
     * @return value.
     */
    public static int getInt(Map prefs, String name, int def)
    {
        String obj = getString(prefs, name, null);
        return obj == null ? def : Integer.parseInt(obj);
    }

    /**
     * Returns long value taken from the properties map.
     *
     * @param prefs     preferences map.
     * @param name      name of the property.
     * @param def       default value.
     *
     * @return value.
     */
    private static long getLong(Map prefs, String name, long def)
    {
        String obj = getString(prefs, name, null);
        return obj == null ? def : Long.parseLong(obj);
    }
}
TOP

Related Classes of com.salas.bb.service.sync.SyncIn

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.