+(defun gnus-cloud-all-files ()
+ (let ((files nil))
+ (dolist (elem gnus-cloud-synced-files)
+ (cond
+ ((stringp elem)
+ (push elem files))
+ ((consp elem)
+ (dolist (file (directory-files (plist-get elem :directory)
+ nil
+ (plist-get elem :match)))
+ (push (format "%s/%s"
+ (directory-file-name (plist-get elem :directory))
+ file)
+ files)))))
+ (nreverse files)))
+
+(defvar gnus-cloud-file-timestamps nil)
+
+(defun gnus-cloud-files-to-upload (&optional full)
+ (let ((files nil)
+ timestamp)
+ (dolist (file (gnus-cloud-all-files))
+ (if (file-exists-p file)
+ (when (setq timestamp (gnus-cloud-file-new-p file full))
+ (push `(:type :file :file-name ,file :timestamp ,timestamp) files))
+ (when (assoc file gnus-cloud-file-timestamps)
+ (push `(:type :delete :file-name ,file) files))))
+ (nreverse files)))
+
+(defun gnus-cloud-file-new-p (file full)
+ (let ((timestamp (format-time-string
+ "%FT%T%z" (nth 5 (file-attributes file))))
+ (old (cadr (assoc file gnus-cloud-file-timestamps))))
+ (when (or full
+ (null old)
+ (string< old timestamp))
+ timestamp)))
+
+(declare-function gnus-activate-group "gnus-start"
+ (group &optional scan dont-check method dont-sub-check))
+(declare-function gnus-subscribe-group "gnus-start"
+ (group &optional previous method))
+
+(defun gnus-cloud-ensure-cloud-group ()
+ (let ((method (if (stringp gnus-cloud-method)
+ (gnus-server-to-method gnus-cloud-method)
+ gnus-cloud-method)))
+ (unless (or (gnus-active gnus-cloud-group-name)
+ (gnus-activate-group gnus-cloud-group-name nil nil
+ gnus-cloud-method))
+ (and (gnus-request-create-group gnus-cloud-group-name gnus-cloud-method)
+ (gnus-activate-group gnus-cloud-group-name nil nil gnus-cloud-method)
+ (gnus-subscribe-group gnus-cloud-group-name)))))
+
+(defun gnus-cloud-upload-data (&optional full)
+ (gnus-cloud-ensure-cloud-group)
+ (with-temp-buffer
+ (let ((elems (gnus-cloud-files-to-upload full)))
+ (insert (format "Subject: (sequence: %d type: %s)\n"
+ gnus-cloud-sequence
+ (if full :full :partial)))
+ (insert "From: nobody@invalid.com\n")
+ (insert "\n")
+ (insert (gnus-cloud-make-chunk elems))
+ (when (gnus-request-accept-article gnus-cloud-group-name gnus-cloud-method
+ t t)
+ (setq gnus-cloud-sequence (1+ gnus-cloud-sequence))
+ (gnus-cloud-add-timestamps elems)))))
+
+(defun gnus-cloud-add-timestamps (elems)
+ (dolist (elem elems)
+ (let* ((file-name (plist-get elem :file-name))
+ (old (assoc file-name gnus-cloud-file-timestamps)))
+ (when old
+ (setq gnus-cloud-file-timestamps
+ (delq old gnus-cloud-file-timestamps)))
+ (push (list file-name (plist-get elem :timestamp))
+ gnus-cloud-file-timestamps))))
+
+(defun gnus-cloud-available-chunks ()
+ (gnus-activate-group gnus-cloud-group-name nil nil gnus-cloud-method)
+ (let* ((group (gnus-group-full-name gnus-cloud-group-name gnus-cloud-method))
+ (active (gnus-active group))
+ headers head)
+ (when (gnus-retrieve-headers (gnus-uncompress-range active) group)
+ (with-current-buffer nntp-server-buffer
+ (goto-char (point-min))
+ (while (and (not (eobp))
+ (setq head (nnheader-parse-head)))
+ (push head headers))))
+ (sort (nreverse headers)
+ (lambda (h1 h2)
+ (> (gnus-cloud-chunk-sequence (mail-header-subject h1))
+ (gnus-cloud-chunk-sequence (mail-header-subject h2)))))))
+
+(defun gnus-cloud-chunk-sequence (string)
+ (if (string-match "sequence: \\([0-9]+\\)" string)
+ (string-to-number (match-string 1 string))
+ 0))
+
+(defun gnus-cloud-prune-old-chunks (headers)
+ (let ((headers (reverse headers))
+ (found nil))
+ (while (and headers
+ (not found))
+ (when (string-match "type: :full" (mail-header-subject (car headers)))
+ (setq found t))
+ (pop headers))
+ ;; All the chunks that are older than the newest :full chunk can be
+ ;; deleted.
+ (when headers
+ (gnus-request-expire-articles
+ (mapcar (lambda (h)
+ (mail-header-number h))
+ (nreverse headers))
+ (gnus-group-full-name gnus-cloud-group-name gnus-cloud-method)))))
+
+(defun gnus-cloud-download-data ()
+ (let ((articles nil)
+ chunks)
+ (dolist (header (gnus-cloud-available-chunks))
+ (when (> (gnus-cloud-chunk-sequence (mail-header-subject header))
+ gnus-cloud-sequence)
+ (push (mail-header-number header) articles)))
+ (when articles
+ (nnimap-request-articles (nreverse articles) gnus-cloud-group-name)
+ (with-current-buffer nntp-server-buffer
+ (goto-char (point-min))
+ (while (re-search-forward "^Version " nil t)
+ (beginning-of-line)
+ (push (gnus-cloud-parse-chunk) chunks)
+ (forward-line 1))))))
+
+(defun gnus-cloud-server-p (server)
+ (member server gnus-cloud-covered-servers))
+