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 from __future__ import with_statement
14 import os
15 from entropy.core import Singleton
16 from entropy.exceptions import *
17 from entropy.const import etpConst, etpCache, const_setup_file, const_setup_perms
18 from entropy.i18n import _
19
21
22 ssl_connection = True
23 - def __init__(self, EquoInstance, quiet = True, show_progress = False):
24
25 from entropy.client.interfaces import Client as Cl
26 if not isinstance(EquoInstance,Cl):
27 mytxt = _("A valid Client based instance is needed")
28 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,))
29
30 import socket, threading
31 self.socket, self.threading = socket, threading
32 import struct
33 self.struct = struct
34 self.Entropy = EquoInstance
35 self.store = AuthStore()
36 self.quiet = quiet
37 self.show_progress = show_progress
38 self.UGCCache = Cache(self)
39 self.TxLocks = {}
40
64
76
78
79 aware = self.UGCCache._get_live_cache_item(repository, 'is_repository_eapi3_aware')
80 if aware != None:
81 return aware
82
83 srv = self.get_service_connection(repository, check = False, timeout = 3)
84 if srv == None:
85 aware = False
86 else:
87 session = srv.open_session()
88 if session != None:
89 srv.close_session(session)
90 srv.disconnect()
91 aware = True
92 else:
93 aware = False
94
95 self.UGCCache._set_live_cache_item(repository, 'is_repository_eapi3_aware', aware)
96 return aware
97
100
103
104 - def do_login(self, repository, force = False):
105
106 login_data = self.read_login(repository)
107 if (login_data != None) and not force:
108 return True,_('ok')
109
110 aware = self.is_repository_eapi3_aware(repository)
111 if not aware:
112 return False,_('repository does not support EAPI3')
113
114 def fake_callback(*args,**kwargs):
115 return True
116
117 attempts = 3
118 while attempts:
119
120
121 input_params = [
122 ('username',_('Username'),fake_callback,False),
123 ('password',_('Password'),fake_callback,True)
124 ]
125 login_data = self.Entropy.inputBox(
126 "%s %s %s" % (_('Please login against'),repository,_('repository'),),
127 input_params,
128 cancel_button = True
129 )
130 if not login_data:
131 return False,_('login abort')
132
133
134 srv = self.get_service_connection(repository)
135 if srv == None:
136 return False,_('connection issues')
137 session = srv.open_session()
138 login_status, login_msg = srv.CmdInterface.service_login(login_data['username'], login_data['password'], session)
139 if not login_status:
140 srv.close_session(session)
141 srv.disconnect()
142 self.Entropy.askQuestion("%s: %s" % (_("Access denied. Login failed"),login_msg,), responses = ["Ok"])
143 attempts -= 1
144 continue
145
146
147 srv.close_session(session)
148 srv.disconnect()
149 rc = self.Entropy.askQuestion(_("Login successful. Do you want to save these credentials ?"))
150 save = False
151 if rc == "Yes": save = True
152 self.store.store_login(login_data['username'], login_data['password'], repository, save = save)
153 return True,_('ok')
154
155
156 - def login(self, repository, force = False):
163
164
165 - def logout(self, repository):
167
168 - def do_cmd(self, repository, login_required, func, args, kwargs):
169
170 if not self.TxLocks.has_key(repository):
171 self.TxLocks[repository] = self.threading.Lock()
172
173 with self.TxLocks[repository]:
174
175 if login_required:
176 status, err_msg = self.do_login(repository)
177 if not status:
178 return False,err_msg
179
180 srv = self.get_service_connection(repository)
181 if srv == None:
182 return False,'no connection'
183 session = srv.open_session()
184 if session == None:
185 return False,'no session'
186 args.insert(0,session)
187
188 if login_required:
189 stored_pass = False
190 while 1:
191
192 login_data = self.read_login(repository)
193 if login_data == None:
194 status, msg = self.login(repository)
195 if not status: return status, msg
196 username, password = self.read_login(repository)
197 else:
198 stored_pass = True
199 username, password = login_data
200 logged, error = srv.CmdInterface.service_login(username, password, session)
201 if not logged:
202 if stored_pass:
203 stored_pass = False
204 self.remove_login(repository)
205 continue
206 srv.close_session(session)
207 srv.disconnect()
208 return logged, error
209 break
210
211 try:
212 cmd_func = getattr(srv.CmdInterface, func)
213 except AttributeError:
214 return False, 'local function not available'
215 rslt = cmd_func(*args,**kwargs)
216 try:
217 srv.close_session(session)
218 srv.disconnect()
219 except ConnectionError:
220 return False, 'no connection'
221
222 return rslt
223
226
229
231 return self.do_cmd(repository, False, "ugc_get_documents_by_identifiers", [identifiers], {})
232
236
240
244
245 - def add_vote(self, repository, pkgkey, vote):
246 data = self.do_cmd(repository, True, "ugc_do_vote", [pkgkey, vote], {})
247 if isinstance(data,tuple): voted, add_err_msg = data
248 else: return False,'wrong server answer'
249 if voted: self.get_vote(repository, pkgkey)
250 return voted, add_err_msg
251
252 - def get_vote(self, repository, pkgkey):
253 vote, err_msg = self.do_cmd(repository, False, "ugc_get_vote", [pkgkey], {})
254 if isinstance(vote,float):
255 mydict = {pkgkey: vote}
256 self.UGCCache.update_vote_cache(repository, mydict)
257 return vote, err_msg
258
264
267
269 data = self.do_cmd(repository, False, "ugc_get_downloads", [pkgkey], {})
270 if isinstance(data,tuple): downloads, err_msg = data
271 else: return False,'wrong server answer'
272 if downloads:
273 mydict = {pkgkey: downloads}
274 self.UGCCache.update_downloads_cache(repository, mydict)
275 return downloads, err_msg
276
282
284 return self.do_cmd(repository, False, "ugc_do_download_stats", [pkgkeys], {})
285
286 - def send_file(self, repository, pkgkey, file_path, title, description, keywords):
289
293
294 - def send_image(self, repository, pkgkey, image_path, title, description, keywords):
297
301
302 - def send_youtube_video(self, repository, pkgkey, video_path, title, description, keywords):
305
309
310 - def get_docs(self, repository, pkgkey):
311 data = self.do_cmd(repository, False, "ugc_get_docs", [pkgkey], {})
312 if isinstance(data,tuple): docs_data, err_msg = data
313 else: return False,'wrong server answer'
314 if err_msg == 'ok':
315 self.UGCCache.update_alldocs_cache(pkgkey, repository, docs_data)
316 return docs_data, err_msg
317
319 if ugc_type == etpConst['ugc_doctypes']['generic_file']:
320 return self.send_file(repository, pkgkey, data, title, description, keywords)
321 elif ugc_type == etpConst['ugc_doctypes']['image']:
322 return self.send_image(repository, pkgkey, data, title, description, keywords)
323 elif ugc_type == etpConst['ugc_doctypes']['youtube_video']:
324 return self.send_youtube_video(repository, pkgkey, data, title, description, keywords)
325 elif ugc_type == etpConst['ugc_doctypes']['comments']:
326 return self.add_comment(repository, pkgkey, description, title, keywords)
327 return None,'type not supported locally'
328
339
342
343
345
346 access_file = etpConst['ugc_accessfile']
348
349 from xml.dom import minidom
350 from xml.parsers import expat
351 self.expat = expat
352 self.minidom = minidom
353 self.setup_store_paths()
354 try:
355 self.setup_permissions()
356 except IOError:
357 pass
358 self.store = {}
359 try:
360 self.xmldoc = self.minidom.parse(self.access_file)
361 except (self.expat.ExpatError,IOError,):
362 self.xmldoc = None
363 if self.xmldoc != None:
364 try:
365 self.parse_document()
366 except self.expat.ExpatError:
367 self.xmldoc = None
368 self.store = {}
369
371 myhome = os.getenv("HOME")
372 if myhome != None:
373 if os.path.isdir(myhome) and os.access(myhome,os.W_OK):
374 self.access_file = os.path.join(myhome,".config/entropy",
375 os.path.basename(self.access_file))
376 self.access_dir = os.path.dirname(self.access_file)
377
379 if not os.path.isdir(self.access_dir):
380 os.makedirs(self.access_dir)
381 if not os.path.isfile(self.access_file):
382 f = open(self.access_file, "w")
383 f.close()
384 gid = etpConst['entropygid']
385 if gid is None:
386 gid = 0
387
388 try:
389 const_setup_file(self.access_dir, gid, 0700)
390 except OSError:
391 pass
392 try:
393 const_setup_file(self.access_file, gid, 0600)
394 except OSError:
395 pass
396
398 self.store.clear()
399 store = self.xmldoc.getElementsByTagName("store")[0]
400 repositories = store.getElementsByTagName("repository")
401 for repository in repositories:
402 repoid = repository.getAttribute("id")
403 if not repoid: continue
404 username = repository.getElementsByTagName("username")[0].firstChild.data.strip()
405 password = repository.getElementsByTagName("password")[0].firstChild.data.strip()
406 self.store[repoid] = {'username': username, 'password': password}
407
408 - def store_login(self, username, password, repository, save = True):
409 self.store[repository] = {'username': username, 'password': password}
410 if save:
411 self.save_store()
412
414
415 self.xmldoc = self.minidom.Document()
416 store = self.xmldoc.createElement("store")
417
418 for repository in self.store:
419 repo = self.xmldoc.createElement("repository")
420 repo.setAttribute('id',repository)
421
422 username = self.xmldoc.createElement("username")
423 username_value = self.xmldoc.createTextNode(self.store[repository]['username'])
424 username.appendChild(username_value)
425 repo.appendChild(username)
426
427 password = self.xmldoc.createElement("password")
428 password_value = self.xmldoc.createTextNode(self.store[repository]['password'])
429 password.appendChild(password_value)
430 repo.appendChild(password)
431 store.appendChild(repo)
432
433 self.xmldoc.appendChild(store)
434 f = open(self.access_file,"w")
435 f.writelines(self.xmldoc.toprettyxml(indent=" "))
436 f.flush()
437 f.close()
438 self.setup_permissions()
439 self.parse_document()
440
446
450
452
454
455 if not isinstance(UGCClientInstance,Client):
456 mytxt = _("A valid UGC Client interface based instance is needed")
457 raise IncorrectParameter("IncorrectParameter: %s" % (mytxt,))
458
459 import threading
460 import entropy.dump as dumpTools
461 self.CacheLock = threading.Lock()
462 self.dumpTools = dumpTools
463 self.Service = UGCClientInstance
464 self.xcache = {}
465
470
472 if repository not in self.xcache:
473 self.xcache[repository] = {}
474 if type(obj) in (list,tuple,):
475 my_obj = obj[:]
476 elif type(obj) in (set,frozenset,dict,):
477 my_obj = obj.copy()
478 else:
479 my_obj = obj
480 self.xcache[repository][item] = my_obj
481
488
491
494
496 return self._get_downloads_cache_dir(repository)
497
499 return self._get_alldocs_cache_dir(repository)+"/"+pkgkey
500
503
506
509
512
515
517 return 'get_package_alldocs_cache_'+repository
518
520 cache_file = os.path.join(etpConst['dumpstoragedir'],self._get_store_cache_file(iddoc, repository, doc_url))
521 cache_dir = os.path.dirname(cache_file)
522
523 try:
524 if not os.path.isdir(cache_dir):
525 os.makedirs(cache_dir,0775)
526 if etpConst['entropygid'] != None:
527 const_setup_perms(cache_dir,etpConst['entropygid'])
528 except OSError:
529 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot setup cache directory"),cache_dir,))
530 if not os.access(cache_dir,os.W_OK):
531 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot write to cache directory"),cache_dir,))
532
533 if os.path.isfile(cache_file) or os.path.islink(cache_file):
534 try:
535 os.remove(cache_file)
536 except OSError:
537 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot remove cache file"),cache_file,))
538
539 fetcher = self.Service.Entropy.urlFetcher(doc_url, cache_file, resume = False)
540 rc = fetcher.download()
541 if rc in ("-1","-2","-3","-4"): return None
542 if not os.path.isfile(cache_file): return None
543
544 try:
545 os.chmod(cache_file,0664)
546 if etpConst['entropygid'] != None:
547 os.chown(cache_file,-1,etpConst['entropygid'])
548 except OSError:
549 raise PermissionDenied("PermissionDenied: %s %s" % (_("Cannot write to cache file"),cache_file,))
550
551 del fetcher
552 return cache_file
553
555 cache_file = os.path.join(etpConst['dumpstoragedir'],self._get_store_cache_file(iddoc, repository, doc_url))
556 if os.path.isfile(cache_file) and os.access(cache_file,os.R_OK):
557 return cache_file
558
566
574
577
582
587
592
598
600 cache_key = self._get_vote_cache_key(repository)
601 cached = self._get_live_cache_item(repository, cache_key)
602 if cached != None:
603 return cached
604 with self.CacheLock:
605 cache_file = self._get_vote_cache_file(repository)
606 try:
607 data = self.dumpTools.loadobj(cache_file)
608 if data != None:
609 self._set_live_cache_item(repository, cache_key, data)
610 except (IOError,EOFError,OSError):
611 data = None
612 return data
613
615 cache_key = self._get_downloads_cache_key(repository)
616 cached = self._get_live_cache_item(repository, cache_key)
617 if cached != None:
618 return cached
619 with self.CacheLock:
620 cache_file = self._get_downloads_cache_file(repository)
621 try:
622 data = self.dumpTools.loadobj(cache_file)
623 if data != None:
624 self._set_live_cache_item(repository, cache_key, data)
625 except (IOError,EOFError,OSError):
626 data = None
627 return data
628
630 cache_key = self._get_alldocs_cache_key(repository)
631 cached = self._get_live_cache_item(repository, cache_key)
632 if isinstance(cached,dict):
633 if cached.has_key(pkgkey): return cached[pkgkey]
634 else:
635 cached = {}
636 with self.CacheLock:
637 cache_file = self._get_alldocs_cache_file(pkgkey, repository)
638 try:
639 data = self.dumpTools.loadobj(cache_file)
640 if data != None:
641 cached[pkgkey] = data
642 self._set_live_cache_item(repository, cache_key, cached)
643 except (IOError,EOFError,OSError):
644 data = None
645 return data
646
651
653 with self.CacheLock:
654 self._clear_live_cache_item(repository, self._get_downloads_cache_key(repository))
655 self.dumpTools.dumpobj(self._get_downloads_cache_file(repository), down_dict)
656
658 with self.CacheLock:
659 self._clear_live_cache_item(repository, self._get_alldocs_cache_key(repository))
660 self.dumpTools.dumpobj(self._get_alldocs_cache_file(pkgkey, repository), alldocs_dict)
661
671
684