a lot of work is taking place, something could be a little bit broken, please be patient
git-svn-id: http://svn.sabayonlinux.org/projects/entropy/trunk@600 cd1c1023-2f26-0410-ae45-c471fc1f0318
This commit is contained in:
@@ -1,19 +1,17 @@
|
||||
TODO list:
|
||||
- reagent: complete smartapps section
|
||||
CLIENT:
|
||||
- regenerate client database from / content
|
||||
- triggers:
|
||||
- alternatives eclass
|
||||
:: sqlite needs it (/usr/bin/lemon)
|
||||
|
||||
- add external triggerable hooks support
|
||||
- branch reorganization based on the release:
|
||||
- install only looks inside the configured branch
|
||||
- move branches to version naming (3.5, 3.6, 3.7) instead of using stable, unstable...
|
||||
- atom|tag ?
|
||||
|
||||
- add scripts directory that contains pre/post-install-remove scripts that is synced using activator
|
||||
- include more triggers
|
||||
- optimize database interface
|
||||
- add multithreading support
|
||||
- limit items/size in on-disk cache?
|
||||
|
||||
Project Status:
|
||||
- reagent: complete. Stabilization mode.
|
||||
|
||||
+1
-1
@@ -347,7 +347,7 @@ def scanfs(quiet = True, dcache = True):
|
||||
scandata[counter] = mydict.copy()
|
||||
|
||||
try:
|
||||
if (not quiet): print_info("("+blue(str(counter))+") "+red(" file: ")+filepath)
|
||||
if (not quiet): print_info("("+blue(str(counter))+") "+red(" file: ")+os.path.dirname(filepath)+"/"+os.path.basename(filepath)[10:])
|
||||
except:
|
||||
pass # possible encoding issues
|
||||
# store data
|
||||
|
||||
+3
-4
@@ -40,9 +40,7 @@ def print_help():
|
||||
print_info(" \t"+blue("status")+brown("\t\t show respositories status"))
|
||||
|
||||
print_info(" \t"+blue("search")+brown("\t\t search a package trough repositories"))
|
||||
print_info(" \t"+blue("world")+brown("\t\t update packages to the latest version"))
|
||||
print_info(" \t\t"+darkgreen("update")+red("\t\t\t update the system in the current branch"))
|
||||
print_info(" \t\t"+darkgreen("upgrade")+red("\t\t\t upgrade the system to the latest branch"))
|
||||
print_info(" \t"+blue("world")+brown("\t\t update system with the latest available packages"))
|
||||
print_info(" \t\t"+red("--ask")+"\t\t\t ask before making any changes")
|
||||
print_info(" \t\t"+red("--fetch")+"\t\t\t just download packages without doing the install")
|
||||
print_info(" \t\t"+red("--pretend")+"\t\t just show what would be done")
|
||||
@@ -85,12 +83,13 @@ def print_help():
|
||||
|
||||
print_info(" \t"+blue("database")+brown("\t handle installed packages database"))
|
||||
print_info(" \t\t"+darkgreen("generate")+red("\t\t generate installed packages database"))
|
||||
print_info(" \t\t"+darkgreen("resurrect")+red("\t\t generate installed packages database using system content [last hope]"))
|
||||
print_info(" \t\t"+darkgreen("depends")+red("\t\t\t to regenerate/generate depends caching table"))
|
||||
print_info(" \t"+blue("cache")+brown("\t\t handle Equo on-disk cache"))
|
||||
print_info(" \t\t"+darkgreen("clean")+red("\t\t\t clean on-disk cache"))
|
||||
print_info(" \t\t"+darkgreen("generate")+red("\t\t generate on-disk cache (to speed up Equo)"))
|
||||
print_info(" \t\t"+red("--quiet")+"\t\t\t show less details (useful for scripting)")
|
||||
print_info(" \t\t"+red("--verbose")+"\t\t\t show more details about what's going on")
|
||||
print_info(" \t\t"+red("--verbose")+"\t\t show more details about what's going on")
|
||||
print_info(" \t"+blue("cleanup")+brown("\t\t remove downloaded packages and clean temporary directories (not cache)"))
|
||||
print_info(" \t"+blue("--info")+brown("\t\t show system information"))
|
||||
|
||||
|
||||
+113
-22
@@ -328,11 +328,11 @@ def fetchRepositoryIfNotAvailable(reponame):
|
||||
'''
|
||||
@description: matches the package that user chose, using dbconnection.atomMatch searching in all available repositories.
|
||||
@input atom: user choosen package name
|
||||
@output: the matched selection, list: [package id,repository name] | if nothing found, returns: [ -1,1 ]
|
||||
@output: the matched selection, list: [package id,repository name] | if nothing found, returns: ( -1,1 )
|
||||
@ exit errors:
|
||||
-1 => repository cannot be fetched online
|
||||
'''
|
||||
def atomMatch(atom, caseSentitive = True, matchSlot = None, matchBranches = [], xcache = True):
|
||||
def atomMatch(atom, caseSentitive = True, matchSlot = None, matchBranches = (), xcache = True): # no one seems to use matchBranches :D
|
||||
|
||||
#print atom
|
||||
if xcache:
|
||||
@@ -356,7 +356,7 @@ def atomMatch(atom, caseSentitive = True, matchSlot = None, matchBranches = [],
|
||||
dbconn = openRepositoryDatabase(repo, xcache = xcache)
|
||||
|
||||
# search
|
||||
query = dbconn.atomMatch(atom, caseSentitive, matchSlot, matchBranches = matchBranches)
|
||||
query = dbconn.atomMatch(atom, caseSensitive = caseSentitive, matchSlot = matchSlot, matchBranches = matchBranches)
|
||||
if query[1] == 0:
|
||||
# package found, add to our dictionary
|
||||
repoResults[repo] = query[0]
|
||||
@@ -574,11 +574,11 @@ def getDependencies(packageInfo):
|
||||
dbconn = openRepositoryDatabase(reponame)
|
||||
|
||||
# retrieve dependencies
|
||||
depend = dbconn.retrieveDependencies(idpackage)
|
||||
depend = set(dbconn.retrieveDependencies(idpackage))
|
||||
# and conflicts
|
||||
conflicts = dbconn.retrieveConflicts(idpackage)
|
||||
conflicts = set(dbconn.retrieveConflicts(idpackage))
|
||||
for x in conflicts:
|
||||
depend.append("!"+x)
|
||||
depend.add("!"+x)
|
||||
dbconn.closeDB()
|
||||
|
||||
''' caching '''
|
||||
@@ -1476,7 +1476,7 @@ def installPackageIntoGentooDatabase(infoDict,packageFile):
|
||||
#print _portage_getInstalledAtom(key)
|
||||
atomsfound = set()
|
||||
for xdir in os.listdir(portDbDir):
|
||||
if xdir == (infoDict['category']):
|
||||
if (xdir == infoDict['category']):
|
||||
for ydir in os.listdir(portDbDir+"/"+xdir):
|
||||
if (key == dep_getkey(xdir+"/"+ydir)):
|
||||
atomsfound.add(xdir+"/"+ydir)
|
||||
@@ -1574,7 +1574,6 @@ def installPackageIntoDatabase(idpackage, repository):
|
||||
clientDbconn.addDependRelationToDependsTable(iddep,match[0])
|
||||
|
||||
except:
|
||||
print "DEBUG!!! dependstable not found"
|
||||
clientDbconn.regenerateDependsTable()
|
||||
|
||||
clientDbconn.closeDB()
|
||||
@@ -1671,13 +1670,8 @@ def package(options):
|
||||
rc = 127
|
||||
|
||||
elif (options[0] == "world"):
|
||||
myopts = options[1:]
|
||||
doupgrade = False
|
||||
for opt in myopts:
|
||||
if opt == "upgrade":
|
||||
doupgrade = True
|
||||
loadCaches()
|
||||
rc, status = worldUpdate(ask = equoRequestAsk, pretend = equoRequestPretend, verbose = equoRequestVerbose, onlyfetch = equoRequestOnlyFetch, upgrade = doupgrade)
|
||||
rc, status = worldUpdate(ask = equoRequestAsk, pretend = equoRequestPretend, verbose = equoRequestVerbose, onlyfetch = equoRequestOnlyFetch)
|
||||
|
||||
elif (options[0] == "remove"):
|
||||
if len(myopts) > 0:
|
||||
@@ -1706,9 +1700,10 @@ def database(options):
|
||||
|
||||
if (options[0] == "generate"):
|
||||
|
||||
print_warning(bold("####### ATTENTION -> ")+red("The installed package database will be regenerated, this will take a LOT of time."))
|
||||
print_warning(bold("####### ATTENTION -> ")+red("The installed package database will be regenerated."))
|
||||
print_warning(bold("####### ATTENTION -> ")+red("Sabayon Linux Officially Repository MUST be on top of the repositories list in ")+etpConst['repositoriesconf'])
|
||||
print_warning(bold("####### ATTENTION -> ")+red("This method is only used for testing at the moment and you need Portage installed. Don't worry about Portage warnings."))
|
||||
print_warning(bold("####### ATTENTION -> ")+red("Please use this function ONLY if you are using an Entropy-enabled Sabayon distribution."))
|
||||
rc = askquestion(" Can I continue ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
@@ -1718,6 +1713,11 @@ def database(options):
|
||||
rc = askquestion(" Do you even know what you're doing ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
|
||||
# clean caches
|
||||
import cacheTools
|
||||
cacheTools.cleanCache(quiet = True)
|
||||
const_resetCache()
|
||||
|
||||
# ok, he/she knows it... hopefully
|
||||
# if exist, copy old database
|
||||
@@ -1791,6 +1791,101 @@ def database(options):
|
||||
|
||||
clientDbconn.closeDB()
|
||||
|
||||
elif (options[0] == "resurrect"):
|
||||
|
||||
print_warning(bold("####### ATTENTION -> ")+red("The installed package database will be resurrected, this will take a LOT of time."))
|
||||
print_warning(bold("####### ATTENTION -> ")+red("Please use this function ONLY if you are using an Entropy-enabled Sabayon distribution."))
|
||||
rc = askquestion(" Can I continue ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
rc = askquestion(" Are you REALLY sure ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
rc = askquestion(" Do you even know what you're doing ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
|
||||
# clean caches
|
||||
import cacheTools
|
||||
cacheTools.cleanCache(quiet = True)
|
||||
const_resetCache()
|
||||
|
||||
# ok, he/she knows it... hopefully
|
||||
# if exist, copy old database
|
||||
print_info(red(" @@ ")+blue("Creating backup of the previous database, if exists.")+red(" @@"))
|
||||
newfile = backupClientDatabase()
|
||||
if (newfile):
|
||||
print_info(red(" @@ ")+blue("Previous database copied to file ")+newfile+red(" @@"))
|
||||
|
||||
# Now reinitialize it
|
||||
print_info(darkred(" Initializing the new database at "+bold(etpConst['etpdatabaseclientfilepath'])), back = True)
|
||||
clientDbconn = openClientDatabase()
|
||||
clientDbconn.initializeDatabase()
|
||||
print_info(darkgreen(" Database reinitialized correctly at "+bold(etpConst['etpdatabaseclientfilepath'])))
|
||||
|
||||
print_info(red(" Collecting installed files. Writing: "+etpConst['packagestmpfile']+" Please wait..."), back = True)
|
||||
|
||||
# since we use find, see if it's installed
|
||||
find = os.system("which find &> /dev/null")
|
||||
if find != 0:
|
||||
print_error(darkred("Attention: ")+red("You must have 'find' installed!"))
|
||||
return
|
||||
# spawn process
|
||||
if os.path.isfile(etpConst['packagestmpfile']):
|
||||
os.remove(etpConst['packagestmpfile'])
|
||||
os.system("find / -mount 1> "+etpConst['packagestmpfile'])
|
||||
if not os.path.isfile(etpConst['packagestmpfile']):
|
||||
print_error(darkred("Attention: ")+red("find couldn't generate an output file."))
|
||||
return
|
||||
|
||||
f = open(etpConst['packagestmpfile'],"r")
|
||||
# creating list of files
|
||||
filelist = set()
|
||||
file = f.readline().strip()
|
||||
while file:
|
||||
filelist.add(file)
|
||||
file = f.readline().strip()
|
||||
f.close()
|
||||
entries = len(filelist)
|
||||
|
||||
print_info(red(" Found "+str(entries)+" files on the system. Assigning packages..."))
|
||||
atoms = {}
|
||||
pkgsfound = set()
|
||||
|
||||
for repo in etpRepositories:
|
||||
print_info(red(" Matching in repository: ")+etpRepositories[repo]['description'])
|
||||
# get all idpackages
|
||||
dbconn = openRepositoryDatabase(repo)
|
||||
idpackages = dbconn.listAllIdpackages(branch = etpConst['branch'])
|
||||
count = str(len(idpackages))
|
||||
cnt = 0
|
||||
for idpackage in idpackages:
|
||||
cnt += 1
|
||||
idpackageatom = dbconn.retrieveAtom(idpackage)
|
||||
print_info(" ("+str(cnt)+"/"+count+")"+red(" Matching files from packages..."), back = True)
|
||||
# content
|
||||
content = dbconn.retrieveContent(idpackage)
|
||||
for file in content:
|
||||
if file in filelist:
|
||||
pkgsfound.add((idpackage,repo))
|
||||
atoms[(idpackage,repo)] = idpackageatom
|
||||
filelist.difference_update(set(content))
|
||||
break
|
||||
dbconn.closeDB()
|
||||
|
||||
print_info(red(" Found "+str(len(pkgsfound))+" packages. Filling database..."))
|
||||
count = str(len(pkgsfound))
|
||||
cnt = 0
|
||||
#XXXos.remove(etpConst['packagestmpfile'])
|
||||
|
||||
for pkgfound in pkgsfound:
|
||||
cnt += 1
|
||||
print_info(" ("+str(cnt)+"/"+count+") "+red("Adding: ")+atoms[pkgfound], back = True)
|
||||
installPackageIntoDatabase(pkgfound[0],pkgfound[1])
|
||||
|
||||
print_info(red(" Database resurrected successfully."))
|
||||
print_warning(red(" Keep in mind that virtual/meta packages couldn't be matched. They don't own any files."))
|
||||
|
||||
elif (options[0] == "depends"):
|
||||
print_info(red(" Regenerating depends caching table..."))
|
||||
clientDbconn = openClientDatabase()
|
||||
@@ -1917,18 +2012,14 @@ def openClientDatabase(xcache = True):
|
||||
#
|
||||
|
||||
|
||||
def worldUpdate(ask = False, pretend = False, verbose = False, onlyfetch = False, upgrade = False):
|
||||
def worldUpdate(ask = False, pretend = False, verbose = False, onlyfetch = False):
|
||||
|
||||
# check if I am root
|
||||
if (not isRoot()) and (not pretend):
|
||||
print_error(red("You must run this function as superuser."))
|
||||
return 1,-1
|
||||
|
||||
if (not upgrade):
|
||||
branches = [etpConst['branch']]
|
||||
else:
|
||||
branches = etpConst['branches']
|
||||
|
||||
branches = (etpConst['branch'],)
|
||||
updateList = []
|
||||
fineList = set()
|
||||
removedList = set()
|
||||
@@ -1982,7 +2073,7 @@ def worldUpdate(ask = False, pretend = False, verbose = False, onlyfetch = False
|
||||
|
||||
if (updateList):
|
||||
print_info(red(" @@ ")+blue("Calculating queue..."))
|
||||
rc = installPackages(atomsdata = updateList, ask = ask, pretend = pretend, verbose = verbose, onlyfetch = onlyfetch, deepdeps = upgrade)
|
||||
rc = installPackages(atomsdata = updateList, ask = ask, pretend = pretend, verbose = verbose, onlyfetch = onlyfetch)
|
||||
if rc[0] != 0:
|
||||
return rc
|
||||
else:
|
||||
|
||||
+21
-14
@@ -27,7 +27,7 @@ from entropyConstants import *
|
||||
from clientConstants import *
|
||||
from outputTools import *
|
||||
from databaseTools import etpDatabase
|
||||
from entropyTools import dep_getkey
|
||||
from entropyTools import dep_getkey, dep_getslot, remove_slot
|
||||
from equoTools import openClientDatabase, openRepositoryDatabase, printPackageInfo, generateDependsTree, atomMatch # move them away?
|
||||
|
||||
########################################################
|
||||
@@ -101,7 +101,9 @@ def searchInstalledPackages(packages, idreturn = False, quiet = False):
|
||||
dataInfo = [] # when idreturn is True
|
||||
|
||||
for package in packages:
|
||||
result = clientDbconn.searchPackages(package)
|
||||
slot = dep_getslot(package)
|
||||
package = remove_slot(package)
|
||||
result = clientDbconn.searchPackages(package, slot = slot)
|
||||
if (result):
|
||||
# print info
|
||||
if (not idreturn) and (not quiet):
|
||||
@@ -132,6 +134,8 @@ def searchBelongs(files, idreturn = False, quiet = False):
|
||||
clientDbconn = openClientDatabase()
|
||||
dataInfo = [] # when idreturn is True
|
||||
|
||||
results = {}
|
||||
flatresults = {}
|
||||
for file in files:
|
||||
like = False
|
||||
if file.find("*") != -1:
|
||||
@@ -139,9 +143,18 @@ def searchBelongs(files, idreturn = False, quiet = False):
|
||||
out = re.subn("\*","%",file)
|
||||
file = out[0]
|
||||
like = True
|
||||
result = clientDbconn.searchBelongs(file, like)
|
||||
if (result):
|
||||
results[file] = set()
|
||||
idpackages = clientDbconn.searchBelongs(file, like)
|
||||
for idpackage in idpackages:
|
||||
if not flatresults.get(idpackage):
|
||||
results[file].add(idpackage)
|
||||
flatresults[idpackage] = True
|
||||
|
||||
if (results):
|
||||
for result in results:
|
||||
# print info
|
||||
file = result
|
||||
result = results[result]
|
||||
if (not idreturn) and (not quiet):
|
||||
print_info(blue(" Keyword: ")+bold("\t"+file))
|
||||
print_info(blue(" Found: ")+bold("\t"+str(len(result)))+red(" entries"))
|
||||
@@ -466,10 +479,12 @@ def searchPackage(packages, idreturn = False):
|
||||
dbconn = openRepositoryDatabase(repo)
|
||||
dataInfo = [] # when idreturn is True
|
||||
for package in packages:
|
||||
result = dbconn.searchPackages(package)
|
||||
slot = dep_getslot(package)
|
||||
package = remove_slot(package)
|
||||
result = dbconn.searchPackages(package, slot = slot)
|
||||
|
||||
if (not result): # look for provide
|
||||
provide = dbconn.searchProvide(package)
|
||||
provide = dbconn.searchProvide(package, slot = slot)
|
||||
if (provide):
|
||||
result = [[provide[0],provide[1]]]
|
||||
|
||||
@@ -484,14 +499,6 @@ def searchPackage(packages, idreturn = False):
|
||||
idpackage = pkg[1]
|
||||
atom = pkg[0]
|
||||
branch = dbconn.retrieveBranch(idpackage)
|
||||
# does the package exist in the selected branch?
|
||||
if etpConst['branch'] != branch:
|
||||
# get branch name position in branches
|
||||
myBranchIndex = etpConst['branches'].index(etpConst['branch'])
|
||||
foundBranchIndex = etpConst['branches'].index(branch)
|
||||
if foundBranchIndex > myBranchIndex:
|
||||
# package found in branch more unstable than the selected one, for us, it does not exist
|
||||
continue
|
||||
if (idreturn):
|
||||
dataInfo.append([idpackage,repo])
|
||||
else:
|
||||
|
||||
+71
-6
@@ -64,6 +64,9 @@ def postinstall(pkgdata):
|
||||
if pkgdata['category']+"/"+pkgdata['name'] == "dev-lang/python":
|
||||
functions.add("pythoninst")
|
||||
|
||||
if pkgdata['category']+"/"+pkgdata['name'] == "dev-db/sqlite":
|
||||
functions.add('sqliteinst')
|
||||
|
||||
# kde package ?
|
||||
if "kde" in pkgdata['eclasses']:
|
||||
functions.add("kbuildsycoca")
|
||||
@@ -79,6 +82,9 @@ def postinstall(pkgdata):
|
||||
functions.add('gconfinstallschemas')
|
||||
functions.add('gconfreload')
|
||||
|
||||
if pkgdata['name'] == "pygtk":
|
||||
functions.add('pygtksetup')
|
||||
|
||||
# prepare content
|
||||
mycnt = set(pkgdata['content'])
|
||||
|
||||
@@ -127,12 +133,26 @@ def postremove(pkgdata):
|
||||
|
||||
# opengl configuration
|
||||
if pkgdata['category'] == "x11-drivers":
|
||||
functions.add("openglsetup")
|
||||
functions.add("openglsetup_xorg")
|
||||
|
||||
# kde package ?
|
||||
if "kde" in pkgdata['eclasses']:
|
||||
functions.add("kbuildsycoca")
|
||||
|
||||
if pkgdata['name'] == "pygtk":
|
||||
functions.add('pygtkremove')
|
||||
|
||||
if pkgdata['category']+"/"+pkgdata['name'] == "dev-db/sqlite":
|
||||
functions.add('sqliteinst')
|
||||
|
||||
# python configuration
|
||||
if pkgdata['category']+"/"+pkgdata['name'] == "dev-lang/python":
|
||||
functions.add("pythoninst")
|
||||
|
||||
# fonts configuration
|
||||
if pkgdata['category'] == "media-fonts":
|
||||
functions.add("fontconfig")
|
||||
|
||||
# prepare content
|
||||
mycnt = set(pkgdata['removecontent'])
|
||||
|
||||
@@ -243,13 +263,18 @@ def kernelmod(pkgdata):
|
||||
update_moduledb(item)
|
||||
print_info(" "+brown("[POST] Running depmod..."))
|
||||
kos = [x for x in pkgdata['content'] if x.startswith("/lib/modules") and x.endswith(".ko")]
|
||||
run_depmod(kos, pkgdata['versiontag'])
|
||||
run_depmod()
|
||||
|
||||
def pythoninst(pkgdata):
|
||||
equoLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_NORMAL,"[POST] Configuring Python...")
|
||||
print_info(" "+brown("[POST] Configuring Python..."))
|
||||
python_update_symlink()
|
||||
|
||||
def sqliteinst(pkgdata):
|
||||
equoLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_NORMAL,"[POST] Configuring SQLite...")
|
||||
print_info(" "+brown("[POST] Configuring SQLite..."))
|
||||
sqlite_update_symlink()
|
||||
|
||||
def initdisable(pkgdata):
|
||||
mycnt = set(pkgdata['removecontent'])
|
||||
for file in mycnt:
|
||||
@@ -282,6 +307,16 @@ def openglsetup(pkgdata):
|
||||
equoLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_NORMAL,"[POST] Eselect NOT found, cannot run OpenGL trigger")
|
||||
print_info(" "+brown("[POST] Eselect NOT found, cannot run OpenGL trigger"))
|
||||
|
||||
def openglsetup_xorg(pkgdata):
|
||||
eselect = os.system("eselect opengl &> /dev/null")
|
||||
if eselect == 0:
|
||||
equoLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_NORMAL,"[POST] Reconfiguring OpenGL to fallback xorg-x11 ...")
|
||||
print_info(" "+brown("[POST] Reconfiguring OpenGL..."))
|
||||
os.system("eselect opengl set xorg-x11")
|
||||
else:
|
||||
equoLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_NORMAL,"[POST] Eselect NOT found, cannot run OpenGL trigger")
|
||||
print_info(" "+brown("[POST] Eselect NOT found, cannot run OpenGL trigger"))
|
||||
|
||||
# FIXME: this only supports grub (no lilo support)
|
||||
def addbootablekernel(pkgdata):
|
||||
kernels = [x for x in pkgdata['content'] if x.startswith("/boot/kernel-")]
|
||||
@@ -349,6 +384,7 @@ def gconfinstallschemas(pkgdata):
|
||||
gtest = os.system("which gconftool-2 &> /dev/null")
|
||||
if gtest == 0:
|
||||
schemas = [x for x in pkgdata['content'] if x.startswith("/etc/gconf/schemas") and x.endswith(".schemas")]
|
||||
print_info(" "+brown("[POST] Installing GConf2 schemas..."))
|
||||
for schema in schemas:
|
||||
os.system("""
|
||||
unset GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL
|
||||
@@ -356,6 +392,18 @@ def gconfinstallschemas(pkgdata):
|
||||
gconftool-2 --makefile-install-rule """+schema+""" 1>/dev/null
|
||||
""")
|
||||
|
||||
def pygtksetup(pkgdata):
|
||||
python_sym_files = [x for x in pkgdata['content'] if x.startswith("/usr/lib/python") and (x.endswith("pygtk.py-2.0") or x.endswith("pygtk.pth-2.0"))]
|
||||
for file in python_sym_files:
|
||||
if os.path.isfile(file):
|
||||
os.symlink(file,file[:-4])
|
||||
|
||||
def pygtkremove(pkgdata):
|
||||
python_sym_files = [x for x in pkgdata['content'] if x.startswith("/usr/lib/python") and (x.endswith("pygtk.py-2.0") or x.endswith("pygtk.pth-2.0"))]
|
||||
for file in python_sym_files:
|
||||
if os.path.isfile(file[:-4]):
|
||||
os.remove(file[:-4])
|
||||
|
||||
########################################################
|
||||
####
|
||||
## Internal functions
|
||||
@@ -479,10 +527,9 @@ def update_moduledb(item):
|
||||
@description: insert kernel object into kernel modules db
|
||||
@output: returns int() as exit status
|
||||
'''
|
||||
def run_depmod(files, kv):
|
||||
def run_depmod():
|
||||
if os.access('/sbin/depmod',os.X_OK):
|
||||
for file in files:
|
||||
rc = os.system('/sbin/depmod -a -v '+kv+' '+file+' &> /dev/null')
|
||||
os.system('/sbin/depmod -a &> /dev/null')
|
||||
return 0
|
||||
|
||||
'''
|
||||
@@ -493,11 +540,23 @@ def python_update_symlink():
|
||||
bins = [x for x in os.listdir("/usr/bin") if x.startswith("python2.")]
|
||||
versions = [x[6:] for x in bins]
|
||||
versions.sort()
|
||||
latest = versions[len(versions)-1]
|
||||
latest = versions[-1]
|
||||
os.system('ln -sf /usr/bin/python'+str(latest)+' /usr/bin/python')
|
||||
os.system('ln -sf /usr/bin/python'+str(latest)+' /usr/bin/python2')
|
||||
return 0
|
||||
|
||||
'''
|
||||
@description: update /usr/bin/lemon symlink
|
||||
@output: returns int() as exit status
|
||||
'''
|
||||
def sqlite_update_symlink():
|
||||
bins = [x for x in os.listdir("/usr/bin") if x.startswith("lemon-")]
|
||||
versions = [x[6:] for x in bins]
|
||||
versions.sort()
|
||||
latest = versions[-1]
|
||||
os.system('ln -sf /usr/bin/lemon-'+str(latest)+' /usr/bin/lemon')
|
||||
return 0
|
||||
|
||||
'''
|
||||
@description: shuts down selected init script, and remove from runlevel
|
||||
@output: returns int() as exit status
|
||||
@@ -521,6 +580,12 @@ def configure_boot_grub(kernel,initramfs):
|
||||
grub = open("/boot/grub/grub.conf","aw")
|
||||
# get boot dev
|
||||
boot_dev = get_grub_boot_dev()
|
||||
# test if entry has been already added
|
||||
grubtest = open("/boot/grub/grub.conf","r")
|
||||
content = grubtest.readlines()
|
||||
if "title="+etpConst['systemname']+" ("+os.path.basename(kernel)+")\n" in content:
|
||||
grubtest.close()
|
||||
return
|
||||
else:
|
||||
# create
|
||||
boot_dev = "(hd0,0)"
|
||||
|
||||
@@ -15,13 +15,13 @@ repository|sabayonlinux.org|Sabayon Linux Official Repository|http://svn.sabayon
|
||||
#
|
||||
# syntax for branch:
|
||||
#
|
||||
# branch: in Sabayon Linux there are two trees, stable and unstable. Stable packages are the ones of the latest stable release.
|
||||
# branch|name of the branch (no spaces!)
|
||||
# branch: in Sabayon Linux each release has its branch that is the version name (eg. 3.4, 3.5)
|
||||
# branch|actual branch string (no spaces!)
|
||||
#
|
||||
# example:
|
||||
# branch|stable
|
||||
# branch|3.5
|
||||
# or:
|
||||
# branch|unstable
|
||||
# branch|3.6
|
||||
#
|
||||
# Branch default setting
|
||||
branch|unstable
|
||||
|
||||
@@ -35,49 +35,6 @@ etpInstallTriggers = {}
|
||||
### structure same as above
|
||||
etpRemovalTriggers = {}
|
||||
|
||||
|
||||
# Client packages/database repositories
|
||||
# used by equo
|
||||
etpRepositories = {}
|
||||
etpRepositoriesOrder = []
|
||||
if os.path.isfile(etpConst['repositoriesconf']):
|
||||
f = open(etpConst['repositoriesconf'],"r")
|
||||
repositoriesconf = f.readlines()
|
||||
f.close()
|
||||
|
||||
for line in repositoriesconf:
|
||||
line = line.strip()
|
||||
# populate etpRepositories
|
||||
if (line.find("repository|") != -1) and (not line.startswith("#")) and (len(line.split("|")) == 5):
|
||||
reponame = line.split("|")[1]
|
||||
repodesc = line.split("|")[2]
|
||||
repopackages = line.split("|")[3]
|
||||
repodatabase = line.split("|")[4]
|
||||
if (repopackages.startswith("http://") or repopackages.startswith("ftp://")) and (repodatabase.startswith("http://") or repodatabase.startswith("ftp://")):
|
||||
etpRepositories[reponame] = {}
|
||||
etpRepositoriesOrder.append(reponame)
|
||||
etpRepositories[reponame]['description'] = repodesc
|
||||
etpRepositories[reponame]['packages'] = []
|
||||
for x in repopackages.split():
|
||||
etpRepositories[reponame]['packages'].append(x)
|
||||
etpRepositories[reponame]['database'] = repodatabase+"/"+etpConst['currentarch']
|
||||
etpRepositories[reponame]['dbpath'] = etpConst['etpdatabaseclientdir']+"/"+reponame+"/"+etpConst['currentarch']
|
||||
# initialize CONFIG_PROTECT - will be filled the first time the db will be opened
|
||||
etpRepositories[reponame]['configprotect'] = set()
|
||||
etpRepositories[reponame]['configprotectmask'] = set()
|
||||
elif (line.find("branch|") != -1) and (not line.startswith("#")) and (len(line.split("|")) == 2):
|
||||
branch = line.split("|")[1]
|
||||
etpConst['branch'] = branch
|
||||
if branch not in etpConst['branches']:
|
||||
etpConst['branches'].append(branch)
|
||||
if not os.path.isdir(etpConst['packagesbindir']+"/"+branch):
|
||||
if os.getuid() == 0:
|
||||
os.makedirs(etpConst['packagesbindir']+"/"+branch)
|
||||
else:
|
||||
print "ERROR: please run equo as root at least once or create: "+str(etpConst['packagesbindir']+"/"+branch)
|
||||
sys.exit(49)
|
||||
|
||||
|
||||
# equo section
|
||||
if (not os.path.isfile(etpConst['equoconf'])):
|
||||
print "ERROR: "+etpConst['equoconf']+" does not exist"
|
||||
|
||||
+266
-321
File diff suppressed because it is too large
Load Diff
@@ -389,8 +389,8 @@ etpConst = {
|
||||
'preinstallscript': "preinstall.sh", # used by the client to run some pre-install actions
|
||||
'postinstallscript': "postinstall.sh", # used by the client to run some post-install actions
|
||||
|
||||
'branches': ["stable","unstable"], # available branches, do not scramble!
|
||||
'branch': "unstable", # choosen branch
|
||||
'branches': ["3.5","2008"], # available branches, this only exists for the server part
|
||||
'branch': "3.5", # choosen branch
|
||||
'gentoo-compat': False, # Gentoo compatibility (/var/db/pkg + Portage availability)
|
||||
'filesystemdirs': ['/bin','/boot','/emul','/etc','/lib','/lib32','/lib64','/opt','/sbin','/usr','/var'], # directory of the filesystem
|
||||
'filesystemdirsmask': [
|
||||
@@ -562,6 +562,45 @@ else:
|
||||
import time
|
||||
time.sleep(5)
|
||||
|
||||
# Client packages/database repositories
|
||||
etpRepositories = {}
|
||||
etpRepositoriesOrder = []
|
||||
if os.path.isfile(etpConst['repositoriesconf']):
|
||||
f = open(etpConst['repositoriesconf'],"r")
|
||||
repositoriesconf = f.readlines()
|
||||
f.close()
|
||||
|
||||
for line in repositoriesconf:
|
||||
line = line.strip()
|
||||
# populate etpRepositories
|
||||
if (line.find("repository|") != -1) and (not line.startswith("#")) and (len(line.split("|")) == 5):
|
||||
reponame = line.split("|")[1]
|
||||
repodesc = line.split("|")[2]
|
||||
repopackages = line.split("|")[3]
|
||||
repodatabase = line.split("|")[4]
|
||||
if (repopackages.startswith("http://") or repopackages.startswith("ftp://")) and (repodatabase.startswith("http://") or repodatabase.startswith("ftp://")):
|
||||
etpRepositories[reponame] = {}
|
||||
etpRepositoriesOrder.append(reponame)
|
||||
etpRepositories[reponame]['description'] = repodesc
|
||||
etpRepositories[reponame]['packages'] = []
|
||||
for x in repopackages.split():
|
||||
etpRepositories[reponame]['packages'].append(x)
|
||||
etpRepositories[reponame]['database'] = repodatabase+"/"+etpConst['currentarch']
|
||||
etpRepositories[reponame]['dbpath'] = etpConst['etpdatabaseclientdir']+"/"+reponame+"/"+etpConst['currentarch']
|
||||
# initialize CONFIG_PROTECT - will be filled the first time the db will be opened
|
||||
etpRepositories[reponame]['configprotect'] = set()
|
||||
etpRepositories[reponame]['configprotectmask'] = set()
|
||||
elif (line.find("branch|") != -1) and (not line.startswith("#")) and (len(line.split("|")) == 2):
|
||||
branch = line.split("|")[1]
|
||||
etpConst['branch'] = branch
|
||||
if branch not in etpConst['branches']:
|
||||
etpConst['branches'].append(branch)
|
||||
if not os.path.isdir(etpConst['packagesbindir']+"/"+branch):
|
||||
if os.getuid() == 0:
|
||||
os.makedirs(etpConst['packagesbindir']+"/"+branch)
|
||||
else:
|
||||
print "ERROR: please run equo as root at least once or create: "+str(etpConst['packagesbindir']+"/"+branch)
|
||||
sys.exit(49)
|
||||
|
||||
# database section
|
||||
if (not os.path.isfile(etpConst['databaseconf'])):
|
||||
|
||||
@@ -39,7 +39,6 @@ mirrorLog = logTools.LogFile(level=etpConst['mirrorsloglevel'],filename = etpCon
|
||||
|
||||
class handlerFTP:
|
||||
|
||||
# ftp://linuxsabayon:asdasd@silk.dreamhost.com/sabayon.org
|
||||
# this must be run before calling the other functions
|
||||
def __init__(self, ftpuri, debug = None):
|
||||
|
||||
@@ -48,8 +47,6 @@ class handlerFTP:
|
||||
debug = True
|
||||
else:
|
||||
debug = False
|
||||
# FIXME: remove this
|
||||
#debug = True
|
||||
|
||||
# import FTP modules
|
||||
import timeoutsocket
|
||||
@@ -208,6 +205,11 @@ class handlerFTP:
|
||||
mirrorLog.log(ETP_LOGPRI_ERROR,ETP_LOGLEVEL_VERBOSE,"handlerFTP.deleteFile: result -> False (exception occured!!!)")
|
||||
return False
|
||||
|
||||
def mkdir(self,directory):
|
||||
mirrorLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_VERBOSE,"handlerFTP.mkdir: called for -> "+str(directory))
|
||||
# FIXME: add rc
|
||||
self.ftpconn.mkd(directory)
|
||||
|
||||
# this function also supports callback, because storbinary doesn't
|
||||
def advancedStorBinary(self, cmd, fp, callback=None, blocksize=8192):
|
||||
mirrorLog.log(ETP_LOGPRI_INFO,ETP_LOGLEVEL_VERBOSE,"handlerFTP.advancedStorBinary: called with -> "+str(cmd))
|
||||
|
||||
+107
-72
@@ -98,89 +98,126 @@ def update(options):
|
||||
# collect differences between the packages in the database and the ones on the system
|
||||
|
||||
reagentRequestSeekStore = False
|
||||
reagentRequestRepackage = False
|
||||
repackageItems = []
|
||||
_options = []
|
||||
for opt in options:
|
||||
if opt.startswith("--seekstore"):
|
||||
reagentRequestSeekStore = True
|
||||
elif opt.startswith("--repackage"):
|
||||
reagentRequestRepackage = True
|
||||
else:
|
||||
if (reagentRequestRepackage) and (not opt.startswith("--")):
|
||||
if not opt in repackageItems:
|
||||
repackageItems.append(opt)
|
||||
continue
|
||||
_options.append(opt)
|
||||
options = _options
|
||||
|
||||
|
||||
if (not reagentRequestSeekStore):
|
||||
|
||||
print_info(yellow(" * ")+red("Scanning the database for differences..."))
|
||||
dbconn = databaseTools.etpDatabase(readOnly = True, noUpload = True)
|
||||
from portageTools import getInstalledPackagesCounters, quickpkg, getPackageSlot
|
||||
installedPackages = getInstalledPackagesCounters()
|
||||
installedCounters = {}
|
||||
databasePackages = dbconn.listAllPackages()
|
||||
toBeAdded = []
|
||||
toBeRemoved = []
|
||||
|
||||
# fill lists
|
||||
|
||||
# packages to be added
|
||||
for x in installedPackages[0]:
|
||||
installedCounters[x[1]] = 1
|
||||
counter = dbconn.isCounterAvailable(x[1])
|
||||
if (not counter):
|
||||
toBeAdded.append(x)
|
||||
|
||||
if (not reagentRequestRepackage):
|
||||
print_info(yellow(" * ")+red("Scanning the database for differences..."))
|
||||
from portageTools import getInstalledPackagesCounters, quickpkg, getPackageSlot
|
||||
installedPackages = getInstalledPackagesCounters()
|
||||
installedCounters = {}
|
||||
databasePackages = dbconn.listAllPackages()
|
||||
toBeAdded = []
|
||||
toBeRemoved = []
|
||||
|
||||
# packages to be added
|
||||
for x in installedPackages[0]:
|
||||
installedCounters[x[1]] = 1
|
||||
counter = dbconn.isCounterAvailable(x[1])
|
||||
if (not counter):
|
||||
toBeAdded.append(x)
|
||||
|
||||
# packages to be removed from the database
|
||||
databaseCounters = dbconn.listAllCounters()
|
||||
for x in databaseCounters:
|
||||
match = installedCounters.get(x[0], None)
|
||||
#print match
|
||||
if (not match):
|
||||
# check if the package is in toBeAdded
|
||||
if (toBeAdded):
|
||||
atomkey = dep_getkey(dbconn.retrieveAtom(x[1]))
|
||||
atomslot = dbconn.retrieveSlot(x[1])
|
||||
add = True
|
||||
for pkgdata in toBeAdded:
|
||||
addslot = getPackageSlot(pkgdata[0])
|
||||
addkey = dep_getkey(pkgdata[0])
|
||||
# workaround for ebuilds not having slot
|
||||
if addslot == None:
|
||||
addslot = ''
|
||||
if (atomkey == addkey) and (atomslot == addslot):
|
||||
# do not add to toBeRemoved
|
||||
add = False
|
||||
break
|
||||
if add:
|
||||
toBeRemoved.append(x[1])
|
||||
else:
|
||||
toBeRemoved.append(x[1])
|
||||
|
||||
if (not toBeRemoved) and (not toBeAdded):
|
||||
print_info(yellow(" * ")+red("Nothing to do, check later."))
|
||||
# then exit gracefully
|
||||
sys.exit(0)
|
||||
|
||||
if (toBeRemoved):
|
||||
print_info(yellow(" @@ ")+blue("These are the packages that would be removed from the database:"))
|
||||
for x in toBeRemoved:
|
||||
atom = dbconn.retrieveAtom(x)
|
||||
print_info(yellow(" # ")+red(atom))
|
||||
rc = askquestion(">> Would you like to remove them now ?")
|
||||
if rc == "Yes":
|
||||
rwdbconn = databaseTools.etpDatabase(readOnly = False, noUpload = True)
|
||||
for x in toBeRemoved:
|
||||
atom = rwdbconn.retrieveAtom(x)
|
||||
print_info(yellow(" @@ ")+blue("Removing from database: ")+red(atom), back = True)
|
||||
rwdbconn.removePackage(x)
|
||||
rwdbconn.closeDB()
|
||||
print_info(yellow(" @@ ")+blue("Database removal complete."))
|
||||
|
||||
if (toBeAdded):
|
||||
print_info(yellow(" @@ ")+blue("These are the packages that would be added/updated to the add list:"))
|
||||
for x in toBeAdded:
|
||||
print_info(yellow(" # ")+red(x[0]))
|
||||
rc = askquestion(">> Would you like to packetize them now ?")
|
||||
if rc == "No":
|
||||
# packages to be removed from the database
|
||||
databaseCounters = dbconn.listAllCounters()
|
||||
for x in databaseCounters:
|
||||
match = installedCounters.get(x[0], None)
|
||||
#print match
|
||||
if (not match):
|
||||
# check if the package is in toBeAdded
|
||||
if (toBeAdded):
|
||||
atomkey = dep_getkey(dbconn.retrieveAtom(x[1]))
|
||||
atomslot = dbconn.retrieveSlot(x[1])
|
||||
add = True
|
||||
for pkgdata in toBeAdded:
|
||||
addslot = getPackageSlot(pkgdata[0])
|
||||
addkey = dep_getkey(pkgdata[0])
|
||||
# workaround for ebuilds not having slot
|
||||
if addslot == None:
|
||||
addslot = ''
|
||||
if (atomkey == addkey) and (atomslot == addslot):
|
||||
# do not add to toBeRemoved
|
||||
add = False
|
||||
break
|
||||
if add:
|
||||
toBeRemoved.append(x[1])
|
||||
else:
|
||||
toBeRemoved.append(x[1])
|
||||
|
||||
if (not toBeRemoved) and (not toBeAdded):
|
||||
print_info(yellow(" * ")+red("Nothing to do, check later."))
|
||||
# then exit gracefully
|
||||
sys.exit(0)
|
||||
|
||||
if (toBeRemoved):
|
||||
print_info(yellow(" @@ ")+blue("These are the packages that would be removed from the database:"))
|
||||
for x in toBeRemoved:
|
||||
atom = dbconn.retrieveAtom(x)
|
||||
print_info(yellow(" # ")+red(atom))
|
||||
rc = askquestion(">> Would you like to remove them now ?")
|
||||
if rc == "Yes":
|
||||
rwdbconn = databaseTools.etpDatabase(readOnly = False, noUpload = True)
|
||||
for x in toBeRemoved:
|
||||
atom = rwdbconn.retrieveAtom(x)
|
||||
print_info(yellow(" @@ ")+blue("Removing from database: ")+red(atom), back = True)
|
||||
rwdbconn.removePackage(x)
|
||||
rwdbconn.closeDB()
|
||||
print_info(yellow(" @@ ")+blue("Database removal complete."))
|
||||
|
||||
if (toBeAdded):
|
||||
print_info(yellow(" @@ ")+blue("These are the packages that would be added/updated to the add list:"))
|
||||
for x in toBeAdded:
|
||||
print_info(yellow(" # ")+red(x[0]))
|
||||
rc = askquestion(">> Would you like to packetize them now ?")
|
||||
if rc == "No":
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
if not repackageItems:
|
||||
print_info(yellow(" * ")+red("Nothing to do, check later."))
|
||||
# then exit gracefully
|
||||
sys.exit(0)
|
||||
|
||||
from portageTools import getPortageAppDbPath,quickpkg
|
||||
appdb = getPortageAppDbPath()
|
||||
|
||||
packages = []
|
||||
for item in repackageItems:
|
||||
match = dbconn.atomMatch(item)
|
||||
if match[0] == -1:
|
||||
print_warning(darkred(" !!! ")+red("Cannot match ")+bold(item))
|
||||
else:
|
||||
cat = dbconn.retrieveCategory(match[0])
|
||||
name = dbconn.retrieveName(match[0])
|
||||
version = dbconn.retrieveVersion(match[0])
|
||||
slot = dbconn.retrieveSlot(match[0])
|
||||
if os.path.isdir(appdb+"/"+cat+"/"+name+"-"+version):
|
||||
packages.append([cat+"/"+name+"-"+version,0])
|
||||
|
||||
# FIXME: complete this
|
||||
if not packages:
|
||||
print_info(yellow(" * ")+red("Nothing to do, check later."))
|
||||
# then exit gracefully
|
||||
sys.exit(0)
|
||||
|
||||
toBeAdded = packages
|
||||
|
||||
# package them
|
||||
print_info(yellow(" @@ ")+blue("Compressing packages..."))
|
||||
for x in toBeAdded:
|
||||
@@ -324,9 +361,7 @@ def extractPkgData(package, etpBranch = etpConst['branch']):
|
||||
f.close()
|
||||
|
||||
print_info(yellow(" * ")+red(info_package+"Setting package branch..."),back = True)
|
||||
# always unstable when created
|
||||
i = etpConst['branches'].index(etpBranch)
|
||||
etpData['branch'] = etpConst['branches'][i]
|
||||
etpData['branch'] = etpBranch
|
||||
|
||||
print_info(yellow(" * ")+red(info_package+"Getting package description..."),back = True)
|
||||
# Fill description
|
||||
|
||||
+3
-5
@@ -42,19 +42,17 @@ def print_help():
|
||||
print_info(" --nocolor\t\tdisable colorized output")
|
||||
print_info(blue("Tools available: "))
|
||||
print_info(" \t"+green("update")+yellow("\t\t Update Entropy Database analyzing the system for new installed packages"))
|
||||
print_info(" \t\t"+red("--branch=<branch name>")+"\t\t Choose which branch to assign to the packages")
|
||||
print_info(" \t\t"+red("--branch=<branch name>")+"\t\t Choose which branch assign to the packages")
|
||||
print_info(" \t\t"+red("--seekstore")+"\t\t\t Skip differential COUNTERS scanning and analyze STORE directory")
|
||||
print_info(" \t\t"+red("--repackage")+"\t\t\t Repackage specified atoms")
|
||||
print_info(" \t"+green("database")+yellow("\t Entropy database tool manager"))
|
||||
print_info(" \t\t"+red("--initialize")+"\t\t\t (Re)Initialize the Entropy packages database [DO NOT USE THIS]")
|
||||
print_info(" \t\t"+red("statistics")+"\t\t\t Show Entropy database statistics.")
|
||||
print_info(" \t\t"+green("search")+"\t\t\t\t Search a package inside the Entropy packages database")
|
||||
print_info(" \t\t"+green("remove")+"\t\t\t\t Remove a package or a list of packages")
|
||||
print_info(" \t\t\t"+red("--branch=<branch name>")+"\t Choose which branch of the package to remove")
|
||||
print_info(" \t\t"+green("create-empty-database")+"\t\t Create an empty Entropy database file in the specified <path>")
|
||||
print_info(" \t\t"+green("stabilize")+"\t\t\t Mark as stable a package, a list of packages, world")
|
||||
print_info(" \t\t"+green("unstabilize")+"\t\t\t Mark as unstable a package, a list of packages, world")
|
||||
print_info(" \t\t"+green("switchbranch")+"\t\t\t Switch to the specified branch, a package, a list of packages, world")
|
||||
print_info(" \t\t"+green("md5check")+"\t\t\t Check digest of a package, a list of packages, world")
|
||||
#print_info(" \t\t"+green(bold("orphans"))+"\t\t\t Dump the list of files that don't belong on any installed package") FIXME can be done using sets
|
||||
print_info(" \t"+green("deptest")+yellow("\t\t Look for unsatisfied dependencies inside database"))
|
||||
print_info(" \t"+green("depends")+yellow("\t\t Regenerate depends table (plus database lock and bump)"))
|
||||
print_info(" \t\t"+red("--quiet")+"\t\t\t\t just print the dependencies list")
|
||||
|
||||
Reference in New Issue
Block a user