protected void doRun() throws Exception {
    SyncWatchEvent lastSyncWatchEvent = SyncWatchEventService.getLastSyncWatchEvent(_syncAccountId);

    if (lastSyncWatchEvent == null) {
      return;
    }

    long delta = System.currentTimeMillis() - lastSyncWatchEvent.getTimestamp();

    if (delta <= 500) {
      SyncEngineUtil.fireSyncEngineStateChanged(
          _syncAccountId, SyncEngineUtil.SYNC_ENGINE_STATE_PROCESSING);

      return;
    }

    if (_logger.isTraceEnabled()) {
      _logger.trace("Processing Sync watch events");
    }

    _pendingTypePKSyncFileIds.clear();

    List<SyncWatchEvent> syncWatchEvents = null;

    if (OSDetector.isApple()) {
      syncWatchEvents = SyncWatchEventService.findBySyncAccountId(_syncAccountId);
    } else {
      syncWatchEvents =
          SyncWatchEventService.findBySyncAccountId(_syncAccountId, "eventType", true);
    }

    for (SyncWatchEvent syncWatchEvent : syncWatchEvents) {
      processSyncWatchEvent(syncWatchEvent);
    }

    for (Map.Entry<String, List<SyncWatchEvent>> entry : _dependentSyncWatchEventsMaps.entrySet()) {

      SyncFile syncFile = SyncFileService.fetchSyncFile(entry.getKey());

      if ((syncFile != null) && (syncFile.getTypePK() > 0)) {
        for (SyncWatchEvent syncWatchEvent : entry.getValue()) {
          processSyncWatchEvent(syncWatchEvent);
        }
      }
    }

    SyncEngineUtil.fireSyncEngineStateChanged(
        _syncAccountId, SyncEngineUtil.SYNC_ENGINE_STATE_PROCESSED);

    _processedSyncWatchEventIds.clear();
  }
  protected synchronized void processSyncWatchEvent(SyncWatchEvent syncWatchEvent)
      throws Exception {

    SyncAccount syncAccount = SyncAccountService.fetchSyncAccount(_syncAccountId);

    if (syncAccount.getState() != SyncAccount.STATE_CONNECTED) {
      return;
    }

    if (_processedSyncWatchEventIds.contains(syncWatchEvent.getSyncWatchEventId())) {

      SyncWatchEventService.deleteSyncWatchEvent(syncWatchEvent.getSyncWatchEventId());

      return;
    }

    String eventType = syncWatchEvent.getEventType();

    if (eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME_FROM)) {
      eventType = SyncWatchEvent.EVENT_TYPE_DELETE;

      syncWatchEvent.setEventType(eventType);

      SyncWatchEventService.update(syncWatchEvent);
    }

    if (_logger.isDebugEnabled()) {
      _logger.debug("Processing Sync watch event {}", syncWatchEvent.toString());
    }

    String fileType = syncWatchEvent.getFileType();

    if (eventType.equals(SyncWatchEvent.EVENT_TYPE_CREATE)) {
      if (fileType.equals(SyncFile.TYPE_FILE)) {
        SyncWatchEvent duplicateSyncWatchEvent = null;

        if (OSDetector.isApple()) {
          duplicateSyncWatchEvent =
              SyncWatchEventService.fetchDuplicateSyncWatchEvent(syncWatchEvent);
        }

        if (duplicateSyncWatchEvent != null) {
          if (_logger.isDebugEnabled()) {
            _logger.debug("Skipping outdated Sync watch event");
          }
        } else {
          addFile(syncWatchEvent);
        }
      } else {
        addFolder(syncWatchEvent);
      }
    } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_DELETE)) {
      deleteFile(syncWatchEvent);
    } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_MODIFY)) {
      SyncWatchEvent duplicateSyncWatchEvent =
          SyncWatchEventService.fetchDuplicateSyncWatchEvent(syncWatchEvent);

      if (duplicateSyncWatchEvent != null) {
        if (_logger.isDebugEnabled()) {
          _logger.debug("Skipping outdated Sync watch event");
        }
      } else {
        modifyFile(syncWatchEvent);
      }
    } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_MOVE)) {
      moveFile(syncWatchEvent);
    } else if (eventType.equals(SyncWatchEvent.EVENT_TYPE_RENAME)) {
      renameFile(syncWatchEvent);
    }

    syncAccount = SyncAccountService.fetchSyncAccount(_syncAccountId);

    if (syncAccount.getState() == SyncAccount.STATE_CONNECTED) {
      SyncWatchEventService.deleteSyncWatchEvent(syncWatchEvent.getSyncWatchEventId());
    }
  }
  protected void sendFile(
      final ChannelHandlerContext channelHandlerContext,
      FullHttpRequest fullHttpRequest,
      SyncFile syncFile)
      throws Exception {

    Path path = Paths.get(syncFile.getFilePathName());

    if (Files.notExists(path)) {
      _syncTrafficShapingHandler.decrementConnectionsCount();

      if (_logger.isTraceEnabled()) {
        Channel channel = channelHandlerContext.channel();

        _logger.trace("Client {}: file not found {}", channel.remoteAddress(), path);
      }

      _sendError(channelHandlerContext, NOT_FOUND);

      return;
    }

    if (_logger.isDebugEnabled()) {
      Channel channel = channelHandlerContext.channel();

      _logger.debug("Client {}: sending file {}", channel.remoteAddress(), path);
    }

    long modifiedTime = syncFile.getModifiedTime();
    long previousModifiedTime = syncFile.getPreviousModifiedTime();

    if (OSDetector.isApple()) {
      modifiedTime = modifiedTime / 1000 * 1000;
      previousModifiedTime = previousModifiedTime / 1000 * 1000;
    }

    FileTime currentFileTime = Files.getLastModifiedTime(path, LinkOption.NOFOLLOW_LINKS);

    long currentTime = currentFileTime.toMillis();

    if ((currentTime != modifiedTime) && (currentTime != previousModifiedTime)) {

      _syncTrafficShapingHandler.decrementConnectionsCount();

      Channel channel = channelHandlerContext.channel();

      _logger.error(
          "Client {}: file modified {}, currentTime {}, modifiedTime "
              + "{}, previousModifiedTime {}",
          channel.remoteAddress(),
          path,
          currentTime,
          modifiedTime,
          previousModifiedTime);

      _sendError(channelHandlerContext, NOT_FOUND);

      return;
    }

    HttpResponse httpResponse = new DefaultHttpResponse(HTTP_1_1, OK);

    long size = Files.size(path);

    HttpUtil.setContentLength(httpResponse, size);

    HttpHeaders httpHeaders = httpResponse.headers();

    MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();

    httpHeaders.set(
        HttpHeaderNames.CONTENT_TYPE, mimetypesFileTypeMap.getContentType(syncFile.getName()));

    if (HttpUtil.isKeepAlive(fullHttpRequest)) {
      httpHeaders.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
    }

    channelHandlerContext.write(httpResponse);

    SyncChunkedFile syncChunkedFile = new SyncChunkedFile(path, size, 4 * 1024 * 1024, currentTime);

    ChannelFuture channelFuture =
        channelHandlerContext.writeAndFlush(
            new HttpChunkedInput(syncChunkedFile), channelHandlerContext.newProgressivePromise());

    channelFuture.addListener(
        new ChannelFutureListener() {

          @Override
          public void operationComplete(ChannelFuture channelFuture) throws Exception {

            _syncTrafficShapingHandler.decrementConnectionsCount();

            if (channelFuture.isSuccess()) {
              return;
            }

            Throwable exception = channelFuture.cause();

            Channel channel = channelHandlerContext.channel();

            _logger.error(
                "Client {}: {}", channel.remoteAddress(), exception.getMessage(), exception);

            channelHandlerContext.close();
          }
        });

    if (!HttpUtil.isKeepAlive(fullHttpRequest)) {
      channelFuture.addListener(ChannelFutureListener.CLOSE);
    }
  }