/* * Copyright (c) 2015 Jonas Kalderstam. * * 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.nononsenseapps.notepad.data.local.orgmode; import android.annotation.SuppressLint; import com.nononsenseapps.notepad.data.model.orgmode.RemoteTaskListFile; import com.nononsenseapps.notepad.data.model.orgmode.RemoteTaskNode; import com.nononsenseapps.notepad.data.model.sql.Notification; import com.nononsenseapps.notepad.data.model.sql.RemoteTask; import com.nononsenseapps.notepad.data.model.sql.RemoteTaskList; import com.nononsenseapps.notepad.data.model.sql.Task; import com.nononsenseapps.notepad.data.model.sql.TaskList; import org.cowboyprogrammer.org.OrgFile; import org.cowboyprogrammer.org.OrgNode; import org.cowboyprogrammer.org.OrgTimestamp; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class handles conversion from the internal database format to org-mode * fileformat */ public class OrgConverter { private static final String LISTSTYLECOMMENT = "# NONSENSESTYLE: "; private static final String LISTSORTCOMMENT = "# NONSENSESORTING: "; private static final String TASKNODEID = "# NONSENSEID: "; private static final Pattern PatternStyle = Pattern.compile( "#\\s*NONSENSESTYLE:\\s*(.+)\\s*?", Pattern.CASE_INSENSITIVE); private static final Pattern PatternSorting = Pattern .compile("#\\s*NONSENSESORTING:\\s*(.+)\\s*?", Pattern.CASE_INSENSITIVE); // Ending white space used when removed private static final String NonsenseIdPattern = "#\\s*NONSENSEID:\\s*(\\w+)\\s*"; private static final Pattern PatternId = Pattern.compile(NonsenseIdPattern, Pattern.CASE_INSENSITIVE); private static final String TAG = "OrgConverter"; private static Random rand; /** * Generates an id for RemoteTask(List) objects. */ public static String generateId() { final int len = 8; if (rand == null) { rand = new Random(); } String hex = Integer.toHexString(rand.nextInt()); // Pad with zeros if too short while (hex.length() < len) { hex = "0".concat(hex); } return hex.substring(0, len); } /** * Fill in all the properties of the file that should go in the TaskList * object. */ public static void toListFromFile(final TaskList list, final OrgFile file) { // Minus .org extension list.title = file.getFilename().substring(0, file.getFilename().length() - 4); list.sorting = getListSortingFromMeta(file); list.listtype = getListTypeFromMeta(file); } /** * Reads comment section of file. Returns null if not found. */ public static String getListTypeFromMeta(final OrgFile file) { final Matcher m = PatternStyle.matcher(file.getComments()); if (m.find()) { return m.group(1); } else { return null; } } /** * Reads comment section of file. Returns null if not found. */ public static String getListSortingFromMeta(final OrgFile file) { final Matcher m = PatternSorting.matcher(file.getComments()); if (m.find()) { return m.group(1); } else { return null; } } /** * Fill in all the properties of the file that should go in the * RemoteTaskList object. */ public static void toRemoteFromFile(final RemoteTaskList entry, final OrgFile file) { entry.remoteId = file.getFilename(); RemoteTaskListFile.setSorting(entry, getListSortingFromMeta(file)); RemoteTaskListFile.setListType(entry, getListTypeFromMeta(file)); entry.updated = Calendar.getInstance().getTimeInMillis(); } /** * Fill in all the properties of the node that should go in the Task object. */ public static void toTaskFromNode(final Task task, final OrgNode node) { task.title = node.getTitle(); task.due = getDeadline(node); task.completed = getCompleted(node); task.note = node.getBody(); /* * It's not possible to differentiate if the user added a trailing * newline or the sync logic did. I will assume that the sync logic did. */ if (task.note != null && !task.note.isEmpty() && task.note.endsWith("\n")) { task.note = task.note.substring(0, task.note.length() - 1); } } /** * Fill in all the properties of the nodes from the task object. */ public static void toNodeFromTask(final Task task, final OrgNode node) { node.setLevel(1); node.setTitle(task.title); node.setBody(task.note); setTodo(node, task.completed); removeTimestamps(node); setDeadline(node, task.due); } private static void setNotifications(final OrgNode node, final List<Notification> reminders) { if (reminders == null) return; for (Notification reminder : reminders) { if (reminder.radius == null && reminder.time != null) { OrgTimestamp ts = new OrgTimestamp(reminder.time, true); ts.setInactive(false); node.getTimestamps().add(ts); } } } /** * Returns NOW as completed if node is DONE. Else null. */ private static Long getCompleted(final OrgNode node) { if ("DONE".equals(node.getTodo())) { return new Date().getTime(); } else { return null; } } private static void setTodo(final OrgNode node, final Long completed) { if (completed == null) { node.setTodo("TODO"); } else { node.setTodo("DONE"); } } /** * Return the (first) deadline of the object, or null */ public static Long getDeadline(final OrgNode node) { for (OrgTimestamp ts : node.getTimestamps()) { if (OrgTimestamp.Type.DEADLINE == ts.getType()) { return ts.getDate().toDate().getTime(); } } return null; } private static void removeTimestamps(final OrgNode node) { node.getTimestamps().clear(); } public static void setDeadline(final OrgNode node, final Long due) { node.getTimestamps().clear(); // Add deadline if not null if (due != null) { OrgTimestamp ts = new OrgTimestamp(due, false); ts.setType(OrgTimestamp.Type.DEADLINE); node.getTimestamps().add(ts); } } /** * Remove a possible comment containing a nonsenseid */ private static String getOrgBodySansId(final OrgNode node) { return node.getOrgBody().replaceFirst(NonsenseIdPattern, ""); } /** * Fill in all the properties of the node that should go in the RemoteTask * object. If no ID is set, then an ID is added to both the entry and the * node. This method returns true if an id was added to the node, and it * should be updated in file. */ public static boolean toRemoteFromNode(final RemoteTask dbEntry, final OrgNode node) { boolean addedToNode = false; if (dbEntry.remoteId == null) { String id = getNodeId(node); if (id == null) { id = generateId(); addIdToNode(id, node); addedToNode = true; } dbEntry.remoteId = id; } dbEntry.updated = Calendar.getInstance().getTimeInMillis(); RemoteTaskNode.setTitle(dbEntry, node.getTitle()); RemoteTaskNode.setBody(dbEntry, node.getBody()); final Long t = getDeadline(node); String s = null; if (t != null) s = Long.toString(t); RemoteTaskNode.setDueTime(dbEntry, s); RemoteTaskNode.setTodo(dbEntry, node.getTodo()); return addedToNode; } /** * Add an id to the meta-section of a node. */ @SuppressLint("DefaultLocale") private static void addIdToNode(final String id, final OrgNode node) { node.setComments(new StringBuilder(TASKNODEID).append(id.toUpperCase()) .append("\n").toString()); } /** * Set the meta section to be only the id. This does not overwrite rest of * comments since they are stored in the task and sent to the body. */ @SuppressLint("DefaultLocale") public static void toNodeFromRemote(final OrgNode node, final RemoteTask dbEntry) { node.setComments(TASKNODEID + dbEntry.remoteId.toUpperCase() + "\n"); } /** * Returns the id from the meta-section, if present. Null otherwise. */ @SuppressLint("DefaultLocale") public static String getNodeId(final OrgNode node) { final Matcher m = PatternId.matcher(node.getComments()); if (m.find()) { return m.group(1).toUpperCase(); } else { return null; } } /** * Fills in the information in the file from the list */ public static void toFileFromList(final TaskList list, final OrgFile file) { setSortingOnFile(list, file); setListTypeOnFile(list, file); } public static String getTitleAsFilename(TaskList list) { return list.title + ".org"; } public static void setListTypeOnFile(TaskList list, OrgFile file) { final StringBuilder comments = new StringBuilder(); if (list.listtype != null) { comments.append(LISTSTYLECOMMENT).append(list.listtype) .append("\n"); } comments.append(PatternStyle.matcher(file.getComments()).replaceAll ("").trim()); file.setComments(comments.toString()); } public static void setSortingOnFile(final TaskList list, final OrgFile file) { final StringBuilder comments = new StringBuilder(); if (list.sorting != null) { comments.append(LISTSORTCOMMENT).append(list.sorting).append("\n"); } comments.append(PatternSorting.matcher(file.getComments()).replaceAll ("").trim()); file.setComments(comments.toString()); } }