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}))
diff --git a/frontend/config/config.component.html b/frontend/config/config.component.html
index 5ef82f4..4b53180 100644
--- a/frontend/config/config.component.html
+++ b/frontend/config/config.component.html
@@ -1,7 +1,8 @@
 <nav #subnav id="subnav">
-    <a *ngFor="let session of sessionsService.sessions" [class.active]="session.key==activeSession.key"
+    <a *ngFor="let session of sessionsService.sessions" [class.active]="session.key == sessionsService.activeSession"
         (click)="changeActiveSession(session.key)">{{session.device.hostname}}:{{session.device.port}}
-        <img *ngIf="session.key==activeSession.key" class="tab-icon tab-reload tab-action-first" src="assets/netopeer/icons/reload.svg"  alt="o" title="reload"
+        <img *ngIf="session.key == sessionsService.activeSession"
+            class="tab-icon tab-reload tab-action-first" src="assets/netopeer/icons/reload.svg"  alt="o" title="reload"
             onmouseover="this.src='assets/netopeer/icons/reload_active.svg'"
             onmouseout="this.src='assets/netopeer/icons/reload.svg'" (click)="reloadData()"/>
         <img class="tab-icon tab-close tab-action-last" src="assets/netopeer/icons/close.svg"  alt="x" title="disconnect"
@@ -15,7 +16,7 @@
 </nav>
 
 <div class="netopeer-content" [style.padding-top]="'calc(' + subnav.offsetHeight + 'px - -0.7em)'">
-<img *ngIf="!activeSession" src="assets/netopeer/starthere.svg" alt="Start here with that + sign."/>
+<img *ngIf="!activeSession" (click)="addSession()" src="assets/netopeer/starthere.svg" alt="Start here with that + sign."/>
 <div *ngIf="activeSession">
     <p class="msg-failure msg-rounded" *ngIf="err_msg"><img class="msg-close" (click)="err_msg=''" src="assets/netopeer/icons/close_active.svg" alt="x" title="close"/>{{err_msg}}</p>
     <table class="items">
diff --git a/frontend/config/sessions.service.ts b/frontend/config/sessions.service.ts
index 8d28e24..fc850c4 100644
--- a/frontend/config/sessions.service.ts
+++ b/frontend/config/sessions.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@angular/core';
+import { Injectable, OnInit } from '@angular/core';
 import { Http, Response, RequestOptions, URLSearchParams } from '@angular/http';
 import { Observable } from 'rxjs/Observable';
 import 'rxjs/add/operator/catch';
@@ -9,7 +9,7 @@
 import { Session } from './session';
 
 @Injectable()
