Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Full db and scanning support, wohoo

  • Loading branch information...
commit 303dbb50dd78d8f53df2dc6cdc3dc1a6dbf12b0b 1 parent 128ed28
@lfranchi authored
Showing with 168 additions and 23 deletions.
  1. +41 −2 resources/schema.sql
  2. +45 −17 src/ono/core.clj
  3. +82 −4 src/ono/db.clj
View
43 resources/schema.sql
@@ -1,10 +1,49 @@
+CREATE TABLE IF NOT EXISTS source (id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ friendlyname TEXT,
+ lastop TEXT NOT NULL DEFAULT "", -- guid of last op we've successfully applied
+ isonline BOOLEAN NOT NULL DEFAULT false);
+CREATE UNIQUE INDEX source_name ON source(name);
+
CREATE TABLE IF NOT EXISTS artist (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
sortname TEXT NOT NULL);
+CREATE TABLE IF NOT EXISTS album (id INTEGER PRIMARY KEY AUTOINCREMENT,
+ artist_id INTEGER NOT NULL REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ name TEXT NOT NULL,
+ sortname TEXT NOT NULL);
+CREATE UNIQUE INDEX album_artist_sortname ON album(artist_id,sortname);
+
CREATE TABLE IF NOT EXISTS track (id INTEGER PRIMARY KEY AUTOINCREMENT,
- artist INTEGER NOT NULL REFERENCES artist(id)
+ artist_id INTEGER NOT NULL REFERENCES artist(id)
ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE
INITIALLY DEFERRED,
name TEXT NOT NULL,
- sortname TEXT NOT NULL);
+ sortname TEXT NOT NULL);
+
+CREATE TABLE IF NOT EXISTS file ( id INTEGER PRIMARY KEY AUTOINCREMENT,
+ source_id INTEGER REFERENCES source(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ url TEXT NOT NULL,
+ size INTEGER NOT NULL,
+ mtime INTEGER NOT NULL, -- file mtime, so we know to rescan
+ md5 TEXT, -- useful when comparing stuff p2p
+ mimetype TEXT, -- "audio/mpeg"
+ duration INTEGER NOT NULL DEFAULT 0, -- seconds
+ bitrate INTEGER NOT NULL DEFAULT 0); -- kbps (or equiv)Ï
+
+CREATE UNIQUE INDEX file_url_src_uniq ON file(source_id, url);
+CREATE INDEX file_source ON file(source_id);
+CREATE INDEX file_mtime ON file(mtime);
+
+CREATE TABLE IF NOT EXISTS file_join (
+ file_id INTEGER PRIMARY KEY REFERENCES file(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ artist_id INTEGER NOT NULL REFERENCES artist(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ track_id INTEGER NOT NULL REFERENCES track(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ album_id INTEGER REFERENCES album(id) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ albumpos INTEGER,
+ discnumber INTEGER
+);
+CREATE INDEX file_join_track ON file_join(track_id);
+CREATE INDEX file_join_artist ON file_join(artist_id);
+CREATE INDEX file_join_album ON file_join(album_id);
View
62 src/ono/core.clj
@@ -1,41 +1,69 @@
(ns ono.core
- (:use ono.db)
- (:use [cheshire.core :only (parse-string)])
- (:use [fs.core :only (exists?, mkdir, create, walk, file)])
- (:use [korma.db])
+ (:require [ono.db :as db])
+ (:require [cheshire.core :as json])
+ (:require [fs.core :as fs])
+ (:import [org.jaudiotagger.audio])
+ (:import [org.jaudiotagger.tag])
(:gen-class))
-; Constants
+;; Constants
(def configDir (str (System/getProperty "user.home") "/.ono"))
(def confFile (str configDir "/config"))
(def dbFile (str configDir "/db.sqlite3"))
+(def supportedSuffixes #{".mp3" ".flac" ".ogg" ".mp4"})
-; Globals
+;; Globals
(def config (ref {}))
(defn- setup
"Loads configuration and database"
[]
- ; Create files if they don't exist yet
- (if (not (exists? configDir))
- (mkdir configDir))
- (if (not (exists? confFile))
- (create (file confFile)))
+ ;; Create files if they don't exist yet
+ (if (not (fs/exists? configDir))
+ (fs/mkdir configDir))
+ (if (not (fs/exists? confFile))
+ (fs/create (fs/file confFile)))
(dosync
- (ref-set config (parse-string (slurp confFile)))))
+ (ref-set config (json/parse-string (slurp confFile)))))
- (setupdb dbFile)
+ (db/setupdb dbFile)
+
+(defn- extractID3
+ "Extracts basic ID3 info from a file"
+ [f]
+ (if (contains? supportedSuffixes (last (fs/split-ext f)))
+ (let [fd (fs/file f)
+ audio (org.jaudiotagger.audio.AudioFileIO/read fd)
+ tag (.getTag audio)
+ header (.getAudioHeader audio)]
+ {:title (.getFirst tag org.jaudiotagger.tag.FieldKey/TITLE)
+ :artist (.getFirst tag org.jaudiotagger.tag.FieldKey/ARTIST)
+ :album (.getFirst tag org.jaudiotagger.tag.FieldKey/ALBUM)
+ :year (.getFirst tag org.jaudiotagger.tag.FieldKey/YEAR)
+ :track (.getFirst tag org.jaudiotagger.tag.FieldKey/TRACK)
+ :duration (.getTrackLength header)
+ :bitrate (.getSampleRateAsNumber header)
+ :mtime (fs/mod-time fd)
+ :size (fs/size fd)
+ :file f
+ :source nil})))
;; Scanner
-(defn scan
+(defn- scan
"Scans a desired folder recursively for any audio files we can recognize.
Parses the ID3 tags and inserts into the database."
[folder]
(println (str "Scanning folder " folder))
- (walk (fn [r dirs files]
- ; (println (str "Walking " r dirs files)))
- (println "OHAI"))
+ ;; jaudiotagger is super verbose on stderr
+ (let [os System/err]
+ (System/setErr (new java.io.PrintStream (new java.io.FileOutputStream "/dev/null")))
+ (dorun (fs/walk (fn [r dirs files]
+ (db/addFiles (filter #(not (empty? %)) ;; Remove files with no tags
+ (map (fn [f]
+ (extractID3 (fs/file r f)))
+ files))))
folder))
+ (System/setErr os)))
(defn handle [input]
(cond
View
86 src/ono/db.clj
@@ -1,30 +1,108 @@
(ns ono.db
(:use [korma.db])
(:use [korma.core])
- (:use [fs.core :only (exists?, create, file)]))
+ (:use [fs.core :only (exists?)]))
+
+(def testtrack { :title "One",:artist "U2", :album "Joshua Tree" , :year 1992 , :track 3 , :duration 240, :bitrate 256, :mtime 123123123 , :size 0, :file "/test/mp3", :source 0 })
(defn addSortName
"Adds the sort name field to an insert"
[fields]
(assoc fields :sortname (:name fields))) ;; For now don't actually calculate the sortname
+(defn prepareFile
+ ""
+ [fields]
+ fields)
+
(defn setupdb
"Sets up the db connection and relations"
[dbFile]
+ ;; I was unable to get korma or java-jdbc to create the initial db/tables
+ ;; from scratch, so we use a simple shell script to do it for us
(if-not (exists? dbFile)
(do (println "No DB found. Please create it with ./setupdb.sh")
(System/exit 0)))
(defdb sqlite (sqlite3 {:db dbFile}))
+ (defentity source
+ (entity-fields :name :friendlyname :lastop :isonline))
+
(defentity artist
(entity-fields :name :sortname)
(prepare addSortName))
+ (defentity album
+ (entity-fields :artist_id :name :sortname)
+ (prepare addSortName))
+
(defentity track
- (entity-fields :name :artist :sortname)
- (has-one artist)
+ (entity-fields :name :artist_id :sortname)
+ (belongs-to artist)
(prepare addSortName))
- (insert track (values {:name "Some Track" :artist "Artist Name"})))
+ (defentity fileT
+ (table :file)
+ (entity-fields :source_id :url :size :mtime :md5 :mimetype :duration :bitrate)
+ (belongs-to source))
+
+ (defentity file_join
+ (pk :file_id)
+ (entity-fields :file_id :artist_id :track_id :album_id :albumpos :discnumber)
+ (belongs-to fileT artist track album)))
+
+;; (insert track (values {:name "Some Track" :artist "Artist Name"})))
+
+(defn getArtist
+ "Returns the artist id for a given name, or creates one if it doesn't exist yet"
+ [artistName]
+ (if-let [id (first (select artist
+ (fields [:id])
+ (where {:name [like artistName]})))]
+ (id :id) ;; Have an id, return it directly
+ (first (vals (insert artist (values {:name artistName})))))) ;; Not here yet, insert it first and return the id
+
+(defn getAlbum
+ "Returns the album id for a given album name and artist id, or creates one if it doesn't exist yet"
+ [albumName, artistId]
+ (if-let [id (first (select album
+ (fields [:id])
+ (where {:name [like albumName]
+ :artist_id [= artistId]})))]
+ (id :id)
+ (first (vals (insert album (values {:name albumName :artist_id artistId}))))))
+
+(defn getTrack
+ "Returns the track id for the trackname and artist id, or creates one if it doesn't exist yet"
+ [trackName, artistId]
+ (if-let [id (first (select track
+ (fields [:id])
+ (where {:name [like trackName]
+ :artist_id [= artistId]})))]
+ (id :id)
+ (first (vals (insert track (values {:name trackName :artist_id artistId}))))))
+
+(defn addFiles
+ "Adds a list of file maps to the database"
+ [files]
+ (println (str "Adding number of files: " (count files)))
+ (dorun (map
+ (fn [{:keys [title artist album year track duration bitrate mtime size file source]}]
+ (let [fileId ((insert fileT (values {:source_id source,
+ :url file
+ :size size
+ :mtime mtime
+ :duration duration
+ :bitrate bitrate})) :last_insert_rowid())
+ artistId (getArtist artist)
+ albumId (getAlbum album, artistId)
+ trackId (getTrack title, artistId)]
+ (insert file_join (values {:file_id fileId
+ :artist_id artistId
+ :track_id trackId
+ :album_id albumId
+ :albumpos track}))
+ ))
+ files)))
Please sign in to comment.
Something went wrong with that request. Please try again.