├── .gitignore ├── LICENSE ├── README.md ├── package ├── DEVONthinkSearch.alfredworkflow └── DEVONthinkSearch.json └── src ├── icon.png ├── info.plist ├── search ├── setup ├── update.json └── workflowHandler.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-NonCommercial 3.0 Unported License. 2 | To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/3.0/. 3 | 4 | If you want to use it in your own project, please link back to this GitHub repository. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEVONthink Search Alfred 2 Workflow 2 | ================================== 3 | 4 | Features 5 | -------- 6 | 7 | This workflow allows you to search your DEVONthink documents from within Alfred. It supports DEVONthink Personal as well as DEVONthink Pro (with multiple databases) 8 | 9 | Installation 10 | ------------ 11 | 12 | [Download](http://bit.ly/18s4R1b) and import into Alfred 2. 13 | 14 | Usage 15 | ----- 16 | 17 | ### Keywords 18 | 19 | * **`devon [your search string]`** - Search in all databases 20 | 21 | ![](https://dl.dropboxusercontent.com/u/5453663/devon.png) 22 | 23 | * **`devondb [your search string]`** - Search in specific database 24 | 25 | ![](https://dl.dropboxusercontent.com/u/5453663/devondb1.png) 26 | ![](https://dl.dropboxusercontent.com/u/5453663/devondb2.png) 27 | 28 | * **`devondb!`** - Refresh database list 29 | 30 | The association between database names and uuids can only be obtained via AppleScript. This is done automatically if you run the `devondb` command for the first time. The information is then cached for better performance. If you add a new DEVONthink database later on, you'll have to run the `devondb!` command to refresh the database list. 31 | 32 | ![](https://dl.dropboxusercontent.com/u/5453663/devondb!.png) -------------------------------------------------------------------------------- /package/DEVONthinkSearch.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokaestner/devonthink-document-search/3314f8cb96eaf17ef7961b6b9060c2cc97550fe3/package/DEVONthinkSearch.alfredworkflow -------------------------------------------------------------------------------- /package/DEVONthinkSearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.3, 3 | "download_url": "http://bit.ly/18s4R1b", 4 | "description": "bugfixes" 5 | } -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/markokaestner/devonthink-document-search/3314f8cb96eaf17ef7961b6b9060c2cc97550fe3/src/icon.png -------------------------------------------------------------------------------- /src/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.markokaestner.devonthinksearch 7 | connections 8 | 9 | 1220BFFD-3CB7-4BDF-BE34-127AEF70127C 10 | 11 | 1DDAB52E-A468-49A3-972A-0B249E1B9C0B 12 | 13 | 14 | destinationuid 15 | 1220BFFD-3CB7-4BDF-BE34-127AEF70127C 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | 21 | 22 | AEEE74EC-013E-4E75-9613-B680942F4B57 23 | 24 | 25 | destinationuid 26 | 30CB87BB-3A5B-4AFB-80FF-6049286B6513 27 | modifiers 28 | 0 29 | modifiersubtext 30 | 31 | 32 | 33 | 34 | createdby 35 | Marko Kaestner 36 | description 37 | Search DEVONthink Documents 38 | disabled 39 | 40 | name 41 | DEVONthink Search 42 | objects 43 | 44 | 45 | config 46 | 47 | type 48 | alfred.workflow.action.openfile 49 | uid 50 | 30CB87BB-3A5B-4AFB-80FF-6049286B6513 51 | 52 | 53 | config 54 | 55 | anchorfields 56 | 57 | daterange 58 | 0 59 | fields 60 | 61 | 62 | field 63 | kMDItemDisplayName 64 | not 65 | 66 | split 67 | 68 | value 69 | {query} 70 | words 71 | 72 | 73 | 74 | field 75 | kMDItemFinderComment 76 | not 77 | 78 | split 79 | 80 | value 81 | {query} 82 | words 83 | 84 | 85 | 86 | field 87 | kMDItemTextContent 88 | not 89 | 90 | split 91 | 92 | value 93 | {query} 94 | words 95 | 96 | 97 | 98 | field 99 | kMDItemTitle 100 | not 101 | 102 | split 103 | 104 | value 105 | {query} 106 | words 107 | 108 | 109 | 110 | includesystem 111 | 112 | keyword 113 | devon 114 | scopes 115 | 116 | title 117 | DEVONthink Search 118 | types 119 | 120 | com.devon-technologies.metadata.think 121 | com.devon-technologies.metadata.thinkpro 122 | 123 | withspace 124 | 125 | 126 | type 127 | alfred.workflow.input.filefilter 128 | uid 129 | AEEE74EC-013E-4E75-9613-B680942F4B57 130 | 131 | 132 | config 133 | 134 | argumenttype 135 | 0 136 | escaping 137 | 102 138 | keyword 139 | devondb 140 | script 141 | ./search "{query}" 2>&1 | tee devon.log 142 | title 143 | DEVONthink Search in Database 144 | type 145 | 0 146 | withspace 147 | 148 | 149 | type 150 | alfred.workflow.input.scriptfilter 151 | uid 152 | 1DDAB52E-A468-49A3-972A-0B249E1B9C0B 153 | 154 | 155 | config 156 | 157 | escaping 158 | 102 159 | script 160 | . workflowHandler.sh 161 | 162 | QUERY="{query}" 163 | 164 | if [ -f "$QUERY" ]; then 165 | open "$QUERY" 166 | else 167 | setPref "searchdb" "{query}" 0 168 | SEARCHTERM=$(getPref "searchterm" 0) 169 | 170 | /usr/bin/osascript -e "tell application \"Alfred 2\" to search \"devondb $SEARCHTERM\"" 171 | fi 172 | type 173 | 0 174 | 175 | type 176 | alfred.workflow.action.script 177 | uid 178 | 1220BFFD-3CB7-4BDF-BE34-127AEF70127C 179 | 180 | 181 | config 182 | 183 | argumenttype 184 | 2 185 | escaping 186 | 127 187 | keyword 188 | devondb! 189 | runningsubtext 190 | Searching available databases 191 | script 192 | . workflowHandler.sh 193 | DATADIR=$(getDataDir) 194 | if [ ! -d "$DATADIR" ]; then 195 | mkdir -p "$DATADIR" 196 | fi 197 | ./setup > "${DATADIR}/databases" 198 | DBCOUNT=$(cat "${DATADIR}/databases" | wc -l) 199 | addResult "1" "" "DEVONthink database list updated" "$DBCOUNT databases found" "icon.png" "no" 200 | getXMLResults 201 | title 202 | Refresh DEVONthink Database List 203 | type 204 | 0 205 | withspace 206 | 207 | 208 | type 209 | alfred.workflow.input.scriptfilter 210 | uid 211 | 8F78B728-AEE3-4FDB-AC4D-57D4AEE5485D 212 | 213 | 214 | readme 215 | 216 | uidata 217 | 218 | 1220BFFD-3CB7-4BDF-BE34-127AEF70127C 219 | 220 | ypos 221 | 120 222 | 223 | 1DDAB52E-A468-49A3-972A-0B249E1B9C0B 224 | 225 | ypos 226 | 120 227 | 228 | 30CB87BB-3A5B-4AFB-80FF-6049286B6513 229 | 230 | ypos 231 | 10 232 | 233 | 8F78B728-AEE3-4FDB-AC4D-57D4AEE5485D 234 | 235 | ypos 236 | 230 237 | 238 | AEEE74EC-013E-4E75-9613-B680942F4B57 239 | 240 | ypos 241 | 10 242 | 243 | 244 | webaddress 245 | http://markokaestner.com 246 | 247 | 248 | -------------------------------------------------------------------------------- /src/search: -------------------------------------------------------------------------------- 1 | . workflowHandler.sh 2 | 3 | QUERY="$1" 4 | UUID=$(date +"%s") 5 | DB=$(getPref "searchdb" 0) 6 | DATADIR="$(getDataDir)" 7 | DBFILE="${DATADIR}/databases" 8 | 9 | setupDatabases() { 10 | ./setup > "$DBFILE" 11 | } 12 | 13 | showDatabases() { 14 | addResult "${UUID}1" "all" "All databases" "" "icon.png" "yes" 15 | COUNT=1 16 | while read DB; do 17 | DBUUID=${DB%%|*} 18 | DBNAME=${DB##*|} 19 | addResult "${UUID}${COUNT}" "$DBUUID" "$DBNAME" "" "icon.png" "yes" 20 | let COUNT+=1 21 | done < "$DBFILE" 22 | } 23 | 24 | showResults() { 25 | OLDIFS="$IFS" 26 | IFS=' 27 | ' 28 | 29 | if [ "$DB" == "all" ]; then 30 | MATCHES=$(/usr/bin/mdfind "(kMDItemContentType == 'com.devon-technologies.metadata.think' || kMDItemContentType == 'com.devon-technologies.metadata.thinkpro') && (kMDItemDisplayName == '*${QUERY}*'cd || kMDItemFinderComment == '*${QUERY}*'cd || kMDItemTextContent == '*${QUERY}*'cd || kMDItemTitle == '*${QUERY}*'cd)") 31 | else 32 | MATCHES=$(/usr/bin/mdfind "(kMDItemContentType == 'com.devon-technologies.metadata.think' || kMDItemContentType == 'com.devon-technologies.metadata.thinkpro') && (kMDItemDisplayName == '*${QUERY}*'cd || kMDItemFinderComment == '*${QUERY}*'cd || kMDItemTextContent == '*${QUERY}*'cd || kMDItemTitle == '*${QUERY}*'cd)" | grep "$DB") 33 | fi 34 | 35 | COUNT=1 36 | for MATCH in ${MATCHES[*]}; do 37 | FILENAME=$(/usr/bin/mdls -name "kMDItemDisplayName" -raw $MATCH) 38 | DBUUID=$(echo "$MATCH" | rev | cut -d "/" -f 3 | rev) 39 | DBNAME=$(grep "$DBUUID" "$DBFILE" | cut -d "|" -f 2) 40 | addResult "${RANDOM}${COUNT}" "$MATCH" "$FILENAME" "$DBNAME" "icon.png" "yes" 41 | let COUNT+=1 42 | done 43 | 44 | IFS="$OLDIFS" 45 | } 46 | 47 | resetWorkflow() { 48 | setPref "searchterm" "" 0 49 | setPref "searchdb" "" 0 50 | } 51 | 52 | main() { 53 | if [ ! -d "$DATADIR" ]; then 54 | mkdir -p "$DATADIR" 55 | fi 56 | 57 | if [ ! -f "$DBFILE" ]; then 58 | setupDatabases 59 | fi 60 | 61 | if [ -z "$DB" ]; then 62 | setPref "searchterm" "$QUERY" 0 63 | showDatabases 64 | else 65 | showResults 66 | resetWorkflow 67 | fi 68 | 69 | getXMLResults 70 | } 71 | 72 | main 73 | -------------------------------------------------------------------------------- /src/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | on run argv 4 | try 5 | my getDTDBs() 6 | on error errmsg 7 | return errmsg 8 | end try 9 | end run 10 | 11 | on getDTDBs() 12 | set retVal to "" 13 | tell application "DEVONthink Pro" 14 | repeat with theDB in databases 15 | set dbUUID to uuid of theDB as string 16 | set dbName to name of theDB as string 17 | if (length of retVal) > 0 then 18 | set retVal to retVal & "\n" 19 | end if 20 | set retVal to retVal & dbUUID & "|" & dbName 21 | end repeat 22 | end tell 23 | return retVal 24 | end getDTDBs 25 | -------------------------------------------------------------------------------- /src/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1.3, 3 | "remote_json": "https://raw.github.com/markokaestner/devonthink-document-search/master/package/DEVONthinkSearch.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/workflowHandler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VPREFS="${HOME}/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data/" 4 | NVPREFS="${HOME}/Library/Application Support/Alfred 2/Workflow Data/" 5 | 6 | RESULTS=() 7 | 8 | ################################################################################ 9 | # Adds a result to the result array 10 | # 11 | # $1 uid 12 | # $2 arg 13 | # $3 title 14 | # $4 subtitle 15 | # $5 icon 16 | # $6 valid 17 | # $7 autocomplete 18 | ############################################################################### 19 | addResult() { 20 | RESULT="$(xmlEncode "$3")$(xmlEncode "$4")$(xmlEncode "$5")" 21 | RESULTS+=("$RESULT") 22 | } 23 | 24 | ############################################################################### 25 | # Prints the feedback xml to stdout 26 | ############################################################################### 27 | getXMLResults() { 28 | echo "" 29 | 30 | # if [ "${#string[@]}" = "0" ]; then 31 | # echo "No results foundPlease try another search term" 32 | # fi 33 | 34 | for R in ${RESULTS[*]}; do 35 | echo "$R" | tr "\n" " " 36 | done 37 | 38 | echo "" 39 | } 40 | 41 | ############################################################################### 42 | # Escapes XML special characters with their entities 43 | ############################################################################### 44 | xmlEncode() { 45 | echo "$1" | sed s/"&"/"\&"/ | sed s/">"/"\>"/ | sed s/"<"/"\<"/ | sed s/"\'"/"\'"/ | sed s/"\""/"\""/ 46 | } 47 | 48 | ############################################################################### 49 | # Read the bundleid from the workflow's info.plist 50 | ############################################################################### 51 | getBundleId() { 52 | /usr/libexec/PlistBuddy -c "Print :bundleid" "info.plist" 53 | } 54 | 55 | ############################################################################### 56 | # Get the workflow data dir 57 | ############################################################################### 58 | getDataDir() { 59 | local BUNDLEID=$(getBundleId) 60 | echo "${NVPREFS}${BUNDLEID}" 61 | } 62 | 63 | ############################################################################### 64 | # Get the workflow cache dri 65 | ############################################################################### 66 | getCacheDir() { 67 | local BUNDLEID=$(getBundleId) 68 | echo "${VPREFS}${BUNDLEID}" 69 | } 70 | 71 | ############################################################################### 72 | # Save key=value to the workflow properties 73 | # 74 | # $1 key 75 | # $2 value 76 | # $3 non-volatile 0/1 77 | # $4 filename (optional, filename will be "settings" if not specified) 78 | ############################################################################### 79 | setPref() { 80 | local BUNDLEID=$(getBundleId) 81 | if [ "$3" = "0" ]; then 82 | local PREFDIR="${VPREFS}${BUNDLEID}" 83 | else 84 | local PREFDIR="${NVPREFS}${BUNDLEID}" 85 | fi 86 | 87 | if [ ! -d "$PREFDIR" ]; then 88 | mkdir -p "$PREFDIR" 89 | fi 90 | 91 | if [ -z "$4" ]; then 92 | local PREFFILE="${PREFDIR}/settings" 93 | else 94 | local PREFFILE="${PREFDIR}/$4" 95 | fi 96 | 97 | if [ ! -f "$PREFFILE" ]; then 98 | touch "$PREFFILE" 99 | fi 100 | 101 | local KEY_EXISTS=$(grep -c "$1=" "$PREFFILE") 102 | if [ "$KEY_EXISTS" = "0" ]; then 103 | echo "$1=$2" >> "$PREFFILE" 104 | else 105 | sed -i "" s/"$1=.*"/"$1=$2"/ "$PREFFILE" 106 | fi 107 | } 108 | 109 | ############################################################################### 110 | # Read a value for a given key from the workflow preferences 111 | # 112 | # $1 key 113 | # $2 non-volatile 0/1 114 | # $3 filename (optional, filename will be "settings" if not specified) 115 | ############################################################################### 116 | getPref() { 117 | local BUNDLEID=$(getBundleId) 118 | if [ "$2" = "0" ]; then 119 | local PREFDIR="${VPREFS}${BUNDLEID}" 120 | else 121 | local PREFDIR="${NVPREFS}${BUNDLEID}" 122 | fi 123 | 124 | if [ ! -d "$PREFDIR" ]; then 125 | return 126 | fi 127 | 128 | if [ -z "$3" ]; then 129 | local PREFFILE="${PREFDIR}/settings" 130 | else 131 | local PREFFILE="${PREFDIR}/$3" 132 | fi 133 | 134 | if [ ! -f "$PREFFILE" ]; then 135 | return 136 | fi 137 | 138 | local VALUE=$(sed "/^\#/d" "$PREFFILE" | grep "$1" | tail -n 1 | cut -d "=" -f2-) 139 | echo "$VALUE" 140 | } 141 | 142 | getLang() { 143 | defaults read .GlobalPreferences AppleLanguages | tr -d [:space:] | cut -c2-3 144 | } 145 | --------------------------------------------------------------------------------