<commit_before>;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 <NAME> <<EMAIL>>
;; Copyright (c) 2015-2016 <NAME> <<EMAIL>>

(ns uxbox.data.pages
  (:require [cuerdas.core :as str]
            [promesa.core :as p]
            [beicon.core :as rx]
            [lentes.core :as l]
            [uxbox.rstore :as rs]
            [uxbox.router :as r]
            [uxbox.repo :as rp]
            [uxbox.locales :refer (tr)]
            [uxbox.schema :as sc]
            [uxbox.state :as st]
            [uxbox.state.project :as stpr]
            [uxbox.ui.messages :as uum]
            [uxbox.util.datetime :as dt]
            [uxbox.util.data :refer (without-keys)]))

(defprotocol IPageUpdate
  "A marker protocol for mark events that alters the
  page and is subject to perform a backend synchronization.")

;; --- Pages Fetched

(defrecord PagesFetched [pages]
  rs/UpdateEvent
  (-apply-update [_ state]
    (as-> state $
      (reduce stpr/unpack-page $ pages)
      (reduce stpr/assoc-page $ pages))))

(defn pages-fetched?
  [v]
  (instance? PagesFetched v))

;; --- Fetch Pages (by project id)

(defrecord FetchPages [projectid]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-loaded [{pages :payload}]
              (->PagesFetched pages))
            (on-error [err]
              (js/console.error err)
              (rx/empty))]
      (->> (rp/do :fetch/pages-by-project {:project projectid})
           (rx/map on-loaded)
           (rx/catch on-error)))))

(defn fetch-pages
  [projectid]
  (FetchPages. projectid))

;; --- Create Page

