/* * 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 android.content.Context; import android.os.Environment; import android.os.FileObserver; import com.nononsenseapps.notepad.data.service.OrgSyncService; import com.nononsenseapps.notepad.util.PermissionsHelper; import com.nononsenseapps.notepad.util.SharedPreferencesHelper; import org.cowboyprogrammer.org.OrgFile; import org.cowboyprogrammer.org.parser.OrgParser; import org.cowboyprogrammer.org.parser.RegexParser; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.util.HashSet; /** * A synchronizer that that uses an external directory on the SD-card as * destination. */ public class SDSynchronizer extends Synchronizer implements SynchronizerInterface { // Where files are kept. User changeable in preferences. public static final String DEFAULT_ORG_DIR = Environment .getExternalStorageDirectory().toString() + "/NoNonsenseNotes"; public final static String SERVICENAME = "SDORG"; protected String ORG_DIR; protected final boolean configured; public SDSynchronizer(Context context) { super(context); ORG_DIR = SharedPreferencesHelper.getSdDir(context); final boolean permitted = PermissionsHelper.hasPermissions(context, PermissionsHelper.PERMISSIONS_SD); if (permitted) { configured = SharedPreferencesHelper.isSdSyncEnabled(context); } else { configured = false; SharedPreferencesHelper.disableSdCardSync(context); } } /** * @return A unique name for this service. Should be descriptive, like * DropboxOrg, SDOrg or SSHOrg. */ @Override public String getAccountName() { return SERVICENAME; } /** * @return The username of the configured service. Likely an e-mail. */ @Override public String getServiceName() { return SERVICENAME; } /** * Returns true if the synchronizer has been configured. This is called * before synchronization. It will be true if the user has selected an * account, folder etc... */ @Override public boolean isConfigured() { // TODO handle errors if (this.configured) { File d = new File(ORG_DIR); if (!d.isDirectory()) { d.mkdir(); } return this.configured && d.isDirectory(); } return this.configured; } /** * Returns an OrgFile object with a filename set that is guaranteed to * not already exist. Use this method to avoid having multiple objects * pointing to the same file. Also prevents names with slashes. * * @param desiredName The name you'd want. If it exists, * it will be used as the base in desiredName1, * desiredName2, etc. Limited to 99. * @return an OrgFile guaranteed not to exist. * @throws java.io.IOException */ @Override public OrgFile getNewFile(final String orgdesiredName) throws IOException, IllegalArgumentException { OrgParser orgParser = new RegexParser(); // Replace slashes with underscores String desiredName = orgdesiredName.replace("/", "_"); String filename; for (int i = 0; i < 100; i++) { if (i == 0) { filename = desiredName + ".org"; } else { filename = desiredName + i + ".org"; } File f = new File(ORG_DIR, filename); if (!f.exists()) { return new OrgFile(orgParser, filename); } } throw new IllegalArgumentException("Filename not accessible"); } /** * Replaces the file on the remote end with the given content. * * @param orgFile The file to save. Uses the filename stored in the object. */ @Override public void putRemoteFile(OrgFile orgFile) throws IOException { final File file = new File(ORG_DIR, orgFile.getFilename()); final BufferedWriter bw = new BufferedWriter(new FileWriter(file)); bw.write(orgFile.treeToString()); bw.close(); } /** * Delete the file on the remote end. * * @param orgFile The file to delete. */ @Override public void deleteRemoteFile(OrgFile orgFile) { if (orgFile != null && orgFile.getFilename() != null) { final File file = new File(ORG_DIR, orgFile.getFilename()); file.delete(); } } /** * Rename the file on the remote end. * * @param oldName The name it is currently stored as on the remote end. * @param orgFile */ @Override public void renameRemoteFile(String oldName, OrgFile orgFile) { if (orgFile == null || orgFile.getFilename() == null) { throw new NullPointerException("No new filename"); } final File oldFile = new File(ORG_DIR, oldName); final File newFile = new File(ORG_DIR, orgFile.getFilename()); oldFile.renameTo(newFile); } /** * Returns a BufferedReader to the remote file. Null if it doesn't exist. * * @param filename Name of the file, without path */ @Override public BufferedReader getRemoteFile(String filename) { final File file = new File(ORG_DIR, filename); BufferedReader br = null; if (file.exists()) { try { br = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e) { br = null; } } return br; } /** * @return a set of all remote files. */ @SuppressLint("DefaultLocale") @Override public HashSet<String> getRemoteFilenames() { final HashSet<String> filenames = new HashSet<String>(); final File dir = new File(ORG_DIR); final File[] files = dir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".org"); } }); if (files != null) { for (File f : files) { filenames.add(f.getName()); } } return filenames; } /** * Use this to disconnect from any services and cleanup. */ @Override public void postSynchronize() { // Nothing to do } @Override public Monitor getMonitor() { return new FileWatcher(ORG_DIR); } public class FileWatcher extends FileObserver implements Monitor { public OrgSyncService.SyncHandler handler; private int changeId = 0; public FileWatcher(String path) { super(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.DELETE_SELF | FileObserver.MODIFY | FileObserver.MOVE_SELF | FileObserver.MOVED_FROM | FileObserver.MOVED_TO); } @Override public void onEvent(int event, String path) { if (handler != null) { handler.onMonitorChange(); } } @Override public void startMonitor(final OrgSyncService.SyncHandler handler) { this.handler = handler; startWatching(); } @Override public void pauseMonitor() { stopWatching(); handler = null; } @Override public void terminate() { stopWatching(); } } }