| 
					
				 | 
			
			
				@@ -0,0 +1,145 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import json 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from operator import attrgetter 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from threading import Thread 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+from random import random 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+# ======================================================== 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+# Contact Model 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+# ======================================================== 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+PAGE_SIZE = 100 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class Contact: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    # mock contacts database 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    db = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, id_=None, first=None, last=None, phone=None, email=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.id = id_ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.first = first 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.last = last 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.phone = phone 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.email = email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.errors = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __str__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return json.dumps(self.__dict__, ensure_ascii=False) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def update(self, first, last, phone, email): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.first = first 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.last = last 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.phone = phone 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.email = email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def validate(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.email: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.errors['email'] = "Email Required" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        existing_contact = next(filter(lambda c: c.id != self.id and c.email == self.email, Contact.db.values()), None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if existing_contact: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.errors['email'] = "Email Must Be Unique" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return len(self.errors) == 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def save(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self.validate(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self.id is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if len(Contact.db) == 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                max_id = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                max_id = max(contact.id for contact in Contact.db.values()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.id = max_id + 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Contact.db[self.id] = self 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Contact.save_db() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def delete(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        del Contact.db[self.id] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Contact.save_db() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def count(cls): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        time.sleep(2) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return len(cls.db) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def all(cls, page=1): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        page = int(page) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        start = (page - 1) * PAGE_SIZE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        end = start + PAGE_SIZE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return list(cls.db.values())[start:end] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def search(cls, text): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        result = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for c in cls.db.values(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match_first = c.first is not None and text in c.first 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match_last = c.last is not None and text in c.last 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match_email = c.email is not None and text in c.email 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match_phone = c.phone is not None and text in c.phone 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if match_first or match_last or match_email or match_phone: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                result.append(c) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return result 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def load_db(cls): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        with open('contacts.json', 'r') as contacts_file: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            contacts = json.load(contacts_file) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            cls.db.clear() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for c in contacts: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                cls.db[c['id']] = Contact(c['id'], c['first'], c['last'], c['phone'], c['email']) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @staticmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def save_db(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        out_arr = [c.__dict__ for c in Contact.db.values()] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        with open("contacts.json", "w") as f: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            json.dump(out_arr, f, indent=2) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def find(cls, id_): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        id_ = int(id_) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        c = cls.db.get(id_) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if c is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            c.errors = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return c 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+class Archiver: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    archive_status = "Waiting" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    archive_progress = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    thread = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def status(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Archiver.archive_status 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def progress(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Archiver.archive_progress 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def run(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if Archiver.archive_status == "Waiting": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Archiver.archive_status = "Running" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Archiver.archive_progress = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Archiver.thread = Thread(target=self.run_impl) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Archiver.thread.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def run_impl(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for i in range(10): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            time.sleep(1 * random()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if Archiver.archive_status != "Running": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Archiver.archive_progress = (i + 1) / 10 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            print("Here... " + str(Archiver.archive_progress)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        time.sleep(1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if Archiver.archive_status != "Running": 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Archiver.archive_status = "Complete" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def archive_file(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return "contacts.json" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def reset(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Archiver.archive_status = "Waiting" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @classmethod 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def get(cls): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Archiver() 
			 |