def testArchiveConferenceFile( self ):
     """Makes sure a file wich is attached to a conference gets stored in
         the right path: basePath/year/C0/0/test.txt
     """
     #first we create a dummy user which will be the conf creator
     from MaKaC.user import Avatar
     av = Avatar()
     #Now we create a dummy conference and set its id to 0
     from MaKaC.conference import Conference
     c = Conference( av )
     c.setId( "0" )
     #Now we create the material (id=0) and attach it to the conference
     from MaKaC.conference import Material
     m = Material()
     c.addMaterial( m )
     #Now we create a dummy file and attach it to the material
     filePath = os.path.join( os.getcwd(), "test.txt" )
     fh = open(filePath, "w")
     fh.write("hola")
     fh.close()
     from MaKaC.conference import LocalFile
     f = LocalFile()
     f.setFilePath( filePath )
     f.setFileName( "test.txt" )
     m.addResource( f )
Exemple #2
0
 def _generateZipFile(self, srcPath):
     repo = OfflineRepository.getRepositoryFromDB()
     filename = os.path.basename(srcPath) + ".zip"
     fd = LocalFile()
     fd.setFilePath(srcPath)
     fd.setFileName(filename)
     repo.storeFile(fd, self._conf.getId())
     return fd
Exemple #3
0
    def testArchiveConferenceFile(self):
        """Makes sure a file wich is attached to a conference gets stored in
            the right path: basePath/year/C0/0/test.txt
        """

        with self._context('database'):
            #first we create a dummy user which will be the conf creator
            from MaKaC.user import Avatar
            av = Avatar()
            #Now we create a dummy conference and set its id to 0
            from MaKaC.conference import Conference
            c = Conference(av)
            c.setId("0")
            #Now we create the material (id=0) and attach it to the conference
            from MaKaC.conference import Material
            m = Material()
            c.addMaterial(m)
            #Now we create a dummy file and attach it to the material
            filePath = os.path.join(os.getcwd(), "test.txt")
            fh = open(filePath, "w")
            fh.write("hola")
            fh.close()
            from MaKaC.conference import LocalFile
            f = LocalFile()
            f.setFilePath(filePath)
            f.setFileName("test.txt")
            m.addResource(f)
 def _generateZipFile(self, srcPath):
     repo = OfflineRepository.getRepositoryFromDB()
     filename = os.path.basename(srcPath) + ".zip"
     fd = LocalFile()
     fd.setFilePath(srcPath)
     fd.setFileName(filename)
     repo.storeFile(fd, self._conf.getId())
     return fd