(defrecord CreatePage [name width height project layout]
  rs/WatchEvent
  (-apply-watch [this state s]
    (letfn [(on-created [{page :payload}]
              (rx/of
               #(stpr/unpack-page % page)
               #(stpr/assoc-page % page)))
            (on-failed [page]
              (uum/error (tr "errors.auth"))
              (rx/empty))]
      (let [params (-> (into {} this)
                       (assoc :data {}))]
        (->> (rp/do :create/page params)
             (rx/mapcat on-created)
             (rx/catch on-failed))))))

(def ^:static +create-page-schema+
  {:name [sc/required sc/string]
   :layout [sc/required sc/string]
   :width [sc/required sc/integer]
   :height [sc/required sc/integer]
   :project [sc/required sc/uuid]})

(defn create-page
  [data]
  (sc/validate! +create-page-schema+ data)
  (map->CreatePage data))

;; --- Sync Page

(defrecord SyncPage [id]
  rs/WatchEvent
  (-apply-watch [this state s]
    (println "SyncPage")
    (letfn [(on-success [{page :payload}]
              (rx/of
               #(assoc-in % [:pages-by-id id :version] (:version page))
               #(stpr/assoc-page % page)))
            (on-failure [e]
              (uum/error (tr "errors.page-update"))
              (rx/empty))]
      (let [page (stpr/pack-page state id)]
        (->> (rp/do :update/page page)
             (rx/mapcat on-success)
             (rx/catch on-failure))))))

(defn sync-page
  [id]
  (SyncPage. id))

;; --- Update Page

(declare fetch-page-history)
(declare fetch-pinned-page-history)

(defrecord UpdatePage [id]
  rs/WatchEvent
  (-apply-watch [this state s]
    (println "UpdatePage")
    (let [page (get-in state [:pages-by-id id])]
      (if (:history page)
        (rx/empty)
        (rx/of (sync-page id)
               (fetch-page-history id)
               (fetch-pinned-page-history id))))))

(defn update-page
  [id]
  (UpdatePage. id))

(defn watch-page-changes
  [id]
  (letfn [(on-value []
            (rs/emit! (update-page id)))]
    (as-> rs/stream $
      (rx/filter #(satisfies? IPageUpdate %) $)
      (rx/debounce 2000 $)
      (rx/on-next $ on-value))))

;; --- Update Page Metadata

;; This is a simplified version of `UpdatePage` event
;; that does not sends the heavyweiht `:data` attribute
;; and only serves for update other page data.

(defrecord UpdatePageMetadata [id name width height layout]
  rs/UpdateEvent
  (-apply-update [_ state]
    (letfn [(updater [page]
              (merge page
                     (when width {:width width})
                     (when height {:height height})
                     (when name {:name name})))]
      (update-in state [:pages-by-id id] updater)))

  rs/WatchEvent
  (-apply-watch [this state s]
    (letfn [(on-success [{page :payload}]
              #(assoc-in % [:pages-by-id id :version] (:version page)))
            (on-failure [e]
              (uum/error (tr "errors.page-update"))
              (rx/empty))]
      (->> (rp/do :update/page-metadata (into {} this))
           (rx/map on-success)
           (rx/catch on-failure)))))

(def ^:const +update-page-schema+
  {:name [sc/required sc/string]
   :width [sc/required sc/integer]
   :height [sc/required sc/integer]
   :layout [sc/required sc/string]})

(defn update-page-metadata
  [data]
  (sc/validate! +update-page-schema+ data)
  (map->UpdatePageMetadata (dissoc data :data)))

;; --- Delete Page (by id)

(defrecord DeletePage [id callback]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [_]
              (rs/swap #(stpr/purge-page % id)))
            (on-failure [e]
              (uum/error (tr "errors.delete-page"))
              (rx/empty))]
      (->> (rp/do :delete/page id)
           (rx/map on-success)
           (rx/tap callback)
           (rx/filter identity)
           (rx/catch on-failure)))))

(defn delete-page
  ([id] (DeletePage. id (constantly nil)))
  ([id callback] (DeletePage. id callback)))

;; --- Pinned Page History Fetched

(defrecord PinnedPageHistoryFetched [history]
  rs/UpdateEvent
  (-apply-update [_ state]
    (assoc-in state [:workspace :history :pinned-items] history)))

;; --- Fetch Pinned Page History

(defrecord FetchPinnedPageHistory [id]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [{history :payload}]
              (->PinnedPageHistoryFetched history))
            (on-failure [e]
              (uum/error (tr "errors.fetch-page-history"))
              (rx/empty))]
      (let [params {:page id :pinned true}]
        (->> (rp/do :fetch/page-history params)
             (rx/map on-success)
             (rx/catch on-failure))))))

(defn fetch-pinned-page-history
  [id]
  (->FetchPinnedPageHistory id))

;; --- Page History Fetched

