package org.eclipse.buckminster.rssowl; import org.eclipse.buckminster.core.metadata.WorkspaceInfo; import org.eclipse.buckminster.core.metadata.model.Resolution; import org.eclipse.buckminster.generic.utils.ProgressUtils; import org.eclipse.buckminster.opml.IBody; import org.eclipse.buckminster.opml.IOutline; import org.eclipse.buckminster.opml.model.OPML; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.rssowl.core.Owl; import org.rssowl.core.persist.IBookMark; import org.rssowl.core.persist.IFeed; import org.rssowl.core.persist.IFolder; import org.rssowl.core.persist.IMark; import org.rssowl.core.persist.ISearchMark; import org.rssowl.core.persist.dao.DynamicDAO; import org.rssowl.core.persist.dao.IFeedDAO; import org.rssowl.core.persist.dao.IFolderDAO; import org.rssowl.core.persist.reference.FeedLinkReference; import org.rssowl.core.persist.reference.FeedReference; import org.rssowl.core.util.URIUtils; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Synchronizes the OPML for all resolutions with Rss Owl bookmarks and feeds. * * @author Henrik Lindberg */ public class OwlSynchronizer { private static URI s_componentBlogroll; /** * Synchronizes the bookmarks for all resolutions. * * @throws CoreException */ public static void syncAllResolutions(IProgressMonitor monitor) throws CoreException { final IProgressMonitor waitingForResolutionsMonitor = ProgressUtils.submon(monitor, 1); waitingForResolutionsMonitor.beginTask("Waiting for resolutions", IProgressMonitor.UNKNOWN); FolderState root = getComponentsFolder(); List<Resolution> resolutions = null; waitingForResolutionsMonitor.worked(1); resolutions = WorkspaceInfo.getAllResolutions(); waitingForResolutionsMonitor.done(); if(resolutions == null) return; final IProgressMonitor bookmarkSyncMonitor = ProgressUtils.submon(monitor, 1); bookmarkSyncMonitor.beginTask("Synchronizing Bookmarks", resolutions.size()+2); for(Resolution r : resolutions) { bookmarkSyncMonitor.worked(1); // A resolution without OPML, or where the Body is null or has no outlines // is not worth processing. We may still end up with an empty folder though if all // the links in the outline are just url links... // OPML opml = r.getOPML(); if(opml == null || opml.getBody() == null || opml.getBody().getOutlines().size() < 1) continue; FolderState cf = root.createFolder(r.getName()); sync(r, cf); } //--SAVE everything to keep (this is easy since the OWL DAO cascades all the changes. // root.save(); bookmarkSyncMonitor.worked(1); //--REMOVE everything else root.removeUnused(); bookmarkSyncMonitor.worked(1); bookmarkSyncMonitor.done(); } /** * Updates one selected Resolution with Owl bookmarks. * The folder for the resolution will not be removed if the resolution has changed from * having OPML to being empty as this requires synchronizing the parent folder. * * @param resolution * @throws CoreException */ public static void syncResolution(Resolution resolution) throws CoreException { FolderState root = getComponentsFolder(); FolderState cf = root.createFolder(resolution.getName()); sync(resolution, cf); root.save(); // Do NOT call root.removeUnused as that would remove everything else but the // just synchronized folder! (As we did not mark any other folder as "keep"). cf.removeUnused(); } /** * Synchronize the resolution and the folder. The resolution must have opml with * at least one outline in the body. * @param list * @param componentFolder */ private static void sync(Resolution resolution, FolderState componentFolder) { OPML opml = resolution.getOPML(); IBody body = opml.getBody(); if(body == null) return; // empty OPML sync(body.getOutlines(), componentFolder); } private static void sync(List<? extends IOutline> outlines, FolderState folder) { if(outlines == null) return; for(IOutline outline : outlines) { // An outline is just a link if it has an url property and such outlines are ignored // as rssowl only handles feeds. if(outline.getUrl() != null) continue; // An outline that has an "XmlUrl" attribute is a feed reference, // and an outline that is not a feed is a folder if(outline.getXmlUrl() == null) sync(outline.getOutlines(), folder.createFolder(outline.getText())); else folder.createMark(outline); } } /** * If the buckminster root folder in OWL is not already created, it is automatically * created. * @return the Buckminster Component's root folder. */ private static FolderState getComponentsFolder() { // Create URI blogroll = getComponentBlogroll(); IFolderDAO folderDAO = DynamicDAO.getDAO(IFolderDAO.class); // Get root folders and search for the buckminster component folder. // If not found, create the folder. // Collection<IFolder> rootFolders = folderDAO.loadRoots(); IFolder componentsFolder = null; for(IFolder f : rootFolders) { if("Components".equals(f.getName()) && blogroll.equals(f.getBlogrollLink())) //$NON-NLS-1$ { // found it componentsFolder = f; break; } } if(componentsFolder == null) { // Create the components root folder. // Set a fake blogroll to make it special componentsFolder = Owl.getModelFactory().createFolder(null, null, "Components"); //$NON-NLS-1$ componentsFolder.setBlogrollLink(blogroll); folderDAO.save(componentsFolder); // Return the newly created folder state return new FolderState(componentsFolder, true); } // return the folder state for the existing root folder // (this will read the entire tree of folders) return new FolderState(componentsFolder, false); } /** * Returns a "fake" URI used to represent the internal blogroll of all components in the * workspace. * * @return */ private static synchronized URI getComponentBlogroll() { if(s_componentBlogroll == null) s_componentBlogroll = URIUtils.createURI("http://org.eclipse.buckminster.rssowl/Components"); //$NON-NLS-1$ return s_componentBlogroll; } private static class MarkState { private IBookMark m_mark; private boolean m_keep; public MarkState(IBookMark mark, boolean keep) { m_mark = mark; m_keep = keep; } public void setKeep(boolean keep) { m_keep = keep; } public boolean isKeep() { return m_keep; } public IBookMark getMark() { return m_mark; } } private static class FolderState { private IFolder m_folder; private List<IFolder> m_children; private Map<String, FolderState> m_folderMap; private Map<String, MarkState> m_markMap; private boolean m_keep; public FolderState(IFolder folder, boolean infant) { m_folder = folder; m_children = infant ? new ArrayList<IFolder>(0) : folder.getFolders(); m_folderMap = new HashMap<String, FolderState>(m_children.size()); for(IFolder f : m_children) m_folderMap.put(f.getName(), new FolderState(f, false)); m_keep = false; // get the marks - these can be both search marks, and bookmarks // search marks complicates things - a user may have entered a search mark // that has the same name as a bookmark from the component, hence step one // is to drop all found search marks found inside the synchronized structure. // List<IMark> marks = folder.getMarks(); m_markMap = new HashMap<String, MarkState>(marks.size()); for(IMark mark : marks) { if(mark instanceof ISearchMark) DynamicDAO.delete(mark); if(mark instanceof IBookMark) m_markMap.put(mark.getName(), new MarkState((IBookMark)mark, false)); } } public void save() { DynamicDAO.save(m_folder); } public void removeUnused() { // All folders not marked as keep should be deleted // All folders that are kept should be visited so they can remove their unused // folders for(FolderState fs : m_folderMap.values()) { if(fs.isKeep()) fs.removeUnused(); else DynamicDAO.delete(fs.m_folder); } // All bookmarks not marked as keep should be deleted for(MarkState ms : m_markMap.values()) { if(!ms.isKeep()) DynamicDAO.delete(ms.getMark()); } } /** * Returns a folder state for the stated name after possibly creating the folder * first. If folder existed it is marked as a folder to keep. * @param name * @return */ public FolderState createFolder(String name) { FolderState fs = m_folderMap.get(name); if(fs == null) { fs = new FolderState(Owl.getModelFactory().createFolder(null, m_folder, name), true); m_folderMap.put(name, fs); } fs.setKeep(true); return fs; } protected void setKeep(boolean keep) { m_keep = keep; } public boolean isKeep() { return m_keep; } public void createMark(IOutline outline) { if(outline.getXmlUrl() != null) { // Check if a Feed with the XML URL already exists IFeedDAO feedDao = Owl.getPersistenceService().getDAOService().getFeedDAO(); FeedReference feedRef = feedDao.loadReference(outline.getXmlUrl()); // Create if non existing if(feedRef == null) { IFeed feed = Owl.getModelFactory().createFeed(null, outline.getXmlUrl()); feed.setHomepage(outline.getHtmlUrl()); feed.setDescription(outline.getDescription()); feed = DynamicDAO.save(feed); } // Is the Bookmark already defined? String label = outline.getTitle(); label = label == null ? outline.getText() : label; label = label == null ? outline.getXmlUrl().toString() : label; MarkState mark = m_markMap.get(label); if(mark != null) { // mark existed already - the feed may be wrong though FeedLinkReference flr = mark.getMark().getFeedLinkReference(); if(!flr.getLink().equals(outline.getXmlUrl())) mark.getMark().setFeedLinkReference(new FeedLinkReference(outline.getXmlUrl())); mark.setKeep(true); } else { // Create the BookMark FeedLinkReference feedLinkRef = new FeedLinkReference(outline.getXmlUrl()); IBookMark created = Owl.getModelFactory().createBookMark(null, m_folder, feedLinkRef, label); // record that this is a new bookmark we want to keep m_markMap.put(created.getName(), new MarkState(created, true)); } } } } }