├── taggy.json ├── LICENCE.md ├── taggy_client.cfg ├── taggy_client.sh ├── README.md └── taggy.py /taggy.json: -------------------------------------------------------------------------------- 1 | { 2 | "taggy_host" : "0.0.0.0", 3 | "taggy_port" : "5555", 4 | "dav_auth_path" : "http://localhost/nextcloud/remote.php/dav/files/", 5 | "dbname" : "nextcloud", 6 | "dbuser" : "nextcloud", 7 | "dbpass" : "nextcloud", 8 | "dbhost" : "localhost", 9 | "dbport" : "3306" 10 | } 11 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Julian Thome 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /taggy_client.cfg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # taggy - a nextcloud tag server 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2017 Julian Thome 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | # Nextcloud configuration 28 | DAV_PATH="/nextcloud/remote.php/dav/files" 29 | HOST="localhost" 30 | PORT="80" 31 | 32 | # Taggy server configuration 33 | TAGGY_HOST="$HOST" 34 | TAGGY_PORT="5555" 35 | 36 | 37 | -------------------------------------------------------------------------------- /taggy_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # taggy - a nextcloud tag server 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2017 Julian Thome 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | USAGE="Usage: $0 " 28 | 29 | source ./taggy_client.cfg 30 | 31 | USER="$1" 32 | PW="$2" 33 | FILE="$3" 34 | TAG="$4" 35 | 36 | [ -z "$USER" ] || [ -z "$PW" ] || [ -z "$FILE" ] || [ -z "$TAG" ] && { 37 | echo "malformed arguments" 38 | echo "$USAGE" 39 | exit 1 40 | } 41 | 42 | [ $# -ne 4 ] && { 43 | echo "Wrong number of arguments" 44 | echo "$USAGE" 45 | exit 1 46 | } 47 | 48 | [ ! -f "$FILE" ] && { 49 | echo "file \"$FILE\" does not exist" 50 | exit 1 51 | } 52 | 53 | [ -z "$TAG" ] && { 54 | echo "tag definition \"$TAG\" is empty" 55 | exit 1 56 | } 57 | 58 | [[ ! $TAG =~ ^[0-9a-zA-Z]{1,40}$ ]] && { 59 | echo "malformed tag" 60 | exit 1 61 | } 62 | 63 | FILENAME="$(basename $FILE)" 64 | 65 | curl -u "$USER:$PW" -T "$FILE" "http://$HOST:$PORT/$DAV_PATH/$USER/$FILENAME" 66 | 67 | PAYLOAD="{\"user\":\"$USER\",\"pw\":\"$PW\",\"file\":\"files/$FILENAME\",\"tags\":[\"$TAG\"]}" 68 | 69 | curl -H "Content-Type: application/json" -X POST -d "$PAYLOAD" "http://$TAGGY_HOST:$TAGGY_PORT/tag" 70 | 71 | exit 0 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # taggy 2 | 3 | taggy is a simple micro-service for tagging files within your 4 | [Nextcloud](https://Nextcloud.com/) repository. 5 | 6 | Nextcloud provides a scriptable DAV interface for managing files within your 7 | Nextcloud storage. However, Nextcloud does not provide a scriptable 8 | interface for automated (remote) file tagging yet. 9 | 10 | In order to tag uploaded files automatically, you can run the taggy 11 | microservice on your Nextcloud server. taggy provides a simple JSON REST 12 | API for tagging your files automatically. The following payload will give 13 | you an idea what information is required by taggy: 14 | 15 | ```json 16 | { 17 | "user": "", 18 | "pw": "", 19 | "file": "files/", 20 | "tags": [ "tag0","tag1" ] 21 | } 22 | ``` 23 | 24 | The script `taggy_client.sh` illustrates how the taggy microservice 25 | (`taggy.py`) can be invoked remotely. The file `taggy_client.cfg` contains the 26 | configuration for the taggy client whereas the file `taggy.json` contains the 27 | configuration for the taggy service which is supposed to run on the 28 | Nextcloud server. 29 | 30 | # Installation 31 | 32 | taggy requires Python Version 3 with the python modules `requests`, `mysql` 33 | and `bottle`. 34 | 35 | # Limitations 36 | 37 | Currently, the communication between taggy client and server is not encrypted 38 | so that the credentials are transmitted in plain text which can be dangerous 39 | depending on your setup. It is recommended to enable HTTPS for your Nextcloud 40 | installation. SSL support for taggy will be added soon. 41 | 42 | # Licence 43 | The MIT License (MIT) 44 | 45 | Copyright (c) 2017 Julian Thome 46 | 47 | Permission is hereby granted, free of charge, to any person obtaining a copy of 48 | this software and associated documentation files (the "Software"), to deal in 49 | the Software without restriction, including without limitation the rights to 50 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 51 | of the Software, and to permit persons to whom the Software is furnished to do 52 | so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in all 55 | copies or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 63 | SOFTWARE. 64 | -------------------------------------------------------------------------------- /taggy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # taggy - a nextcloud tag server 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Copyright (c) 2017 Julian Thome 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | 27 | from bottle import run, post, request, response, get, route 28 | 29 | import subprocess 30 | import mysql.connector as mariadb 31 | import sys 32 | import json 33 | from subprocess import call 34 | import requests 35 | 36 | dbuser = "" 37 | dbpass = "" 38 | dbname = "" 39 | taggy_host = "" 40 | taggy_port = "" 41 | dav_auth_path = "" 42 | 43 | def dbchange(storage, ftotag, tag): 44 | 45 | db = mariadb.connect(user=dbuser, password=dbpass, database=dbname, host=dbhost, port=dbport) 46 | cursor = db.cursor() 47 | 48 | cursor.execute("SELECT name,id FROM oc_systemtag") 49 | 50 | tagdict = {} 51 | tagset = set() 52 | 53 | for name, id in cursor: 54 | tagdict[str(name, 'utf-8')] = str(id) 55 | tagset.add(str(name, 'utf-8')) 56 | print("name:" + str(name, 'utf-8') + ", id:" + str(id)) 57 | print(tagdict) 58 | 59 | tid = -1 60 | if tag in tagset: 61 | tid = tagdict[tag] 62 | print("tag " + tag + " is already in set " + tid) 63 | else: 64 | cursor.execute("insert into oc_systemtag(name) values (%s)", (tag,)) 65 | db.commit() 66 | cursor.execute("select id from oc_systemtag where name = %s", (tag,)) 67 | tid = cursor.fetchone()[0] 68 | 69 | print("storage " + storage) 70 | cursor.execute( 71 | "select numeric_id from oc_storages where id = %s", (storage,)) 72 | 73 | sid = cursor.fetchone()[0] 74 | 75 | print("sid " + str(sid)) 76 | 77 | cursor.execute("select fileid from oc_filecache where storage = %s \ 78 | and path = %s", (str(sid), str(ftotag),)) 79 | 80 | fid = cursor.fetchone()[0] 81 | 82 | print("sid " + str(sid) + " fid " + str(fid)) 83 | 84 | cursor.execute("select * from oc_systemtag_object_mapping where objectid \ 85 | = %s and systemtagid = %s", (str(fid), str(tid),)) 86 | 87 | if cursor.rowcount > 0: 88 | print('systemtag already there') 89 | else: 90 | cursor.fetchall() 91 | print("no systemtag connection") 92 | try: 93 | cursor.execute("insert into oc_systemtag_object_mapping(objectid,\ 94 | objecttype,systemtagid) values (%s,'files',%s)", 95 | (str(fid), str(tid),)) 96 | except mariadb.Error as error: 97 | print(str(error)) 98 | finally: 99 | db.commit() 100 | 101 | cursor.close() 102 | db.close() 103 | 104 | 105 | def authenticate(user, password): 106 | r = requests.get(dav_auth_path, 107 | auth=(user, password)) 108 | if r.status_code == 200: 109 | return True 110 | return False 111 | 112 | 113 | @route('/tag', method='POST') 114 | def tag(): 115 | data = request.json 116 | print('data') 117 | print(data) 118 | storage = "home::" + data['user'] 119 | fil = data['file'] 120 | tags = data['tags'] 121 | user = data['user'] 122 | pw = data['pw'] 123 | if authenticate(user, pw): 124 | print("storage " + storage + " fil " + fil + " tag " + str(tags)) 125 | for tag in tags: 126 | dbchange(storage, fil, tag) 127 | else: 128 | print("authentication error") 129 | 130 | 131 | def read_config(cfg): 132 | global dbuser, dbpass, dbname, dbhost, dbport, taggy_host, taggy_port, dav_auth_path 133 | print('read file %s' % cfg) 134 | try: 135 | cfg = open(cfg, 'r') 136 | except: 137 | print('cannot read file %s' % cfg) 138 | sys.exit(-1) 139 | scfg = str(cfg.read()) 140 | config = json.loads(scfg) 141 | dbuser = config["dbuser"] 142 | dbpass = config["dbpass"] 143 | dbname = config["dbname"] 144 | dbhost = config["dbhost"] 145 | dbport = config["dbport"] 146 | taggy_host = config["taggy_host"] 147 | taggy_port = config["taggy_port"] 148 | dav_auth_path = config["dav_auth_path"] 149 | 150 | 151 | if len(sys.argv) < 2: 152 | sys.exit('Usage: %s ' % sys.argv[0]) 153 | 154 | read_config(sys.argv[1]) 155 | print('start with dbuser: %s, dbname: %s, dbhost: %s, dbport: %s, taggy_host: %s, taggy_port: %s' % 156 | (dbuser, dbname, dbhost, dbport, taggy_host, taggy_port)) 157 | 158 | run(host=taggy_host, port=taggy_port, debug=False) 159 | 160 | --------------------------------------------------------------------------------