(defrecord PageHistoryFetched [history append?]
  rs/UpdateEvent
  (-apply-update [_ state]
    (let [items (into [] history)
          minv (apply min (map :version history))
          state (assoc-in state [:workspace :history :min-version] minv)]
      (if-not append?
        (assoc-in state [:workspace :history :items] items)
        (update-in state [:workspace :history :items] #(reduce conj % items))))))

;; --- Fetch Page History

(defrecord FetchPageHistory [id since max]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [{history :payload}]
              (->PageHistoryFetched history (not (nil? since))))
            (on-failure [e]
              (uum/error (tr "errors.fetch-page-history"))
              (rx/empty))]
      (let [params {:page id  :max (or max 15)}]
        (->> (rp/do :fetch/page-history params)
             (rx/map on-success)
             (rx/catch on-failure))))))

(defn fetch-page-history
  ([id]
   (fetch-page-history id nil))
  ([id params]
   (map->FetchPageHistory (assoc params :id id))))

;; --- Clean Page History

(defrecord CleanPageHistory []
  rs/UpdateEvent
  (-apply-update [_ state]
    (-> state
        (assoc-in [:workspace :history :items] nil)
        (assoc-in [:workspace :history :selected] nil))))

(defn clean-page-history
  []
  (CleanPageHistory.))

;; --- Select Page History

(defrecord SelectPageHistory [id history]
  rs/UpdateEvent
  (-apply-update [_ state]
    (if (nil? history)
      (let [packed (get-in state [:pagedata-by-id id])]
        (-> state
            (stpr/unpack-page packed)
            (assoc-in [:workspace :history :selected] nil)))
      (let [page (get-in state [:pages-by-id id])
            page' (assoc page
                         :history true
                         :data (:data history))]
        (-> state
            (stpr/unpack-page page')
            (assoc-in [:workspace :history :selected] (:id history)))))))

(defn select-page-history
  [id history]
  (SelectPageHistory. id history))

;; --- Apply selected history

(defrecord ApplySelectedHistory [id]
  rs/UpdateEvent
  (-apply-update [_ state]
    (println "ApplySelectedHistory" id)
    (-> state
        (update-in [:pages-by-id id] dissoc :history)
        (assoc-in [:workspace :history :selected] nil)))

  rs/WatchEvent
  (-apply-watch [_ state s]
    (rx/of (update-page id))))

(defn apply-selected-history
  [id]
  (ApplySelectedHistory. id))

;; --- Discard Selected History

(defrecord DiscardSelectedHistory [id]
  rs/UpdateEvent
  (-apply-update [_ state]
    (let [packed (get-in state [:pagedata-by-id id])]
      (-> state
          (stpr/unpack-page packed)
          (assoc-in [:workspace :history :selected] nil)))))

(defn discard-selected-history
  [id]
  (DiscardSelectedHistory. id))
<commit_msg>Remove posible race condition on page sync and history load.<commit_after>;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 <NAME> <<EMAIL>>
;; Copyright (c) 2015-2016 <NAME> <<EMAIL>>

(ns uxbox.data.pages
  (:require [cuerdas.core :as str]
            [promesa.core :as p]
            [beicon.core :as rx]
            [lentes.core :as l]
            [uxbox.rstore :as rs]
            [uxbox.router :as r]
            [uxbox.repo :as rp]
            [uxbox.locales :refer (tr)]
            [uxbox.schema :as sc]
            [uxbox.state :as st]
            [uxbox.state.project :as stpr]
            [uxbox.ui.messages :as uum]
            [uxbox.util.datetime :as dt]
            [uxbox.util.data :refer (without-keys)]))

(defprotocol IPageUpdate
  "A marker protocol for mark events that alters the
  page and is subject to perform a backend synchronization.")

;; --- Pages Fetched

(defrecord PagesFetched [pages]
  rs/UpdateEvent
  (-apply-update [_ state]
    (as-> state $
      (reduce stpr/unpack-page $ pages)
      (reduce stpr/assoc-page $ pages))))

(defn pages-fetched?
  [v]
  (instance? PagesFetched v))

;; --- Fetch Pages (by project id)

(defrecord FetchPages [projectid]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-loaded [{pages :payload}]
              (->PagesFetched pages))
            (on-error [err]
              (js/console.error err)
              (rx/empty))]
      (->> (rp/do :fetch/pages-by-project {:project projectid})
           (rx/map on-loaded)
           (rx/catch on-error)))))

(defn fetch-pages
  [projectid]
  (FetchPages. projectid))

;; --- Create Page

