gui CHANGE refactor work with schemas

- store all schemas as files in backend to unify work and keep all
  dependencies in a single searchpath
- work with schemas as with dictionary - move key from the structure
  itself to the dictionary key. However it is still possible to generate
  the key from the schema itself
diff --git a/backend/schemas.py b/backend/schemas.py
index 7b12e9d..7dfef7c 100644
--- a/backend/schemas.py
+++ b/backend/schemas.py
@@ -8,6 +8,8 @@
 import os
 import errno
 import time
+from subprocess import check_output
+from shutil import copy
 
 from liberouterapi import auth
 from flask import request
@@ -16,7 +18,7 @@
 from .inventory import INVENTORY, inventory_check
 from .error import NetopeerException
 
-__SCHEMAS_EMPTY = '{"schemas":{"timestamp":0,"schema":[]}}'
+__SCHEMAS_EMPTY = '{"timestamp":0, "schemas":{}}'
 
 
 def __schema_parse(path, format = yang.LYS_IN_UNKNOWN):
@@ -36,9 +38,9 @@
 			raise NetopeerException(str(e))
 
 	return module
-	
 
-def __schemas_init():
+
+def __schemas_init(path):
 	schemas = json.loads(__SCHEMAS_EMPTY)
 	try:
 		ctx = yang.Context()
@@ -48,7 +50,27 @@
 	# initialize the list with libyang's internal modules
 	modules = ctx.get_module_iter()
 	for module in modules:
-		schemas['schemas']['schema'].append({'key':module.name() + '@' + module.rev().date(), 'name':module.name(), 'revision':module.rev().date()})
+		name_norm = module.name() + '@' + module.rev().date() + '.yang'
+		schemas['schemas'][name_norm] = {'name':module.name(), 'revision':module.rev().date()}
+		try:
+			with open(os.path.join(path, name_norm), 'w') as schema_file:
+				schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0))
+		except:
+			pass
+	try:
+		nc_schemas_dir = check_output("pkg-config --variable=LNC2_SCHEMAS_DIR libnetconf2", shell = True).decode()
+		nc_schemas_dir = nc_schemas_dir[:len(nc_schemas_dir) - 1]
+		for file in os.listdir(nc_schemas_dir):
+			if file[-5:] == '.yang' or file[-4:] == '.yin':
+				try:
+					copy(os.path.join(nc_schemas_dir, file), path)
+				except:
+					pass
+			else:
+				continue
+	except:
+		pass
+
 	return schemas
 
 
@@ -59,35 +81,37 @@
 			schemas = json.load(schemas_file)
 	except OSError as e:
 		if e.errno == errno.ENOENT:
-			schemas = __schemas_init()
+			schemas = __schemas_init(path)
 		else:
 			raise NetopeerException('Unable to use user\'s schemas inventory ' + schemainv_path + ' (' + str(e) + ').')
 	except ValueError:
-		schemas = __schemas_init()
+		schemas = __schemas_init(path)
 
 	return schemas
 
+
 def __schemas_inv_save(path, schemas):
 	schemainv_path = os.path.join(path, 'schemas.json')
 
 	# update the timestamp
-	schemas['schemas']['timestamp'] = time.time()
+	schemas['timestamp'] = time.time()
 
 	#store the list
 	try:
 		with open(schemainv_path, 'w') as schema_file:
-			json.dump(schemas, schema_file)
+			json.dump(schemas, schema_file, sort_keys = True)
 	except Exception:
 		pass
 
 	return schemas
 
+
 def __schemas_update(path):
 	# get schemas database
 	schemas = __schemas_inv_load(path)
 	
 	# get the previous timestamp
-	timestamp = schemas['schemas']['timestamp']
+	timestamp = schemas['timestamp']
 	
 	# check the current content of the storage
 	for file in os.listdir(path):
@@ -104,22 +128,31 @@
 			try:
 				module = __schema_parse(schemapath, format)
 				if module.rev_size():
-					schemas['schemas']['schema'].append({'key':module.name() + '@' + module.rev().date(),
-														 'name':module.name(),
-														 'revision':module.rev().date(),
-														 'file':os.path.basename(schemapath)})
+					name_norm = module.name() + '@' + module.rev().date() + '.yang'
+					schemas['schemas'][name_norm] = {'name': module.name(), 'revision': module.rev().date()}
 				else:
