001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.operations; 019 020import java.awt.MouseInfo; 021import java.awt.Point; 022import java.awt.Robot; 023import java.io.File; 024import java.util.Timer; 025import java.util.TimerTask; 026import java.util.logging.Level; 027import java.util.logging.Logger; 028 029import org.syncany.config.UserConfig; 030import org.syncany.operations.cleanup.CleanupOperation; 031import org.syncany.plugins.transfer.StorageException; 032import org.syncany.plugins.transfer.TransferManager; 033import org.syncany.plugins.transfer.files.ActionRemoteFile; 034 035/** 036 * The action handler manages the {@link ActionRemoteFile}s written during an {@link Operation}. 037 * 038 * <p>In particular, it uploads an initial action file when the operation is started, deletes it 039 * when it is finished/terminated, and renews the operation's action file in a given interval. 040 * 041 * <p>The renewal is necessary to show other clients that the operation is still running. To ensure 042 * action file renewal, the {@link #start()} method starts a timer that uploads a new {@link ActionRemoteFile} 043 * every {@link #ACTION_RENEWAL_INTERVAL} milliseconds. The {@link #finish()} method stops this timer. 044 * 045 * @see CleanupOperation 046 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 047 */ 048public class ActionFileHandler { 049 private static final Logger logger = Logger.getLogger(ActionFileHandler.class.getSimpleName()); 050 051 /** 052 * Defines the time that the action files updated while an operation is running. 053 * 054 * This time period must be (significantly) smaller than the ignore time defined in 055 * {@link CleanupOperation#ACTION_FILE_DELETE_TIME}. 056 */ 057 public static final int ACTION_RENEWAL_INTERVAL = 2*60*1000; // Minutes 058 059 private TransferManager transferManager; 060 private ActionRemoteFile actionFile; 061 private Timer actionRenewalTimer; 062 063 public ActionFileHandler(TransferManager transferManager, String operationName, String machineName) { 064 try { 065 this.transferManager = transferManager; 066 this.actionFile = new ActionRemoteFile(operationName, machineName, System.currentTimeMillis()); 067 this.actionRenewalTimer = createNewActionRenewalTimer(); 068 } 069 catch (Exception e) { 070 throw new RuntimeException(e); 071 } 072 } 073 074 private Timer createNewActionRenewalTimer() { 075 return new Timer("ActRenewTim"); 076 } 077 078 public void start() throws Exception { 079 logger.log(Level.INFO, "Starting action for " + actionFile + " ..."); 080 081 uploadActionFile(actionFile); 082 scheduleActionRenewalTask(); 083 } 084 085 public void finish() throws StorageException { 086 logger.log(Level.INFO, "Finishing action for " + actionFile + " ..."); 087 088 cancelActionRenewalTask(); 089 deleteActionFile(actionFile); 090 } 091 092 private void deleteActionFile(ActionRemoteFile actionFile) throws StorageException { 093 logger.log(Level.INFO, "Deleting action file: " + actionFile); 094 transferManager.delete(actionFile); 095 } 096 097 private void uploadActionFile(ActionRemoteFile actionFile) throws Exception { 098 logger.log(Level.INFO, "Uploading action file: " + actionFile); 099 100 File tempActionFile = File.createTempFile("syncany-action-", ".tmp"); 101 tempActionFile.deleteOnExit(); 102 103 transferManager.upload(tempActionFile, actionFile); 104 105 tempActionFile.delete(); 106 } 107 108 private void scheduleActionRenewalTask() { 109 logger.log(Level.INFO, "Scheduling action renewal task for every " + (ACTION_RENEWAL_INTERVAL/60/1000) + " minutes, for " + actionFile + " ..."); 110 111 actionRenewalTimer.schedule(new TimerTask() { 112 @Override 113 public void run() { 114 renewActionFile(); 115 116 if (UserConfig.isPreventStandby()) { 117 preventStandby(); 118 } 119 } 120 }, ACTION_RENEWAL_INTERVAL, ACTION_RENEWAL_INTERVAL); 121 } 122 123 private void cancelActionRenewalTask() { 124 actionRenewalTimer.cancel(); 125 actionRenewalTimer = createNewActionRenewalTimer(); 126 } 127 128 private synchronized void renewActionFile() { 129 try { 130 logger.log(Level.INFO, "Scheduling action renewal task for every " + (ACTION_RENEWAL_INTERVAL/60/1000) + " minutes, for " + actionFile + " ..."); 131 132 ActionRemoteFile oldActionFile = actionFile; 133 ActionRemoteFile newActionFile = new ActionRemoteFile(oldActionFile.getOperationName(), oldActionFile.getClientName(), System.currentTimeMillis()); 134 135 uploadActionFile(newActionFile); 136 deleteActionFile(oldActionFile); 137 138 actionFile = newActionFile; 139 } 140 catch (Exception e) { 141 logger.log(Level.SEVERE, "ERROR: Cannot renew action file!", e); 142 } 143 } 144 145 private void preventStandby() { 146 try { 147 Robot robot = new Robot(); 148 149 Point currentMousePosition = MouseInfo.getPointerInfo().getLocation(); 150 Point tempMousePosition = (currentMousePosition.x > 0) ? new Point(currentMousePosition.x - 10, currentMousePosition.y) : new Point( 151 currentMousePosition.x + 10, currentMousePosition.y); 152 153 logger.log(Level.INFO, "Standby prevention: Moving mouse 1px (and back): " + currentMousePosition); 154 155 robot.mouseMove(tempMousePosition.x, tempMousePosition.y); 156 robot.mouseMove(currentMousePosition.x, currentMousePosition.y); 157 } 158 catch (Exception e) { 159 if (e.getMessage() != null && e.getMessage().contains("headless")) { 160 logger.log(Level.INFO, "Cannot prevent standby, because headless mode is enabled (no GUI environment)"); 161 } 162 else { 163 logger.log(Level.WARNING, "Standby prevention failed (headless mode?).", e); 164 } 165 } 166 } 167}