(defrecord CreatePage [name width height project layout]
  rs/WatchEvent
  (-apply-watch [this state s]
    (letfn [(on-created [{page :payload}]
              (rx/of
               #(stpr/unpack-page % page)
               #(stpr/assoc-page % page)))
            (on-failed [page]
              (uum/error (tr "errors.auth"))
              (rx/empty))]
      (let [params (-> (into {} this)
                       (assoc :data {}))]
        (->> (rp/do :create/page params)
             (rx/mapcat on-created)
             (rx/catch on-failed))))))

(def ^:static +create-page-schema+
  {:name [sc/required sc/string]
   :layout [sc/required sc/string]
   :width [sc/required sc/integer]
   :height [sc/required sc/integer]
   :project [sc/required sc/uuid]})

(defn create-page
  [data]
  (sc/validate! +create-page-schema+ data)
  (map->CreatePage data))

;; --- Sync Page

(defrecord PageSynced [page]
  rs/UpdateEvent
  (-apply-update [this state]
    (-> state
        (assoc-in [:pages-by-id (:id page) :version] (:version page))
        (stpr/assoc-page page))))

(defn- page-synced?
  [event]
  (instance? PageSynced event))

(defrecord SyncPage [id]
  rs/WatchEvent
  (-apply-watch [this state s]
    (println "SyncPage")
    (letfn [(on-success [{page :payload}]
              (->PageSynced page))
            (on-failure [e]
              (uum/error (tr "errors.page-update"))
              (rx/empty))]
      (let [page (stpr/pack-page state id)]
        (->> (rp/do :update/page page)
             (rx/map on-success)
             (rx/catch on-failure))))))

(defn sync-page
  [id]
  (SyncPage. id))

;; --- Update Page

(declare fetch-page-history)
(declare fetch-pinned-page-history)

(defrecord UpdatePage [id]
  rs/WatchEvent
  (-apply-watch [this state s]
    (println "UpdatePage")
    (let [page (get-in state [:pages-by-id id])]
      (if (:history page)
        (rx/empty)

        (rx/merge
         (rx/of (sync-page id))
         (->> (rx/filter page-synced? s)
              (rx/take 1)
              (rx/mapcat #(rx/of (fetch-page-history id)
                                 (fetch-pinned-page-history id)))))))))

(defn update-page
  [id]
  (UpdatePage. id))

(defn watch-page-changes
  [id]
  (letfn [(on-value []
            (rs/emit! (update-page id)))]
    (as-> rs/stream $
      (rx/filter #(satisfies? IPageUpdate %) $)
      (rx/debounce 2000 $)
      (rx/on-next $ on-value))))

;; --- Update Page Metadata

;; This is a simplified version of `UpdatePage` event
;; that does not sends the heavyweiht `:data` attribute
;; and only serves for update other page data.

(defrecord UpdatePageMetadata [id name width height layout]
  rs/UpdateEvent
  (-apply-update [_ state]
    (letfn [(updater [page]
              (merge page
                     (when width {:width width})
                     (when height {:height height})
                     (when name {:name name})))]
      (update-in state [:pages-by-id id] updater)))

  rs/WatchEvent
  (-apply-watch [this state s]
    (letfn [(on-success [{page :payload}]
              #(assoc-in % [:pages-by-id id :version] (:version page)))
            (on-failure [e]
              (uum/error (tr "errors.page-update"))
              (rx/empty))]
      (->> (rp/do :update/page-metadata (into {} this))
           (rx/map on-success)
           (rx/catch on-failure)))))

(def ^:const +update-page-schema+
  {:name [sc/required sc/string]
   :width [sc/required sc/integer]
   :height [sc/required sc/integer]
   :layout [sc/required sc/string]})

(defn update-page-metadata
  [data]
  (sc/validate! +update-page-schema+ data)
  (map->UpdatePageMetadata (dissoc data :data)))

;; --- Delete Page (by id)

(defrecord DeletePage [id callback]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [_]
              (rs/swap #(stpr/purge-page % id)))
            (on-failure [e]
              (uum/error (tr "errors.delete-page"))
              (rx/empty))]
      (->> (rp/do :delete/page id)
           (rx/map on-success)
           (rx/tap callback)
           (rx/filter identity)
           (rx/catch on-failure)))))

(defn delete-page
  ([id] (DeletePage. id (constantly nil)))
  ([id callback] (DeletePage. id callback)))

;; --- Pinned Page History Fetched

(defrecord PinnedPageHistoryFetched [history]
  rs/UpdateEvent
  (-apply-update [_ state]
    (assoc-in state [:workspace :history :pinned-items] history)))

;; --- Fetch Pinned Page History

(defrecord FetchPinnedPageHistory [id]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [{history :payload}]
              (->PinnedPageHistoryFetched history))
            (on-failure [e]
              (uum/error (tr "errors.fetch-page-history"))
              (rx/empty))]
      (let [params {:page id :pinned true}]
        (->> (rp/do :fetch/page-history params)
             (rx/map on-success)
             (rx/catch on-failure))))))

