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}