<commit_before>;;; danny.el --- Danny (download nanny) package. -*- lexical-binding: t; -*-

;;; Commentary:
;; Danny monitors downloads and offers quick actions.


;;; Code:

(require 'cl)
(require 'dash)
(require 'f)
(require 'filenotify)
(require 'ht)
(require 'ivy)

(defvar danny--notify-descriptor nil "File notify descriptor.")

(defvar danny-monitor-path nil "Path to monitor.")

(defvar danny--history-hash (ht) "History.")

(defvar danny--destination-root "~/Downloads/Targets")

(defvar danny--history-path "~/.danny")

(cl-defstruct
    danny-destination-root
  name
  path)

(defun danny-stop-monitoring ()
  (interactive)
  "Stop monitoring for new files."
  (when (file-notify-valid-p danny--notify-descriptor)
    (file-notify-rm-watch danny--notify-descriptor)
    (setq danny--notify-descriptor nil)))

(defun danny-start-listening ()
  "Start monitoring for new files."
  (interactive)
  (danny-stop-monitoring)
  (setq danny--history-hash (danny--read-history-hashtable danny--history-path))
  (cl-assert danny-monitor-path nil "`danny-monitor-path' must be set")
  (setq danny--notify-descriptor (file-notify-add-watch danny-monitor-path
                                                        '(change attribute-change)
                                                        'danny--notify-callback)))

(defun danny--notify-callback (event)
  "Handle EVENT for file-notify events."
  (let ((event-type (nth 1 event))
        (file-path (nth 2 event)))
    (when (and (eq event-type 'created)
               (f-file-p file-path))
      (danny--handle-file-created file-path))))

(defun danny--handle-file-created (file-path)
  "Handle new file created at FILE-PATH."
  (with-current-buffer (get-buffer-create "*danny*")
    (let* ((unwind (lambda ()
                     (delete-frame)
                     (other-window 1)))
           (action (lambda (destination-dir)
                     (let ((destination-file (f-join destination-dir
                                                     (danny--read-string (format "Save as (%s): " (f-filename file-path))
                                                                         (f-filename file-path)
                                                                         20))))
                       (if (f-exists-p destination-file)
                           (progn
                             (unless (danny--y-or-n (format "Override? %s\n" destination-file))
                               (error "Aborted"))
                             (rename-file file-path destination-file t)
                             (danny--add-destination-dir destination-dir))
                         (rename-file file-path destination-file t)
                         (danny--add-destination-dir destination-dir))))))
      (danny--framed-ivy-read (list
                               (make-danny--framed-ivy-source :prompt (format "Recent (%s): " (f-filename file-path))
                                                              :action action
                                                              :collection (lambda (str pred v)
                                                                            (danny--last-destinations))
                                                              :unwind unwind)
                               (make-danny--framed-ivy-source :prompt (format "Downloads (%s): " (f-filename file-path))
                                                              :action action
                                                              :collection (lambda (str pred v)
                                                                            (f-directories danny--destination-root))
                                                              :unwind unwind))))))

(cl-defstruct
    danny--framed-ivy-source
  prompt
  collection
  action
  unwind)

(cl-defun danny--framed-ivy-read (sources &key index initial-input)
  (let ((kmap (make-sparse-keymap))
        (source))
    (cl-assert (> (length sources) 0))
    (when (null index)
      (setq index 0))
    (setq source (nth index sources))
    (define-key kmap (kbd "<right>") (lambda ()
                                       (interactive)
                                       (ivy-quit-and-run (danny--framed-ivy-read sources
                                                                                 :index (if (>= (1+ index)
                                                                                                (length sources))
                                                                                            0
                                                                                          (1+ index))
                                                                                 :initial-input ivy-text))))
    (define-key kmap (kbd "<left>") (lambda ()
                                      (interactive)
                                      (ivy-quit-and-run (danny--framed-ivy-read sources
                                                                                :index (if (< (1- index)
                                                                                              0)
                                                                                           (1- (length sources))
                                                                                         (1- index))
                                                                                :initial-input ivy-text))))
    (with-current-buffer (get-buffer-create "*danny*")
      (let* ((frame (make-frame `((auto-raise . t)
                                  (font . "Menlo 15")
                                  (height . 20)
                                  (internal-border-width . 20)
                                  (left . 0.33)
                                  (left-fringe . 0)
                                  (line-spacing . 3)
                                  (menu-bar-lines . 0)
                                  (minibuffer . only)
                                  (right-fringe . 0)
                                  (tool-bar-lines . 0)
                                  (top . 200)
                                  (undecorated . t)
                                  (unsplittable . t)
                                  (vertical-scroll-bars . nil)
                                  ;; Calculate a sensible width, based on longest path (in source).
                                  (width . ,(+ 1
                                               (length (danny--framed-ivy-source-prompt source))
                                               (danny--longest-line-length (funcall (danny--framed-ivy-source-collection source)
                                                                                    nil nil nil))))))))
        (x-focus-frame frame)
        (ivy-read (danny--framed-ivy-source-prompt source)
                  (danny--framed-ivy-source-collection source)
                  :action (danny--framed-ivy-source-action source)
                  :initial-input initial-input<commit_msg>Cleaning up danny package<commit_after>;;; danny.el --- Danny (download nanny) package. -*- lexical-binding: t; -*-

;;; Commentary:
;; Danny monitors downloads and offers quick actions.


;;; Code:

(require 'cl)
(require 'dash)
(require 'f)
(require 'filenotify)
(require 'ht)
(require 'ivy)

(defvar danny--notify-descriptor nil "File notify descriptor.")

(defvar danny-monitor-path nil "Path to monitor.")

(defvar danny--history-hash (ht) "History.")

(defvar danny--destination-root "~/Downloads/Targets")

(defvar danny--history-path "~/.danny")

(cl-defstruct
    danny-destination-root
  name
  path)

(defun danny-stop-monitoring ()
  (interactive)
  "Stop monitoring for new files."
  (when (file-notify-valid-p danny--notify-descriptor)
    (file-notify-rm-watch danny--notify-descriptor)
    (setq danny--notify-descriptor nil)))

(defun danny-start-listening ()
  "Start monitoring for new files."
  (interactive)
  (danny-stop-monitoring)
  (setq danny--history-hash (danny--read-history-hashtable danny--history-path))
  (cl-assert danny-monitor-path nil "`danny-monitor-path' must be set")
  (setq danny--notify-descriptor (file-notify-add-watch danny-monitor-path
                                                        '(change attribute-change)
                                                        'danny--notify-callback)))

(defun danny--notify-callback (event)
  "Handle EVENT for file-notify events."
  (let ((event-type (nth 1 event))
        (file-path (nth 2 event)))
    (when (and (eq event-type 'created)
               (f-file-p file-path))
      (danny--handle-file-created file-path))))

(defun danny--create-move-action-fun (file-path)
  (lambda (destination-dir)
    (let ((destination-file (f-join destination-dir
                                    (danny--read-string (format "Save as (%s): " (f-filename file-path))
                                                        (f-filename file-path)
                                                        20))))
      (if (f-exists-p destination-file)
          (progn
            (unless (danny--y-or-n (format "Override? %s\n" destination-file))
              (error "Aborted"))
            (rename-file file-path destination-file t)
            (danny--add-destination-dir destination-dir))
        (rename-file file-path destination-file t)
        (danny--add-destination-dir destination-dir)))))

(defun danny--create-open-action-fun (file-path)
  (lambda (ignored)
    (find-file file-path)))

(defun danny--handle-file-created (file-path)
  "Handle new file created at FILE-PATH."
  (with-current-buffer (get-buffer-create "*danny*")
    (let* ((unwind (lambda ()
                     (delete-frame)
                     (other-window 1))))
      (danny--framed-ivy-read (list
                               (make-danny--framed-ivy-source :prompt (format "Open? (%s) " (f-filename file-path))
                                                              :action (danny--create-open-action-fun file-path)
                                                              :collection (lambda (str pred v)
                                                                            '())
                                                              :unwind unwind)
                               (make-danny--framed-ivy-source :prompt (format "Save in Recent (%s): " (f-filename file-path))
                                                              :action (danny--create-move-action-fun file-path)
                                                              :collection (lambda (str pred v)
                                                                            (danny--last-destinations))
                                                              :unwind unwind)
                               (make-danny--framed-ivy-source :prompt (format "Save in Downloads (%s): " (f-filename file-path))
                                                              :action (danny--create-move-action-fun file-path)
                                                              :collection (lambda (str pred v)
                                                                            (f-directories danny--destination-root))
                                                              :unwind unwind))))))

(cl-defstruct
    danny--framed-ivy-source
  prompt
  collection
  action
  unwind)

(cl-defun danny--framed-ivy-read (sources &key index initial-input)
  (let ((kmap (make-sparse-keymap))
        (source))
    (cl-assert (> (length sources) 0))
    (when (null index)
      (setq index 0))
    (setq source (nth index sources))
    (define-key kmap (kbd "<right>") (lambda ()
                                       (interactive)
                                       (ivy-quit-and-run (danny--framed-ivy-read sources
                                                                                 :index (if (>= (1+ index)
                                                                                                (length sources))
                                                                                            0
                                                                                          (1+ index))
                                                                                 :initial-input ivy-text))))
    (define-key kmap (kbd "<left>") (lambda ()
                                      (interactive)
                                      (ivy-quit-and-run (danny--framed-ivy-read sources
                                                                                :index (if (< (1- index)
                                                                                              0)
                                                                                           (1- (length sources))
                                                                                         (1- index))
                                                                                :initial-input ivy-text))))
    (with-current-buffer (get-buffer-create "*danny*")
      (let* ((lines-count (length (funcall (danny--framed-ivy-source-collection source)
                                           nil nil nil)))
             (frame (make-frame `((auto-raise . t)
                                  (font . "Menlo 15")
                                  (height . ,(if (> lines-count 0) 10
                                               1))
                                  (internal-border-width . 20)
                                  (left . 0.33)
                                  (left-fringe . 0)
                                  (line-spacing . 3)
                                  (menu-bar-lines . 0)
                                  (minibuffer . only)
                                  (right-fringe . 0)
                                  (tool-bar-lines . 0)
                                  (top . 200)
                                  (undecorated . t)
                                  (unsplittable . t)
                                  (vertical-scroll-bars . nil)
                                  ;; Calculate a sensible width, based on longest path (in source).
                                  (width . ,(if (> lines-count 0)
                                                (+ 1
                                                   lines-count
                                                   (danny--longest-line-length (funcall (danny--framed-ivy-source-collection source)
                                                                                        nil nil nil)))
                                              (1+ (length (danny--framed-ivy-source-prompt source)))))))))
        (ivy-read (danny--framed-ivy-source-prompt source)
                  (danny--framed-ivy-source-collection source)
                  :update-fn (lambda ()
                               ;; Forcing redisplay works around "Open" source not shown
                               ;; after having visited other sources (left/right keys).
                               (redisplay))
                  :action (danny--framed-ivy-source-action source)
                  :initial-input initial-input
{'fraction_non_alphanumeric': 0.10334090171910477, 'fraction_numerical': 0.004411287706779112, 'mean_word_length': 0.8761105026165267, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 212, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1255990', 'n_tokens_mistral': 3511, 'n_tokens_neox': 3363, 'n_words': 793}