Exemple #5
0
    def _addMaterialType(self, text, user):

        from MaKaC.common.fossilize import fossilize
        from MaKaC.fossils.conference import ILocalFileExtendedFossil, ILinkFossil

        Logger.get('requestHandler').debug('Adding %s - request %s' %
                                           (self._uploadType, request))

        mat, newlyCreated = self._getMaterial()

        # if the material still doesn't exist, create it
        if newlyCreated:
            protectedAtResourceLevel = False
        else:
            protectedAtResourceLevel = True

        resources = []
        if self._uploadType in ['file', 'link']:
            if self._uploadType == "file":
                for fileEntry in self._files:
                    resource = LocalFile()
                    resource.setFileName(fileEntry["fileName"])
                    resource.setFilePath(fileEntry["filePath"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getFileName())
                    else:
                        resource.setName(self._displayName)

                    if not type(self._target) is Category:
                        log_info = {
                            "subject":
                            "Added file %s%s" % (fileEntry["fileName"], text)
                        }
                        self._target.getConference().getLogHandler().logAction(
                            log_info, log.ModuleNames.MATERIAL)
                    resources.append(resource)
                    # in case of db conflict we do not want to send the file to conversion again, nor re-store the file

            elif self._uploadType == "link":

                for link in self._links:
                    resource = Link()
                    resource.setURL(link["url"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getURL())
                    else:
                        resource.setName(self._displayName)

                    if not type(self._target) is Category:
                        log_info = {
                            "subject":
                            "Added link %s%s" % (resource.getURL(), text)
                        }
                        self._target.getConference().getLogHandler().logAction(
                            log_info, log.ModuleNames.MATERIAL)
                    resources.append(resource)

            status = "OK"
            info = resources
        else:
            status = "ERROR"
            info = "Unknown upload type"
            return mat, status, info

        # forcedFileId - in case there is a conflict, use the file that is
        # already stored
        repoIDs = []
        for i, resource in enumerate(resources):
            if self._repositoryIds:
                mat.addResource(resource, forcedFileId=self._repositoryIds[i])
            else:
                mat.addResource(resource, forcedFileId=None)

            #apply conversion
            if self._topdf and not isinstance(resource, Link):
                file_ext = os.path.splitext(
                    resource.getFileName())[1].strip().lower()
                if fileConverter.CDSConvFileConverter.hasAvailableConversionsFor(
                        file_ext):
                    # Logger.get('conv').debug('Queueing %s for conversion' % resource.getFilePath())
                    fileConverter.CDSConvFileConverter.convert(
                        resource.getFilePath(), 'pdf', mat)
                    resource.setPDFConversionRequestDate(nowutc())

            # store the repo id, for files
            if isinstance(resource, LocalFile) and self._repositoryIds is None:
                repoIDs.append(resource.getRepositoryId())

            if protectedAtResourceLevel:
                protectedObject = resource
            else:
                protectedObject = mat
                mat.setHidden(self._visibility)
                mat.setAccessKey(self._password)

                protectedObject.setProtection(self._statusSelection)

            for userElement in self._userList:
                if 'isGroup' in userElement and userElement['isGroup']:
                    avatar = GroupHolder().getById(userElement['id'])
                else:
                    avatar = AvatarHolder().getById(userElement['id'])
                protectedObject.grantAccess(avatar)

        self._topdf = False
        if self._repositoryIds is None:
            self._repositoryIds = repoIDs

        return mat, status, fossilize(
            info, {
                "MaKaC.conference.Link": ILinkFossil,
                "MaKaC.conference.LocalFile": ILocalFileExtendedFossil
            })
Exemple #6
0
    def _addMaterialType(self, text, user):

        from MaKaC.common.fossilize import fossilize
        from MaKaC.fossils.conference import ILocalFileExtendedFossil, ILinkFossil

        Logger.get('requestHandler').debug('Adding %s - request %s' %
                                           (self._uploadType, request))

        mat, newlyCreated = self._getMaterial()

        # if the material still doesn't exist, create it
        if newlyCreated:
            protectedAtResourceLevel = False
        else:
            protectedAtResourceLevel = True

        resources = []
        assert self._uploadType == "file"
        for fileEntry in self._files:
            resource = LocalFile()
            resource.setFileName(fileEntry["fileName"])
            resource.setFilePath(fileEntry["filePath"])
            resource.setDescription(self._description)
            if self._displayName == "":
                resource.setName(resource.getFileName())
            else:
                resource.setName(self._displayName)
            resources.append(resource)
        status = "OK"
        info = resources

        # forcedFileId - in case there is a conflict, use the file that is
        # already stored
        repoIDs = []
        for i, resource in enumerate(resources):
            if self._repositoryIds:
                mat.addResource(resource, forcedFileId=self._repositoryIds[i])
            else:
                mat.addResource(resource, forcedFileId=None)

            # store the repo id, for files
            if isinstance(resource, LocalFile) and self._repositoryIds is None:
                repoIDs.append(resource.getRepositoryId())

            if protectedAtResourceLevel:
                protectedObject = resource
            else:
                protectedObject = mat
                mat.setHidden(self._visibility)
                mat.setAccessKey(self._password)

                protectedObject.setProtection(self._statusSelection)

            for principal in map(principal_from_fossil, self._userList):
                protectedObject.grantAccess(principal)

        if self._repositoryIds is None:
            self._repositoryIds = repoIDs

        return mat, status, fossilize(
            info, {
                "MaKaC.conference.Link": ILinkFossil,
                "MaKaC.conference.LocalFile": ILocalFileExtendedFossil
            })
Exemple #7
0
    def archiveTempBackgrounds(self, conf):
        """ Archives all the temporary backgrounds of this template.
        This method archives all of the temporary backgrounds of this template, which are
        stored in the form of filepath strings, in the __tempBackgroundsFilePaths dictionary,
        to a dictionary which stores LocalFile objects. The ids are copied, since there is a
        shared id counter for both dictionaries.
        After the archiving, the __tempBackgroundsFilePaths dictionary is reset to {}
        """


        for backgroundId, filePath in self.__tempBackgroundsFilePaths.iteritems():
            cfg = Config.getInstance()
            tempPath = cfg.getUploadedFilesSharedTempDir()
            filePath = os.path.join(tempPath, filePath)
            fileName = "background" + str(backgroundId) + "_t" + self.__id + "_c" + conf.id

            from MaKaC.conference import LocalFile
            file = LocalFile()
            file.setName( fileName )
            file.setDescription( "Background " + str(backgroundId) + " of the template " + self.__id + " of the conference " + conf.id )
            file.setFileName( fileName )

            file.setFilePath( filePath )
            file.setOwner( conf )
            file.setId( fileName )
            file.archive( conf._getRepository() )
            self.__backgrounds[backgroundId] = file

        self.notifyModification()
        self.__tempBackgroundsFilePaths = {}
Exemple #8
0
    def _addMaterialType(self, text, user):

        from MaKaC.common.fossilize import fossilize
        from MaKaC.fossils.conference import ILocalFileExtendedFossil, ILinkFossil

        Logger.get("requestHandler").debug("Adding %s - request %s" % (self._uploadType, request))

        mat, newlyCreated = self._getMaterial()

        # if the material still doesn't exist, create it
        if newlyCreated:
            protectedAtResourceLevel = False
        else:
            protectedAtResourceLevel = True

        resources = []
        if self._uploadType in ["file", "link"]:
            if self._uploadType == "file":
                for fileEntry in self._files:
                    resource = LocalFile()
                    resource.setFileName(fileEntry["fileName"])
                    resource.setFilePath(fileEntry["filePath"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getFileName())
                    else:
                        resource.setName(self._displayName)

                    if not isinstance(self._target, Category):
                        log_msg = u"Added file {}{}".format(to_unicode(fileEntry["fileName"]), to_unicode(text))
                        self._target.getConference().log(
                            EventLogRealm.management, EventLogKind.positive, u"Material", log_msg, session.user
                        )
                    resources.append(resource)
                    # in case of db conflict we do not want to send the file to conversion again, nor re-store the file

            elif self._uploadType == "link":

                for link in self._links:
                    resource = Link()
                    resource.setURL(link["url"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getURL())
                    else:
                        resource.setName(self._displayName)

                    if not isinstance(self._target, Category):
                        log_msg = u"Added link {}{}".format(to_unicode(resource.getURL()), to_unicode(text))
                        self._target.getConference().log(
                            EventLogRealm.management, EventLogKind.positive, u"Material", log_msg, session.user
                        )
                    resources.append(resource)

            status = "OK"
            info = resources
        else:
            status = "ERROR"
            info = "Unknown upload type"
            return mat, status, info

        # forcedFileId - in case there is a conflict, use the file that is
        # already stored
        repoIDs = []
        for i, resource in enumerate(resources):
            if self._repositoryIds:
                mat.addResource(resource, forcedFileId=self._repositoryIds[i])
            else:
                mat.addResource(resource, forcedFileId=None)

            # apply conversion
            if self._topdf and not isinstance(resource, Link):
                file_ext = os.path.splitext(resource.getFileName())[1].strip().lower()
                if fileConverter.CDSConvFileConverter.hasAvailableConversionsFor(file_ext):
                    fileConverter.CDSConvFileConverter.convert(resource.getFilePath(), "pdf", mat)
                    resource.setPDFConversionRequestDate(nowutc())

            # store the repo id, for files
            if isinstance(resource, LocalFile) and self._repositoryIds is None:
                repoIDs.append(resource.getRepositoryId())

            if protectedAtResourceLevel:
                protectedObject = resource
            else:
                protectedObject = mat
                mat.setHidden(self._visibility)
                mat.setAccessKey(self._password)

                protectedObject.setProtection(self._statusSelection)

            for principal in map(principal_from_fossil, self._userList):
                protectedObject.grantAccess(principal)

        self._topdf = False
        if self._repositoryIds is None:
            self._repositoryIds = repoIDs

        return (
            mat,
            status,
            fossilize(
                info, {"MaKaC.conference.Link": ILinkFossil, "MaKaC.conference.LocalFile": ILocalFileExtendedFossil}
            ),
        )
Exemple #9
0
    def uploadProcess(self):
        # We save the file
        try:
            f = LocalFile()
            f.setFileName("EAForm-%s"%self.spkUniqueId)
            f.setFilePath(self.filePath)
            f.setId(self.spkUniqueId)
            # Update status for speaker wrapper
            manager = self._conf.getCSBookingManager()
            spkWrapper = manager.getSpeakerWrapperByUniqueId(self.spkUniqueId)
            repo = self._conf._getRepository()

            f.setOwner(spkWrapper)
            f.archive(repo)
            # if no error, update the speaker wrapper...
            spkWrapper.setStatus(SpeakerStatusEnum.FROMFILE) #change status
            spkWrapper.setLocalFile(f) # set path to file
            spkWrapper.triggerNotification() # trigger notification task
        except:
            raise MaKaCError("Unexpected error while uploading file")
Exemple #10
0
    def archiveTempBackgrounds(self, conf):
        """ Archives all the temporary backgrounds of this template.
        This method archives all of the temporary backgrounds of this template, which are
        stored in the form of filepath strings, in the __tempBackgroundsFilePaths dictionary,
        to a dictionary which stores LocalFile objects. The ids are copied, since there is a
        shared id counter for both dictionaries.
        After the archiving, the __tempBackgroundsFilePaths dictionary is reset to {}
        """


        for backgroundId, filePath in self.__tempBackgroundsFilePaths.iteritems():
            cfg = Config.getInstance()
            tempPath = cfg.getUploadedFilesSharedTempDir()
            filePath = os.path.join(tempPath, filePath)
            fileName = "background" + str(backgroundId) + "_t" + self.__id + "_c" + conf.id

            from MaKaC.conference import LocalFile
            file = LocalFile()
            file.setName( fileName )
            file.setDescription( "Background " + str(backgroundId) + " of the template " + self.__id + " of the conference " + conf.id )
            file.setFileName( fileName )

            file.setFilePath( filePath )
            file.setOwner( conf )
            file.setId( fileName )
            file.archive( conf._getRepository() )
            self.__backgrounds[backgroundId] = file

        self.notifyModification()
        self.__tempBackgroundsFilePaths = {}
Exemple #11
0
    def uploadProcess(self):
        # We save the file
        try:
            f = LocalFile()
            __, extension = os.path.splitext(self.file.filename)
            f.setFileName("EAForm-{0}{1}".format(self.spkUniqueId, extension))
            f.setFilePath(self.filePath)
            f.setId(self.spkUniqueId)
            # Update status for speaker wrapper
            manager = Catalog.getIdx("cs_bookingmanager_conference").get(self._conf.getId())
            spkWrapper = manager.getSpeakerWrapperByUniqueId(self.spkUniqueId)
            repo = self._conf._getRepository()

            f.setOwner(spkWrapper)
            f.archive(repo)
            # if no error, update the speaker wrapper...
            spkWrapper.setStatus(SpeakerStatusEnum.FROMFILE) #change status
            spkWrapper.setLocalFile(f) # set path to file
            spkWrapper.triggerNotification() # trigger notification task
        except:
            Logger.get('file_upload').exception("Error uploading file")
            raise MaKaCError("Unexpected error while uploading file")
    def _addMaterialType(self, text, user):

        from MaKaC.common.fossilize import fossilize
        from MaKaC.fossils.conference import ILocalFileExtendedFossil, ILinkFossil

        Logger.get('requestHandler').debug('Adding %s - request %s ' % (self._uploadType, id(self._req)))

        mat, newlyCreated = self._getMaterial()

        # if the material still doesn't exist, create it
        if newlyCreated:
            protectedAtResourceLevel = False
        else:
            protectedAtResourceLevel = True

        resources = []
        if self._uploadType in ['file','link']:
            if self._uploadType == "file":
                for fileEntry in self._files:
                    resource = LocalFile()
                    resource.setFileName(fileEntry["fileName"])
                    resource.setFilePath(fileEntry["filePath"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getFileName())
                    else:
                        resource.setName(self._displayName)

                    if not type(self._target) is Category:
                        self._target.getConference().getLogHandler().logAction({"subject":"Added file %s%s" % (fileEntry["fileName"], text)}, "Files", user)
                    resources.append(resource)
                    # in case of db conflict we do not want to send the file to conversion again, nor re-store the file

            elif self._uploadType == "link":

                for link in self._links:
                    resource = Link()
                    resource.setURL(link["url"])
                    resource.setDescription(self._description)
                    if self._displayName == "":
                        resource.setName(resource.getURL())
                    else:
                        resource.setName(self._displayName)

                    if not type(self._target) is Category:
                        self._target.getConference().getLogHandler().logAction({"subject":"Added link %s%s" % (resource.getURL(), text)}, "Files", user)
                    resources.append(resource)

            status = "OK"
            info = resources
        else:
            status = "ERROR"
            info = "Unknown upload type"
            return mat, status, info

        # forcedFileId - in case there is a conflict, use the file that is
        # already stored
        repoIDs = []
        for i, resource in enumerate(resources):
            if self._repositoryIds:
                mat.addResource(resource, forcedFileId=self._repositoryIds[i])
            else:
                mat.addResource(resource, forcedFileId=None)

            #apply conversion
            if self._topdf and fileConverter.CDSConvFileConverter.hasAvailableConversionsFor(os.path.splitext(resource.getFileName())[1].strip().lower()):
                #Logger.get('conv').debug('Queueing %s for conversion' % resource.getFilePath())
                fileConverter.CDSConvFileConverter.convert(resource.getFilePath(), "pdf", mat)

            # store the repo id, for files
            if isinstance(resource, LocalFile) and self._repositoryIds is None:
                repoIDs.append(resource.getRepositoryId())

            if protectedAtResourceLevel:
                protectedObject = resource
            else:
                protectedObject = mat
                mat.setHidden(self._visibility)
                mat.setAccessKey(self._password)

                protectedObject.setProtection(self._statusSelection)

            for userElement in self._userList:
                if 'isGroup' in userElement and userElement['isGroup']:
                    avatar = GroupHolder().getById(userElement['id'])
                else:
                    avatar = AvatarHolder().getById(userElement['id'])
                protectedObject.grantAccess(avatar)

        self._topdf = False
        if self._repositoryIds is None:
            self._repositoryIds = repoIDs

        return mat, status, fossilize(info, {"MaKaC.conference.Link": ILinkFossil,
                                             "MaKaC.conference.LocalFile": ILocalFileExtendedFossil})
Exemple #13
0
    def uploadProcess(self):
        # We save the file
        try:
            f = LocalFile()
            __, extension = os.path.splitext(self.file.filename)
            f.setFileName("EAForm-{0}{1}".format(self.spkUniqueId, extension))
            f.setFilePath(self.filePath)
            f.setId(self.spkUniqueId)
            # Update status for speaker wrapper
            manager = self._conf.getCSBookingManager()
            spkWrapper = manager.getSpeakerWrapperByUniqueId(self.spkUniqueId)
            repo = self._conf._getRepository()

            f.setOwner(spkWrapper)
            f.archive(repo)
            # if no error, update the speaker wrapper...
            spkWrapper.setStatus(SpeakerStatusEnum.FROMFILE) #change status
            spkWrapper.setLocalFile(f) # set path to file
            spkWrapper.triggerNotification() # trigger notification task
        except:
            Logger.get('file_upload').exception("Error uploading file")
            raise MaKaCError("Unexpected error while uploading file")
    def _addMaterialType(self, text, user):

        from MaKaC.common.fossilize import fossilize
        from MaKaC.fossils.conference import ILocalFileExtendedFossil, ILinkFossil

        Logger.get('requestHandler').debug('Adding %s - request %s' % (self._uploadType, request))

        mat, newlyCreated = self._getMaterial()

        # if the material still doesn't exist, create it
        if newlyCreated:
            protectedAtResourceLevel = False
        else:
            protectedAtResourceLevel = True

        resources = []
        assert self._uploadType == "file"
        for fileEntry in self._files:
            resource = LocalFile()
            resource.setFileName(fileEntry["fileName"])
            resource.setFilePath(fileEntry["filePath"])
            resource.setDescription(self._description)
            if self._displayName == "":
                resource.setName(resource.getFileName())
            else:
                resource.setName(self._displayName)
            resources.append(resource)
        status = "OK"
        info = resources

        # forcedFileId - in case there is a conflict, use the file that is
        # already stored
        repoIDs = []
        for i, resource in enumerate(resources):
            if self._repositoryIds:
                mat.addResource(resource, forcedFileId=self._repositoryIds[i])
            else:
                mat.addResource(resource, forcedFileId=None)

            # store the repo id, for files
            if isinstance(resource, LocalFile) and self._repositoryIds is None:
                repoIDs.append(resource.getRepositoryId())

            if protectedAtResourceLevel:
                protectedObject = resource
            else:
                protectedObject = mat
                mat.setHidden(self._visibility)
                mat.setAccessKey(self._password)

                protectedObject.setProtection(self._statusSelection)

            for principal in map(principal_from_fossil, self._userList):
                protectedObject.grantAccess(principal)

        if self._repositoryIds is None:
            self._repositoryIds = repoIDs

        return mat, status, fossilize(info, {"MaKaC.conference.Link": ILinkFossil,
                                             "MaKaC.conference.LocalFile": ILocalFileExtendedFossil})
Exemple #15
0
    def _process(self, rh, params):
        
        # We will need to pickle the data back into JSON
        from MaKaC.common.PickleJar import DictPickler
        
        errorList=[]
        user = rh.getAW().getUser()
        try:
            owner = self._target
            title = owner.getTitle()
            if type(owner) == Conference:
                ownerType = "event"
            elif type(owner) == Session:
                ownerType = "session"
            elif type(owner) == Contribution:
                ownerType = "contribution"
            elif type(owner) == SubContribution:
                ownerType = "subcontribution"
            else:
                ownerType = ""
            text = " in %s %s" % (ownerType,title)
        except:
            owner = None
            text = ""

        errorList=self._getErrorList()
        file = self._file
        link = self._link        
        resource = None
        
        if self._uploadType == "file":
            if len(errorList)==0:
                mat = self._getMaterial()
                
                if mat == None:
                    errorList.append("Unknown material");
                else:
                    resource = LocalFile()
                    resource.setFileName(file["fileName"])
                    resource.setName(resource.getFileName())
                    resource.setFilePath(file["filePath"])
                    resource.setDescription(self._description)
                    mat.addResource(resource)
                    #apply conversion
                    if self._topdf and fileConverter.CDSConvFileConverter.hasAvailableConversionsFor(os.path.splitext(resource.getFileName())[1].strip().lower()):
                        fileConverter.CDSConvFileConverter.convert(resource.getFilePath(), "pdf", mat)
                    if not type(self._target) is Category:
                        self._target.getConference().getLogHandler().logAction({"subject":"Added file %s%s" % (file["fileName"],text)},"Files",user)
                    # in case of db conflict we do not want to send the file to conversion again
                    self._topdf = False
    
            if len(errorList) > 0:
                status = "ERROR"
                info = errorList
            else:
                status = "OK"
                info = DictPickler.pickle(resource)
                info['material'] = mat.getId();
        
        elif self._uploadType == "link":
            if len(errorList)==0:
                mat = self._getMaterial()
                if mat == None:
                    mat = Material()
                    mat.setTitle(link["matType"])
                    self._target.addMaterial( mat )
                resource = Link()
                resource.setURL(link["url"])
                resource.setName(resource.getURL())
                resource.setDescription(self._description)
                mat.addResource(resource)
                if not type(self._target) is Category:
                    self._target.getConference().getLogHandler().logAction({"subject":"Added link %s%s" % (resource.getURL(),text)},"Files",user)
            
                status = "OK"
                info = DictPickler.pickle(resource)
                info['material'] = mat.getId();
            else:
                status = "ERROR"
                info = errorList
        
        else:
            status = "ERROR"
            info = "Unknown upload type"
          
        # hackish, because of mime types. Konqueror, for instance, would assume text if there were no tags,
        # and would try to open it
        from MaKaC.services.interface.rpc import json
        return "<html><head></head><body>"+json.encode({'status': status, 'info': info})+"</body></html>"