-export class SessionsService{
+export class SessionsService implements OnInit {
     public sessions: Session[];
     public activeSession;
 
@@ -18,6 +18,9 @@
         if (!this.activeSession) {
             this.activeSession = "";
         }
+    }
+
+    ngOnInit(): void {
         this.checkSessions();
     }
 
diff --git a/frontend/inventory/schema.ts b/frontend/inventory/schema.ts
index fc0fa31..b20c2d3 100644
--- a/frontend/inventory/schema.ts
+++ b/frontend/inventory/schema.ts
@@ -1,5 +1,4 @@
 export class Schema {
-    key: string;
     name: string;
     revision: string;
 }
diff --git a/frontend/inventory/schemas.component.html b/frontend/inventory/schemas.component.html
index 58cb62b..a95bdd8 100644
--- a/frontend/inventory/schemas.component.html
+++ b/frontend/inventory/schemas.component.html
@@ -22,20 +22,16 @@
     <th>name</th>
     <th class="item_right">revision</th>
   </tr>
-  <tr class="item" [class.selected]="schema === selectedSchema" *ngFor="let schema of schemas">
+  <tr class="item" *ngFor="let key of schemasKeys()">
     <td class="item_left item_actions">
-        <img class="item_action_delete" (click)="remove(schema)"
+        <img class="item_action_delete" (click)="remove(key)"
             src="assets/netopeer/icons/close.svg" alt="x" title="delete"
             onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
             onmouseout="this.src='assets/netopeer/icons/close.svg'"/>
     </td>
-    <td class="schema schema-name" (click)="onSelect(schema)">{{schema.name}}</td>
-    <td class="item_right schema schema-revision" (click)="onSelect(schema)">{{schema.revision}}</td>
+    <td class="schema schema-name" (click)="onSelect(key)">{{schemas[key].name}}</td>
+    <td class="item_right schema schema-revision" (click)="onSelect(key)">{{schemas[key].revision}}</td>
   </tr>
 </table>
 
-<div *ngIf="selectedSchema">
-  <h3>{{selectedSchema.name}}<span *ngIf="selectedSchema.revision">@{{selectedSchema.revision}}</span></h3>
 </div>
-
-</div>
\ No newline at end of file
diff --git a/frontend/inventory/schemas.component.ts b/frontend/inventory/schemas.component.ts
index a1262c1..92f22ea 100644
--- a/frontend/inventory/schemas.component.ts
+++ b/frontend/inventory/schemas.component.ts
@@ -14,14 +14,13 @@
 
 export class InventorySchemasComponent implements OnInit {
     schemas: Schema[];
-    @Input() selectedSchema: Schema;
     addingSchema = false;
     addingResult = -1;
     constructor( private schemasService: SchemasService,
         private router: Router ) { }
 
     getSchemas(): void {
-        this.schemasService.getSchemas().then( result => this.schemas = result );
+        this.schemasService.getSchemas().then( result => {this.schemas = result;});
     }
 
     showAddSchema() {
@@ -40,8 +39,8 @@
             result => { this.addingResult = result['success'] ? 1 : 0; this.getSchemas() } );
     }
 
-    remove( schema: Schema ) {
-        this.schemasService.rmSchema( schema ).subscribe(
+    remove(key: string) {
+        this.schemasService.rmSchema(key).subscribe(
             result => { if ( result['success'] ) { this.getSchemas() } } );
     }
 
@@ -49,9 +48,15 @@
         this.getSchemas();
     }
 
-    onSelect( schema: Schema ): void {
-        this.schemasService.show(schema);
-        this.schemasService.changeActiveSchema(schema.key);
+    schemasKeys() {
+        if (this.schemas) {
+            return Object.keys(this.schemas);
+        }
+    }
+
+    onSelect(key: string): void {
+        this.schemasService.show(key, this.schemas[key]);
+        this.schemasService.changeActiveSchemaKey(key);
         this.router.navigateByUrl( '/netopeer/yang' );
     }
 }
diff --git a/frontend/yang/schemas.service.ts b/frontend/yang/schemas.service.ts
index 91b916e..168ddac 100644
--- a/frontend/yang/schemas.service.ts
+++ b/frontend/yang/schemas.service.ts
@@ -8,7 +8,7 @@
 
 @Injectable()
 export class SchemasService {
-    public schemas: Schema[];
+    public schemas;
     public activeSchema: string;
 
     constructor( private http: Http ) {
@@ -28,25 +28,36 @@
         this.schemas = JSON.parse(localStorage.getItem('schemas'));
     }
 
-    getActiveSchema(key: string = this.activeSchema): Schema {
-        if (!key) {
-            return null;
+    schemasKeys() {
+        if (this.schemas) {
+            return Object.keys(this.schemas);
         }
-        for (let i = this.schemas.length; i > 0; i--) {
-            if (this.schemas[i - 1].key == key) {
-                return this.schemas[i - 1];
-            }
-        }
-        return null;
     }
 
-    changeActiveSchema(key: string): Schema {
-        let result = this.getActiveSchema(key);
-        if (result) {
+    getSchemaKey(schema: Schema) {
+        if (!schema) {
+            return null
+        } else if ('revision' in schema) {
+            return schema.name + '@' + schema.revision + '.yang';
+        } else {
+            return schema.name + '.yang';
+        }
+    }
+
+    getActiveSchema(key: string = this.activeSchema): Schema {
+        if (key in this.schemas) {
+            return this.schemas[key];
+        } else {
+            return null;
+        }
+    }
+
+    changeActiveSchemaKey(key: string): Schema {
+        if (key && (key in this.schemas)) {
             this.activeSchema = key;
             localStorage.setItem('activeSession', this.activeSchema);
         }
-        return result;
+        return this.schemas[key];
     }
 
     getSchemas() {
@@ -54,19 +65,17 @@
             .map(( resp: Response ) => resp.json()).toPromise();
     }
 
-    show(schema: Schema) {
+    show(key: string, schema: Schema) {
         let newSchema = true;
-        for (let i in this.schemas) {
-            if (this.schemas[i].key == schema.key) {
-                schema = this.schemas[i];
-                newSchema = false;
-                break;
-            }
+
+        if (key in this.schemas) {
+            newSchema = false;
+            schema = this.schemas[key];
         }
 
         if (!('data' in schema)) {
             let params = new URLSearchParams();
-            params.set('key', schema.key);
+            params.set('key', key);
             let options = new RequestOptions({ search: params });
             this.http.get('/netopeer/inventory/schema', options)
                 .map((resp: Response) => resp.json()).toPromise().then(result => {
@@ -74,13 +83,12 @@
                     if (result['success']) {
                         schema['data'] = result['data'];
                         this.storeData();
-                        console.log(this.schemas)
                     }
                 });
         }
 
         if (newSchema) {
-            this.schemas.push(schema);
+            this.schemas[key] = schema;
             this.storeData();
         }
     }
@@ -95,8 +103,8 @@
             .catch(( err: Response | any ) => Observable.throw( err ) );
     }
 
-    rmSchema( schema: Schema ) {
-        let options = new RequestOptions( { body: JSON.stringify(schema.key) } );
+    rmSchema(key: string) {
+        let options = new RequestOptions( { body: JSON.stringify(key) } );
         return this.http.delete( '/netopeer/inventory/schemas', options )
             .map(( resp: Response ) => resp.json() )
             .catch(( err: Response | any ) => Observable.throw( err ) );
diff --git a/frontend/yang/yang.component.html b/frontend/yang/yang.component.html
index 1416bb3..ed016e8 100644
--- a/frontend/yang/yang.component.html
+++ b/frontend/yang/yang.component.html
@@ -1,19 +1,17 @@
 <nav #subnav id="subnav">
-    <a *ngFor="let schema of schemasService.schemas" [class.active]="schema.key==activeSchema.key"
-        (click)="changeActiveSchema(schema.key)">{{schema.name}}@{{schema.revision}}
+    <a *ngFor="let key of schemasService.schemasKeys()" [class.active]="key == schemasService.activeSchema"
+        (click)="schemasService.changeActiveSchemaKey(key)">{{schemasService.schemas[key].name}}@{{schemasService.schemas[key].revision}}
         <img class="tab-icon tab-close tab-action-last" src="assets/netopeer/icons/close.svg"  alt="x" title="close"
             onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
-            onmouseout="this.src='assets/netopeer/icons/close.svg'" (click)="close(schema.key)"/>
+            onmouseout="this.src='assets/netopeer/icons/close.svg'" (click)="close(key)"/>
     </a><a (click)="addSchema()" onmouseout="getElementById('tabadd').style.filter='invert(100%)'" onmouseover="getElementById('tabadd').style.filter='invert(0%)'">
         <img id="tabadd" class="tab-icon tab-add tab-action-last" src="assets/netopeer/icons/add.svg" alt="+" title="open schema"
             onmouseover="this.src='assets/netopeer/icons/add_active.svg'"
             onmouseout="this.src='assets/netopeer/icons/add.svg'"/>
     </a>
 </nav>
-
 <div class="netopeer-content" [style.padding-top]="'calc(' + subnav.offsetHeight + 'px - -0.7em)'">
-<img *ngIf="!activeSchema" src="assets/netopeer/starthere.svg" alt="Start here with that + sign."/>
+<img *ngIf="!schemasService.activeSchema" (click)="addSchema()" src="assets/netopeer/starthere.svg" alt="Start here with that + sign."/>
 
-<pre *ngIf="activeSchema">{{activeSchema.data}}</pre>
-
-</div>
\ No newline at end of file
+<pre *ngIf="schemasService.activeSchema">{{schemasService.schemas[schemasService.activeSchema].data}}</pre>
+</div>
diff --git a/frontend/yang/yang.component.ts b/frontend/yang/yang.component.ts
index 388c7ae..eddcf7c 100644
--- a/frontend/yang/yang.component.ts
+++ b/frontend/yang/yang.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 import {Router} from '@angular/router';
 
 import {SchemasService} from './schemas.service';
@@ -10,9 +10,8 @@
   styleUrls : ['./yang.component.scss']
 })
 
-export class YANGComponent implements OnInit {
+export class YANGComponent {
   title = 'YANG Explorer';
-  activeSchema: Schema;
 
   constructor(private schemasService: SchemasService,
               private router: Router) {}
@@ -22,29 +21,18 @@
       this.router.navigateByUrl('/netopeer/inventory/schemas');
   }
 
-  changeActiveSchema(key: string) {
-      this.activeSchema = this.schemasService.changeActiveSchema(key);
-  }
-
   close(key: string) {
-      for (let i in this.schemasService.schemas) {
-          if (this.schemasService.schemas[i].key == key) {
-              this.schemasService.schemas.splice(Number(i), 1);
-              if (this.schemasService.activeSchema == key) {
-                  if (Number(i) > 0) {
-                      this.changeActiveSchema(this.schemasService.schemas[Number(i) - 1].key)
-                  } else if (this.schemasService.schemas.length) {
-                      this.changeActiveSchema(this.schemasService.schemas[0].key)
-                  }
-              }
-              this.schemasService.storeData();
-              break;
+      let index = Object.keys(this.schemasService.schemas).indexOf(key);
+      if (this.schemasService.activeSchema == key) {
+          if (index > 0) {
+              this.schemasService.changeActiveSchemaKey(Object.keys(this.schemasService.schemas)[index - 1])
+          } else if (Object.keys(this.schemasService.schemas).length > 1) {
+              this.schemasService.changeActiveSchemaKey(Object.keys(this.schemasService.schemas)[1])
+          } else {
+              this.schemasService.activeSchema = null;
           }
       }
-      this.activeSchema = this.schemasService.getActiveSchema();
-  }
-
-  ngOnInit(): void {
-      this.activeSchema = this.schemasService.getActiveSchema();
+      delete this.schemasService.schemas[key];
+      this.schemasService.storeData();
   }
 }