Make flags a set rather than a list
As this is essentially what it is, a set of values. This allows as to do set arithmetics to see, e.g. the intersection of 2 flag sets rather than clunkily having to do: for flag in newflags: if flag not in oldflags: oldflags.append(flag) Also some more code documenting. Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de> Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
parent
373e7cdbc1
commit
466ded04d9
@ -22,6 +22,10 @@ import os.path
|
|||||||
import re
|
import re
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
import traceback
|
import traceback
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
class BaseFolder(object):
|
class BaseFolder(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -189,12 +193,9 @@ class BaseFolder(object):
|
|||||||
|
|
||||||
def addmessageflags(self, uid, flags):
|
def addmessageflags(self, uid, flags):
|
||||||
"""Adds the specified flags to the message's flag set. If a given
|
"""Adds the specified flags to the message's flag set. If a given
|
||||||
flag is already present, it will not be duplicated."""
|
flag is already present, it will not be duplicated.
|
||||||
newflags = self.getmessageflags(uid)
|
:param flags: A set() of flags"""
|
||||||
for flag in flags:
|
newflags = self.getmessageflags(uid) | flags
|
||||||
if not flag in newflags:
|
|
||||||
newflags.append(flag)
|
|
||||||
newflags.sort()
|
|
||||||
self.savemessageflags(uid, newflags)
|
self.savemessageflags(uid, newflags)
|
||||||
|
|
||||||
def addmessagesflags(self, uidlist, flags):
|
def addmessagesflags(self, uidlist, flags):
|
||||||
@ -204,11 +205,7 @@ class BaseFolder(object):
|
|||||||
def deletemessageflags(self, uid, flags):
|
def deletemessageflags(self, uid, flags):
|
||||||
"""Removes each flag given from the message's flag set. If a given
|
"""Removes each flag given from the message's flag set. If a given
|
||||||
flag is already removed, no action will be taken for that flag."""
|
flag is already removed, no action will be taken for that flag."""
|
||||||
newflags = self.getmessageflags(uid)
|
newflags = self.getmessageflags(uid) - flags
|
||||||
for flag in flags:
|
|
||||||
if flag in newflags:
|
|
||||||
newflags.remove(flag)
|
|
||||||
newflags.sort()
|
|
||||||
self.savemessageflags(uid, newflags)
|
self.savemessageflags(uid, newflags)
|
||||||
|
|
||||||
def deletemessagesflags(self, uidlist, flags):
|
def deletemessagesflags(self, uidlist, flags):
|
||||||
@ -362,8 +359,8 @@ class BaseFolder(object):
|
|||||||
addflaglist = {}
|
addflaglist = {}
|
||||||
delflaglist = {}
|
delflaglist = {}
|
||||||
for uid in self.getmessageuidlist():
|
for uid in self.getmessageuidlist():
|
||||||
# Ignore messages with negative UIDs missed by pass 1
|
# Ignore messages with negative UIDs missed by pass 1 and
|
||||||
# also don't do anything if the message has been deleted remotely
|
# don't do anything if the message has been deleted remotely
|
||||||
if uid < 0 or not dstfolder.uidexists(uid):
|
if uid < 0 or not dstfolder.uidexists(uid):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -371,30 +368,31 @@ class BaseFolder(object):
|
|||||||
statusflags = statusfolder.getmessageflags(uid)
|
statusflags = statusfolder.getmessageflags(uid)
|
||||||
#if we could not get message flags from LocalStatus, assume empty.
|
#if we could not get message flags from LocalStatus, assume empty.
|
||||||
if statusflags is None:
|
if statusflags is None:
|
||||||
statusflags = []
|
statusflags = set()
|
||||||
addflags = [x for x in selfflags if x not in statusflags]
|
|
||||||
|
addflags = selfflags - statusflags
|
||||||
|
delflags = statusflags - selfflags
|
||||||
|
|
||||||
for flag in addflags:
|
for flag in addflags:
|
||||||
if not flag in addflaglist:
|
if not flag in addflaglist:
|
||||||
addflaglist[flag] = []
|
addflaglist[flag] = []
|
||||||
addflaglist[flag].append(uid)
|
addflaglist[flag].append(uid)
|
||||||
|
|
||||||
delflags = [x for x in statusflags if x not in selfflags]
|
|
||||||
for flag in delflags:
|
for flag in delflags:
|
||||||
if not flag in delflaglist:
|
if not flag in delflaglist:
|
||||||
delflaglist[flag] = []
|
delflaglist[flag] = []
|
||||||
delflaglist[flag].append(uid)
|
delflaglist[flag].append(uid)
|
||||||
|
|
||||||
for flag in addflaglist.keys():
|
for flag, uids in addflaglist.items():
|
||||||
self.ui.addingflags(addflaglist[flag], flag, dstfolder)
|
self.ui.addingflags(uids, flag, dstfolder)
|
||||||
dstfolder.addmessagesflags(addflaglist[flag], [flag])
|
dstfolder.addmessagesflags(uids, set(flag))
|
||||||
statusfolder.addmessagesflags(addflaglist[flag], [flag])
|
statusfolder.addmessagesflags(uids, set(flag))
|
||||||
|
|
||||||
for flag in delflaglist.keys():
|
|
||||||
self.ui.deletingflags(delflaglist[flag], flag, dstfolder)
|
|
||||||
dstfolder.deletemessagesflags(delflaglist[flag], [flag])
|
|
||||||
statusfolder.deletemessagesflags(delflaglist[flag], [flag])
|
|
||||||
|
|
||||||
|
for flag,uids in delflaglist.items():
|
||||||
|
self.ui.deletingflags(uids, flag, dstfolder)
|
||||||
|
dstfolder.deletemessagesflags(uids, set(flag))
|
||||||
|
statusfolder.deletemessagesflags(uids, set(flag))
|
||||||
|
|
||||||
def syncmessagesto(self, dstfolder, statusfolder):
|
def syncmessagesto(self, dstfolder, statusfolder):
|
||||||
"""Syncs messages in this folder to the destination dstfolder.
|
"""Syncs messages in this folder to the destination dstfolder.
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
from IMAP import IMAPFolder
|
from IMAP import IMAPFolder
|
||||||
from offlineimap import imaputil
|
from offlineimap import imaputil
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
|
|
||||||
class GmailFolder(IMAPFolder):
|
class GmailFolder(IMAPFolder):
|
||||||
@ -45,7 +44,7 @@ class GmailFolder(IMAPFolder):
|
|||||||
def deletemessages_noconvert(self, uidlist):
|
def deletemessages_noconvert(self, uidlist):
|
||||||
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
||||||
if not len(uidlist):
|
if not len(uidlist):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.realdelete and not (self.getname() in self.real_delete_folders):
|
if self.realdelete and not (self.getname() in self.real_delete_folders):
|
||||||
# IMAP expunge is just "remove label" in this folder,
|
# IMAP expunge is just "remove label" in this folder,
|
||||||
|
@ -24,6 +24,11 @@ import time
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
from Base import BaseFolder
|
from Base import BaseFolder
|
||||||
from offlineimap import imaputil, imaplibutil, OfflineImapError
|
from offlineimap import imaputil, imaplibutil, OfflineImapError
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
|
|
||||||
class IMAPFolder(BaseFolder):
|
class IMAPFolder(BaseFolder):
|
||||||
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
||||||
@ -176,7 +181,8 @@ class IMAPFolder(BaseFolder):
|
|||||||
finally:
|
finally:
|
||||||
self.imapserver.releaseconnection(imapobj)
|
self.imapserver.releaseconnection(imapobj)
|
||||||
for messagestr in response:
|
for messagestr in response:
|
||||||
# Discard the message number.
|
# looks like: '1 (FLAGS (\\Seen Old) UID 4807)'
|
||||||
|
# Discard initial message number.
|
||||||
messagestr = messagestr.split(' ', 1)[1]
|
messagestr = messagestr.split(' ', 1)[1]
|
||||||
options = imaputil.flags2hash(messagestr)
|
options = imaputil.flags2hash(messagestr)
|
||||||
if not options.has_key('UID'):
|
if not options.has_key('UID'):
|
||||||
@ -652,23 +658,18 @@ class IMAPFolder(BaseFolder):
|
|||||||
if not ('UID' in attributehash and 'FLAGS' in attributehash):
|
if not ('UID' in attributehash and 'FLAGS' in attributehash):
|
||||||
# Compensate for servers that don't return a UID attribute.
|
# Compensate for servers that don't return a UID attribute.
|
||||||
continue
|
continue
|
||||||
lflags = attributehash['FLAGS']
|
flagstr = attributehash['FLAGS']
|
||||||
uid = long(attributehash['UID'])
|
uid = long(attributehash['UID'])
|
||||||
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(lflags)
|
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flagstr)
|
||||||
try:
|
try:
|
||||||
needupdate.remove(uid)
|
needupdate.remove(uid)
|
||||||
except ValueError: # Let it slide if it's not in the list
|
except ValueError: # Let it slide if it's not in the list
|
||||||
pass
|
pass
|
||||||
for uid in needupdate:
|
for uid in needupdate:
|
||||||
if operation == '+':
|
if operation == '+':
|
||||||
for flag in flags:
|
self.messagelist[uid]['flags'] |= flags
|
||||||
if not flag in self.messagelist[uid]['flags']:
|
|
||||||
self.messagelist[uid]['flags'].append(flag)
|
|
||||||
self.messagelist[uid]['flags'].sort()
|
|
||||||
elif operation == '-':
|
elif operation == '-':
|
||||||
for flag in flags:
|
self.messagelist[uid]['flags'] -= flags
|
||||||
if flag in self.messagelist[uid]['flags']:
|
|
||||||
self.messagelist[uid]['flags'].remove(flag)
|
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
self.deletemessages_noconvert([uid])
|
self.deletemessages_noconvert([uid])
|
||||||
@ -682,7 +683,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
if not len(uidlist):
|
if not len(uidlist):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.addmessagesflags_noconvert(uidlist, ['T'])
|
self.addmessagesflags_noconvert(uidlist, set('T'))
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Local status cache virtual folder
|
# Local status cache virtual folder
|
||||||
# Copyright (C) 2002 - 2008 John Goerzen
|
# Copyright (C) 2002 - 2011 John Goerzen & contributors
|
||||||
# <jgoerzen@complete.org>
|
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -19,6 +18,10 @@
|
|||||||
from Base import BaseFolder
|
from Base import BaseFolder
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
|
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
|
||||||
|
|
||||||
@ -80,11 +83,12 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
try:
|
try:
|
||||||
uid, flags = line.split(':')
|
uid, flags = line.split(':')
|
||||||
uid = long(uid)
|
uid = long(uid)
|
||||||
|
flags = set(flags)
|
||||||
except ValueError, e:
|
except ValueError, e:
|
||||||
errstr = "Corrupt line '%s' in cache file '%s'" % (line, self.filename)
|
errstr = "Corrupt line '%s' in cache file '%s'" % \
|
||||||
|
(line, self.filename)
|
||||||
self.ui.warn(errstr)
|
self.ui.warn(errstr)
|
||||||
raise ValueError(errstr)
|
raise ValueError(errstr)
|
||||||
flags = [x for x in flags]
|
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
@ -95,8 +99,7 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
file.write(magicline + "\n")
|
file.write(magicline + "\n")
|
||||||
for msg in self.messagelist.values():
|
for msg in self.messagelist.values():
|
||||||
flags = msg['flags']
|
flags = msg['flags']
|
||||||
flags.sort()
|
flags = ''.join(sorted(flags))
|
||||||
flags = ''.join(flags)
|
|
||||||
file.write("%s:%s\n" % (msg['uid'], flags))
|
file.write("%s:%s\n" % (msg['uid'], flags))
|
||||||
file.flush()
|
file.flush()
|
||||||
if self.doautosave:
|
if self.doautosave:
|
||||||
|
@ -23,6 +23,11 @@ try:
|
|||||||
except:
|
except:
|
||||||
pass #fail only if needed later on, not on import
|
pass #fail only if needed later on, not on import
|
||||||
|
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
class LocalStatusSQLiteFolder(LocalStatusFolder):
|
class LocalStatusSQLiteFolder(LocalStatusFolder):
|
||||||
"""LocalStatus backend implemented with an SQLite database
|
"""LocalStatus backend implemented with an SQLite database
|
||||||
|
|
||||||
@ -127,7 +132,6 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
|
|||||||
for line in file.xreadlines():
|
for line in file.xreadlines():
|
||||||
uid, flags = line.strip().split(':')
|
uid, flags = line.strip().split(':')
|
||||||
uid = long(uid)
|
uid = long(uid)
|
||||||
flags = list(flags)
|
|
||||||
flags = ''.join(sorted(flags))
|
flags = ''.join(sorted(flags))
|
||||||
data.append((uid,flags))
|
data.append((uid,flags))
|
||||||
self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)',
|
self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)',
|
||||||
@ -167,7 +171,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
|
|||||||
self.messagelist = {}
|
self.messagelist = {}
|
||||||
cursor = self.connection.execute('SELECT id,flags from status')
|
cursor = self.connection.execute('SELECT id,flags from status')
|
||||||
for row in cursor:
|
for row in cursor:
|
||||||
flags = [x for x in row[1]]
|
flags = set(row[1])
|
||||||
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
|
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -28,6 +28,11 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from md5 import md5
|
from md5 import md5
|
||||||
|
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
from offlineimap import OfflineImapError
|
from offlineimap import OfflineImapError
|
||||||
|
|
||||||
uidmatchre = re.compile(',U=(\d+)')
|
uidmatchre = re.compile(',U=(\d+)')
|
||||||
@ -166,11 +171,12 @@ class MaildirFolder(BaseFolder):
|
|||||||
nouidcounter -= 1
|
nouidcounter -= 1
|
||||||
else:
|
else:
|
||||||
uid = long(uidmatch.group(1))
|
uid = long(uidmatch.group(1))
|
||||||
|
#identify flags in the path name
|
||||||
flagmatch = self.flagmatchre.search(messagename)
|
flagmatch = self.flagmatchre.search(messagename)
|
||||||
flags = []
|
|
||||||
if flagmatch:
|
if flagmatch:
|
||||||
flags = [x for x in flagmatch.group(1)]
|
flags = set(flagmatch.group(1))
|
||||||
flags.sort()
|
else:
|
||||||
|
flags = set()
|
||||||
retval[uid] = {'uid': uid,
|
retval[uid] = {'uid': uid,
|
||||||
'flags': flags,
|
'flags': flags,
|
||||||
'filename': file}
|
'filename': file}
|
||||||
@ -261,7 +267,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
if rtime != None:
|
if rtime != None:
|
||||||
os.utime(os.path.join(tmpdir, messagename), (rtime, rtime))
|
os.utime(os.path.join(tmpdir, messagename), (rtime, rtime))
|
||||||
|
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': [],
|
self.messagelist[uid] = {'uid': uid, 'flags': set(),
|
||||||
'filename': os.path.join('tmp', messagename)}
|
'filename': os.path.join('tmp', messagename)}
|
||||||
# savemessageflags moves msg to 'cur' or 'new' as appropriate
|
# savemessageflags moves msg to 'cur' or 'new' as appropriate
|
||||||
self.savemessageflags(uid, flags)
|
self.savemessageflags(uid, flags)
|
||||||
@ -288,8 +294,7 @@ class MaildirFolder(BaseFolder):
|
|||||||
infostr = infomatch.group(1)
|
infostr = infomatch.group(1)
|
||||||
newname = newname.split(self.infosep)[0] # Strip off the info string.
|
newname = newname.split(self.infosep)[0] # Strip off the info string.
|
||||||
infostr = re.sub('2,[A-Z]*', '', infostr)
|
infostr = re.sub('2,[A-Z]*', '', infostr)
|
||||||
flags.sort()
|
infostr += '2,' + ''.join(sorted(flags))
|
||||||
infostr += '2,' + ''.join(flags)
|
|
||||||
newname += infostr
|
newname += infostr
|
||||||
|
|
||||||
newfilename = os.path.join(dir_prefix, newname)
|
newfilename = os.path.join(dir_prefix, newname)
|
||||||
|
@ -20,6 +20,11 @@ import re
|
|||||||
import string
|
import string
|
||||||
import types
|
import types
|
||||||
from offlineimap.ui import getglobalui
|
from offlineimap.ui import getglobalui
|
||||||
|
try: # python 2.6 has set() built in
|
||||||
|
set
|
||||||
|
except NameError:
|
||||||
|
from sets import Set as set
|
||||||
|
|
||||||
quotere = re.compile('^("(?:[^"]|\\\\")*")')
|
quotere = re.compile('^("(?:[^"]|\\\\")*")')
|
||||||
|
|
||||||
def debug(*args):
|
def debug(*args):
|
||||||
@ -42,11 +47,21 @@ def dequote(string):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
def flagsplit(string):
|
def flagsplit(string):
|
||||||
|
"""Converts a string of IMAP flags to a list
|
||||||
|
|
||||||
|
:returns: E.g. '(\\Draft \\Deleted)' returns ['\\Draft','\\Deleted'].
|
||||||
|
(FLAGS (\\Seen Old) UID 4807) returns
|
||||||
|
['FLAGS,'(\\Seen Old)','UID', '4807']
|
||||||
|
"""
|
||||||
if string[0] != '(' or string[-1] != ')':
|
if string[0] != '(' or string[-1] != ')':
|
||||||
raise ValueError, "Passed string '%s' is not a flag list" % string
|
raise ValueError, "Passed string '%s' is not a flag list" % string
|
||||||
return imapsplit(string[1:-1])
|
return imapsplit(string[1:-1])
|
||||||
|
|
||||||
def options2hash(list):
|
def options2hash(list):
|
||||||
|
"""convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}"""
|
||||||
|
# effectively this does dict(zip(l[::2],l[1::2])), however
|
||||||
|
# measurements seemed to have indicated that the manual variant is
|
||||||
|
# faster for mosly small lists.
|
||||||
retval = {}
|
retval = {}
|
||||||
counter = 0
|
counter = 0
|
||||||
while (counter < len(list)):
|
while (counter < len(list)):
|
||||||
@ -55,8 +70,12 @@ def options2hash(list):
|
|||||||
debug("options2hash returning:", retval)
|
debug("options2hash returning:", retval)
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def flags2hash(string):
|
def flags2hash(flags):
|
||||||
return options2hash(flagsplit(string))
|
"""Converts IMAP response string from eg IMAP4.fetch() to a hash.
|
||||||
|
|
||||||
|
E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
|
||||||
|
{'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
|
||||||
|
return options2hash(flagsplit(flags))
|
||||||
|
|
||||||
def imapsplit(imapstring):
|
def imapsplit(imapstring):
|
||||||
"""Takes a string from an IMAP conversation and returns a list containing
|
"""Takes a string from an IMAP conversation and returns a list containing
|
||||||
@ -152,15 +171,16 @@ flagmap = [('\\Seen', 'S'),
|
|||||||
('\\Draft', 'D')]
|
('\\Draft', 'D')]
|
||||||
|
|
||||||
def flagsimap2maildir(flagstring):
|
def flagsimap2maildir(flagstring):
|
||||||
retval = []
|
"""Convert string '(\\Draft \\Deleted)' into a flags set(DR)"""
|
||||||
imapflaglist = [x.lower() for x in flagstring[1:-1].split()]
|
retval = set()
|
||||||
|
imapflaglist = flagstring[1:-1].split()
|
||||||
for imapflag, maildirflag in flagmap:
|
for imapflag, maildirflag in flagmap:
|
||||||
if imapflag.lower() in imapflaglist:
|
if imapflag in imapflaglist:
|
||||||
retval.append(maildirflag)
|
retval.add(maildirflag)
|
||||||
retval.sort()
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
def flagsmaildir2imap(maildirflaglist):
|
def flagsmaildir2imap(maildirflaglist):
|
||||||
|
"""Convert set of flags ([DR]) into a string '(\\Draft \\Deleted)'"""
|
||||||
retval = []
|
retval = []
|
||||||
for imapflag, maildirflag in flagmap:
|
for imapflag, maildirflag in flagmap:
|
||||||
if maildirflag in maildirflaglist:
|
if maildirflag in maildirflaglist:
|
||||||
@ -198,8 +218,3 @@ def listjoin(list):
|
|||||||
retval.append(getlist(start, end))
|
retval.append(getlist(start, end))
|
||||||
|
|
||||||
return ",".join(retval)
|
return ",".join(retval)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user