1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{Entropy Client Services UGC Base Interfaces}.
10
11 """
12
13 import os
14 from entropy.core import Singleton
15 from entropy.exceptions import *
16 from entropy.const import etpConst, etpCache, const_setup_file, const_setup_perms
17 from entropy.i18n import _
18
20
21 ssl_connection = True
22 - def __init__(self, EquoInstance, quiet = True, show_progress = False):
23
24 from entropy.client.interfaces import Client as Cl
25 if not isinstance(EquoInstance, Cl):
26 mytxt = _("A valid Client based instance is needed")
27 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,))
28
29 import socket, threading
30 self.socket, self.threading = socket, threading
31 import struct
32 self.struct = struct
33 self.Entropy = EquoInstance
34 self.store = AuthStore()
35 self.quiet = quiet
36 self.show_progress = show_progress
37 self.UGCCache = Cache(self)
38 self.TxLocks = {}
39
66
78
80
81 aware = self.UGCCache._get_live_cache_item(repository, 'is_repository_eapi3_aware')
82 if aware != None:
83 return aware
84
85 srv = self.get_service_connection(repository, check = False,
86 timeout = 6)
87 if srv == None:
88 aware = False
89 else:
90 session = srv.open_session()
91 if session != None:
92 srv.close_session(session)
93 srv.disconnect()
94 aware = True
95 else:
96 aware = False
97
98 self.UGCCache._set_live_cache_item(repository, 'is_repository_eapi3_aware', aware)
99 return aware
100
103
106
107 - def do_login(self, repository, force = False):
108
109 login_data = self.read_login(repository)
110 if (login_data != None) and not force:
111 return True, _('ok')
112
113 aware = self.is_repository_eapi3_aware(repository)
114 if not aware:
115 return False, _('repository does not support EAPI3')
116
117 def fake_callback(*args,**kwargs):
118 return True
119
120 attempts = 3
121 while attempts:
122
123
124 input_params = [
125 ('username', _('Username'), fake_callback, False),
126 ('password', _('Password'), fake_callback, True)
127 ]
128 login_data = self.Entropy.inputBox(
129 "%s %s %s" % (_('Please login against'), repository, _('repository'),),
130 input_params,
131 cancel_button = True
132 )
133 if not login_data:
134 return False, _('login abort')
135
136
137 srv = self.get_service_connection(repository)
138 if srv == None:
139 return False, _('connection issues')
140 session = srv.open_session()
141 login_status, login_msg = srv.CmdInterface.service_login(login_data['username'], login_data['password'], session)
142 if not login_status:
143 srv.close_session(session)
144 srv.disconnect()
145 self.Entropy.askQuestion("%s: %s" % (
146 _("Access denied. Login failed"), login_msg,),
147 responses = [_("Ok")])
148 attempts -= 1
149 continue
150
151
152 srv.close_session(session)
153 srv.disconnect()
154 rc = self.Entropy.askQuestion(
155 _("Login successful. Do you want to save these credentials ?"))
156 save = False
157 if rc == _("Yes"):
158 save = True
159 self.store.store_login(login_data['username'],
160 login_data['password'], repository, save = save)
161 return True, _('ok')
162
163
164 - def login(self, repository, force = False):
171
172
173 - def logout(self, repository):
175
176 - def do_cmd(self, repository, login_required, func, args, kwargs):
177
178 if repository not in self.TxLocks:
179 self.TxLocks[repository] = self.threading.Lock()
180
181 with self.TxLocks[repository]:
182
183 if login_required:
184 status, err_msg = self.do_login(repository)
185 if not status:
186 return False, err_msg
187
188 srv = self.get_service_connection(repository)
189 if srv == None:
190 return False, 'no connection'
191 session = srv.open_session()
192 if session == None:
193 return False, 'no session'
194 args.insert(0, session)
195
196 if login_required:
197 stored_pass = False
198 while True:
199
200 login_data = self.read_login(repository)
201 if login_data == None:
202 status, msg = self.login(repository)
203 if not status: return status, msg
204 username, password = self.read_login(repository)
205 else:
206 stored_pass = True
207 username, password = login_data
208 logged, error = srv.CmdInterface.service_login(username, password, session)
209 if not logged:
210 if stored_pass:
211 stored_pass = False
212 self.remove_login(repository)
213 continue
214 srv.close_session(session)
215 srv.disconnect()
216 return logged, error
217 break
218
219 try:
220 cmd_func = getattr(srv.CmdInterface, func)
221 except AttributeError:
222 return False, 'local function not available'
223 rslt = cmd_func(*args,**kwargs)
224 try:
225 srv.close_session(session)
226 srv.disconnect()
227 except ConnectionError:
228 return False, 'no connection'
229
230 return rslt
231
234
237
239 return self.do_cmd(repository, False, "ugc_get_documents_by_identifiers", [identifiers], {})
240
244
248
252
253 - def add_vote(self, repository, pkgkey, vote):
254 data = self.do_cmd(repository, True, "ugc_do_vote", [pkgkey, vote], {})
255 if isinstance(data, tuple): voted, add_err_msg = data
256 else: return False, 'wrong server answer'
257 if voted: self.get_vote(repository, pkgkey)
258 return voted, add_err_msg
259
260 - def get_vote(self, repository, pkgkey):
261 vote, err_msg = self.do_cmd(repository, False, "ugc_get_vote", [pkgkey], {})
262 if isinstance(vote, float):
263 mydict = {pkgkey: vote}
264 self.UGCCache.update_vote_cache(repository, mydict)
265 return vote, err_msg
266
272
275
277 data = self.do_cmd(repository, False, "ugc_get_downloads", [pkgkey], {})
278 if isinstance(data, tuple): downloads, err_msg = data
279 else: return False, 'wrong server answer'
280 if downloads:
281 mydict = {pkgkey: downloads}
282 self.UGCCache.update_downloads_cache(repository, mydict)
283 return downloads, err_msg
284
290
292 return self.do_cmd(repository, False, "ugc_do_download_stats", [pkgkeys], {})
293
294 - def send_file(self, repository, pkgkey, file_path, title, description, keywords):
297
301
302 - def send_image(self, repository, pkgkey, image_path, title, description, keywords):
305
309
310 - def send_youtube_video(self, repository, pkgkey, video_path, title, description, keywords):
313
317
318 - def get_docs(self, repository, pkgkey):
319 data = self.do_cmd(repository, False, "ugc_get_docs", [pkgkey], {})
320 if isinstance(data, tuple): docs_data, err_msg = data
321 else: return False, 'wrong server answer'
322 if err_msg == 'ok':
323 self.UGCCache.update_alldocs_cache(pkgkey, repository, docs_data)
324 return docs_data, err_msg
325
327 if ugc_type == etpConst['ugc_doctypes']['generic_file']:
328 return self.send_file(repository, pkgkey, data, title, description, keywords)
329 elif ugc_type == etpConst['ugc_doctypes']['image']:
330 return self.send_image(repository, pkgkey, data, title, description, keywords)
331 elif ugc_type == etpConst['ugc_doctypes']['youtube_video']:
332 return self.send_youtube_video(repository, pkgkey, data, title, description, keywords)
333 elif ugc_type == etpConst['ugc_doctypes']['comments']:
334 return self.add_comment(repository, pkgkey, description, title, keywords)
335 return None, 'type not supported locally'
336
347
350
351
353
354 access_file = etpConst['ugc_accessfile']
356
357 from xml.dom import minidom
358 from xml.parsers import expat
359 self.expat = expat
360 self.minidom = minidom
361 self.setup_store_paths()
362 try:
363 self.setup_permissions()
364 except IOError:
365 pass
366 self.store = {}
367 try:
368 self.xmldoc = self.minidom.parse(self.access_file)
369 except (self.expat.ExpatError, IOError,):
370 self.xmldoc = None
371 if self.xmldoc != None:
372 try:
373 self.parse_document()
374 except self.expat.ExpatError:
375 self.xmldoc = None
376 self.store = {}
377
379 myhome = os.getenv("HOME")
380 if myhome != None:
381 if os.path.isdir(myhome) and os.access(myhome, os.W_OK):
382 self.access_file = os.path.join(myhome, ".config/entropy",
383 os.path.basename(self.access_file))
384 self.access_dir = os.path.dirname(self.access_file)
385
404
406 self.store.clear()
407 store = self.xmldoc.getElementsByTagName("store")[0]
408 repositories = store.getElementsByTagName("repository")
409 for repository in repositories:
410 repoid = repository.getAttribute("id")
411 if not repoid: continue
412 username = repository.getElementsByTagName("username")[0].firstChild.data.strip()
413 password = repository.getElementsByTagName("password")[0].firstChild.data.strip()
414 self.store[repoid] = {'username': username, 'password': password}
415
416 - def store_login(self, username, password, repository, save = True):
417 self.store[repository] = {'username': username, 'password': password}
418 if save:
419 self.save_store()
420
422
423 self.xmldoc = self.minidom.Document()
424 store = self.xmldoc.createElement("store")
425
426 for repository in self.store:
427 repo = self.xmldoc.createElement("repository")
428 repo.setAttribute('id', repository)
429
430 username = self.xmldoc.createElement("username")
431 username_value = self.xmldoc.createTextNode(self.store[repository]['username'])
432 username.appendChild(username_value)
433 repo.appendChild(username)
434
435 password = self.xmldoc.createElement("password")
436 password_value = self.xmldoc.createTextNode(self.store[repository]['password'])
437 password.appendChild(password_value)
438 repo.appendChild(password)
439 store.appendChild(repo)
440
441 self.xmldoc.appendChild(store)
442 f = open(self.access_file, "w")
443 f.writelines(self.xmldoc.toprettyxml(indent=" "))
444 f.flush()
445 f.close()
446 self.setup_permissions()
447 self.parse_document()
448
454
458
460
462
463 if not isinstance(UGCClientInstance, Client):
464 mytxt = _("A valid UGC Client interface based instance is needed")
465 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,))
466
467 import threading
468 import entropy.dump as dumpTools
469 self.CacheLock = threading.Lock()
470 self.dumpTools = dumpTools
471 self.Service = UGCClientInstance
472 self.xcache = {}
473
478
480 if repository not in self.xcache:
481 self.xcache[repository] = {}
482 if type(obj) in (list, tuple,):
483 my_obj = obj[:]
484 elif type(obj) in (set, frozenset, dict,):
485 my_obj = obj.copy()
486 else:
487 my_obj = obj
488 self.xcache[repository][item] = my_obj
489
496
499
502
504 return self._get_downloads_cache_dir(repository)
505
507 return self._get_alldocs_cache_dir(repository)+"/"+pkgkey
508
511
514
517
520
523
525 return 'get_package_alldocs_cache_'+repository
526
528 cache_file = os.path.join(etpConst['dumpstoragedir'], self._get_store_cache_file(iddoc, repository, doc_url))
529 cache_dir = os.path.dirname(cache_file)
530
531 try:
532 if not os.path.isdir(cache_dir):
533 os.makedirs(cache_dir, 0o775)
534 if etpConst['entropygid'] != None:
535 const_setup_perms(cache_dir, etpConst['entropygid'])
536 except OSError:
537 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot setup cache directory"), cache_dir,))
538 if not os.access(cache_dir, os.W_OK):
539 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot write to cache directory"), cache_dir,))
540
541 if os.path.isfile(cache_file) or os.path.islink(cache_file):
542 try:
543 os.remove(cache_file)
544 except OSError:
545 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot remove cache file"), cache_file,))
546
547 fetcher = self.Service.Entropy.urlFetcher(doc_url, cache_file, resume = False)
548 rc = fetcher.download()
549 if rc in ("-1", "-2", "-3", "-4"): return None
550 if not os.path.isfile(cache_file): return None
551
552 try:
553 os.chmod(cache_file, 0o664)
554 if etpConst['entropygid'] != None:
555 os.chown(cache_file, -1, etpConst['entropygid'])
556 except OSError:
557 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot write to cache file"), cache_file,))
558
559 del fetcher
560 return cache_file
561
563 cache_file = os.path.join(etpConst['dumpstoragedir'], self._get_store_cache_file(iddoc, repository, doc_url))
564 if os.path.isfile(cache_file) and os.access(cache_file, os.R_OK):
565 return cache_file
566
574
582
585
590
595
600
606
608 cache_key = self._get_vote_cache_key(repository)
609 cached = self._get_live_cache_item(repository, cache_key)
610 if cached != None:
611 return cached
612 with self.CacheLock:
613 cache_file = self._get_vote_cache_file(repository)
614 try:
615 data = self.dumpTools.loadobj(cache_file)
616 if data != None:
617 self._set_live_cache_item(repository, cache_key, data)
618 except (IOError, EOFError, OSError):
619 data = None
620 return data
621
623 cache_key = self._get_downloads_cache_key(repository)
624 cached = self._get_live_cache_item(repository, cache_key)
625 if cached != None:
626 return cached
627 with self.CacheLock:
628 cache_file = self._get_downloads_cache_file(repository)
629 try:
630 data = self.dumpTools.loadobj(cache_file)
631 if data != None:
632 self._set_live_cache_item(repository, cache_key, data)
633 except (IOError, EOFError, OSError):
634 data = None
635 return data
636
638 cache_key = self._get_alldocs_cache_key(repository)
639 cached = self._get_live_cache_item(repository, cache_key)
640 if isinstance(cached, dict):
641 if pkgkey in cached: return cached[pkgkey]
642 else:
643 cached = {}
644 with self.CacheLock:
645 cache_file = self._get_alldocs_cache_file(pkgkey, repository)
646 try:
647 data = self.dumpTools.loadobj(cache_file)
648 if data != None:
649 cached[pkgkey] = data
650 self._set_live_cache_item(repository, cache_key, cached)
651 except (IOError, EOFError, OSError):
652 data = None
653 return data
654
659
661 with self.CacheLock:
662 self._clear_live_cache_item(repository, self._get_downloads_cache_key(repository))
663 self.dumpTools.dumpobj(self._get_downloads_cache_file(repository), down_dict)
664
666 with self.CacheLock:
667 self._clear_live_cache_item(repository, self._get_alldocs_cache_key(repository))
668 self.dumpTools.dumpobj(self._get_alldocs_cache_file(pkgkey, repository), alldocs_dict)
669
679
692