-					schemas['schemas']['schema'].append({'key':module.name() + '@',
-														 'name':module.name(),
-														 'file':os.path.basename(schemapath)})
-			except Exception as e:
+					name_norm = module.name() + '.yang'
+					schemas['schemas'][name_norm] = {'name': module.name()}
+				if file != name_norm:
+					try:
+						with open(os.path.join(path, name_norm), 'w') as schema_file:
+							schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0))
+					except:
+						pass
+
+					try:
+						os.remove(schemapath)
+					except:
+						pass
+			except:
 				continue
 
 	#store the list
 	__schemas_inv_save(path, schemas)
 	
 	# return the up-to-date list 
-	return schemas['schemas']['schema']
+	return schemas['schemas']
+
 
 @auth.required()
 def schemas_list():
@@ -130,7 +163,7 @@
 	inventory_check(path)
 	schemas = __schemas_update(path)
 	
-	return(json.dumps(schemas))
+	return(json.dumps(schemas, sort_keys = True))
 
 
 @auth.required()
@@ -145,18 +178,13 @@
 	key = req['key']
 
 	schemas = __schemas_inv_load(path)
-	for i in range(len(schemas['schemas']['schema'])):
-		schema = schemas['schemas']['schema'][i]
-		if schema['key'] == key:
-			data = ""
-			if 'file' in schema:
-				with open(os.path.join(path, schema['file']), 'r') as schema_file:
-					data = schema_file.read()
-			else:
-				ctx = yang.Context()
-				data = ctx.get_module(schema['name']).print_mem(yang.LYS_OUT_YANG, 0)
+	if key in schemas['schemas']:
+		try:
+			with open(os.path.join(path, key), 'r') as schema_file:
+				data = schema_file.read()
 			return(json.dumps({'success': True, 'data': data}))
-
+		except:
+			pass;
 	return(json.dumps({'success': False, 'error-msg':'Schema ' + key + ' not found.'}))
 
 
@@ -182,13 +210,29 @@
 		else:
 			format = yang.LYS_IN_UNKNOWN
 		module = __schema_parse(path, format)
-		# TODO: normalize file name to allow removing without remembering schema path
+
+		# normalize file name to allow removing without remembering schema path
+		if module.rev_size():
+			name_norm = module.name() + '@' + module.rev().date() + '.yang'
+		else:
+			name_norm = module.name() + '.yang'
+		if file.filename != name_norm:
+			with open(os.path.join(INVENTORY, user.username, name_norm), 'w') as schema_file:
+				schema_file.write(module.print_mem(yang.LYS_OUT_YANG, 0))
+			try:
+				os.remove(path)
+			except:
+				pass
 	except Exception:
-		os.remove(path)
+		try:
+			os.remove(path)
+		except:
+			pass
 		return(json.dumps({'success': False}))
 			
 	return(json.dumps({'success': True}))
 
+
 @auth.required()
 def schemas_rm():
 	session = auth.lookup(request.headers.get('Authorization', None))
@@ -200,15 +244,9 @@
 		raise NetopeerException('Invalid schema remove request.')
 
 	schemas = __schemas_inv_load(path)
-	for i in range(len(schemas['schemas']['schema'])):
-		schema = schemas['schemas']['schema'][i]
-		if schema['key'] == key:
-			schemas['schemas']['schema'].pop(i)
-			break;
-		else:
-			schema = None;
-
-	if not schema:
+	try:
+		schemas.pop(key)
+	except KeyError:
 		# schema not in inventory
 		return (json.dumps({'success': False}))
 
@@ -216,12 +254,11 @@
 	__schemas_inv_save(path, schemas)
 
 	# remove the schema file
-	if 'revision' in schema:
-		path = os.path.join(path, schema['name'] + '@' + schema['revision'] + '.yang')
-	else:
-		path = os.path.join(path, schema['name'] + '.yang')
-	os.remove(path)
+	try:
+		os.remove(os.path.join(path, key))
+	except Exception as e:
+		print(e)
 
-	# TODO: resolve dependencies
+	# TODO: resolve dependencies ?
 
 	return(json.dumps({'success': True}))