(defn fetch-pinned-page-history
  [id]
  (->FetchPinnedPageHistory id))

;; --- Page History Fetched

(defrecord PageHistoryFetched [history append?]
  rs/UpdateEvent
  (-apply-update [_ state]
    (let [items (into [] history)
          minv (apply min (map :version history))
          state (assoc-in state [:workspace :history :min-version] minv)]
      (if-not append?
        (assoc-in state [:workspace :history :items] items)
        (update-in state [:workspace :history :items] #(reduce conj % items))))))

;; --- Fetch Page History

(defrecord FetchPageHistory [id since max]
  rs/WatchEvent
  (-apply-watch [_ state s]
    (letfn [(on-success [{history :payload}]
              (->PageHistoryFetched history (not (nil? since))))
            (on-failure [e]
              (uum/error (tr "errors.fetch-page-history"))
              (rx/empty))]
      (let [params {:page id  :max (or max 15)}]
        (->> (rp/do :fetch/page-history params)
             (rx/map on-success)
             (rx/catch on-failure))))))

(defn fetch-page-history
  ([id]
   (fetch-page-history id nil))
  ([id params]
   (map->FetchPageHistory (assoc params :id id))))

;; --- Clean Page History

(defrecord CleanPageHistory []
  rs/UpdateEvent
  (-apply-update [_ state]
    (-> state
        (assoc-in [:workspace :history :items] nil)
        (assoc-in [:workspace :history :selected] nil))))

(defn clean-page-history
  []
  (CleanPageHistory.))

;; --- Select Page History

(defrecord SelectPageHistory [id history]
  rs/UpdateEvent
  (-apply-update [_ state]
    (if (nil? history)
      (let [packed (get-in state [:pagedata-by-id id])]
        (-> state
            (stpr/unpack-page packed)
            (assoc-in [:workspace :history :selected] nil)))
      (let [page (get-in state [:pages-by-id id])
            page' (assoc page
                         :history true
                         :data (:data history))]
        (-> state
            (stpr/unpack-page page')
            (assoc-in [:workspace :history :selected] (:id history)))))))

(defn select-page-history
  [id history]
  (SelectPageHistory. id history))

;; --- Apply selected history

(defrecord ApplySelectedHistory [id]
  rs/UpdateEvent
  (-apply-update [_ state]
    (println "ApplySelectedHistory" id)
    (-> state
        (update-in [:pages-by-id id] dissoc :history)
        (assoc-in [:workspace :history :selected] nil)))

  rs/WatchEvent
  (-apply-watch [_ state s]
    (rx/of (update-page id))))

(defn apply-selected-history
  [id]
  (ApplySelectedHistory. id))

;; --- Discard Selected History

(defrecord DiscardSelectedHistory [id]
  rs/UpdateEvent
  (-apply-update [_ state]
    (let [packed (get-in state [:pagedata-by-id id])]
      (-> state
          (stpr/unpack-page packed)
          (assoc-in [:workspace :history :selected] nil)))))

(defn discard-selected-history
  [id]
  (DiscardSelectedHistory. id))

{'fraction_non_alphanumeric': 0.15441176470588236, 'fraction_numerical': 0.002803639441388066, 'mean_word_length': 2.2349418206707736, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6479994', 'n_tokens_mistral': 6340, 'n_tokens_neox': 6165, 'n_words': 1623}