Example #1
0
 def __init__(self, name=None, filename=None, create_if_not_exists=False,
         notify_on_change_func=None):
     # verify file exists if we're not about to possibly create a new one:
     new_project = False
     if not os.path.exists(filename):
         new_project = True
         if not create_if_not_exists:
             raise RuntimeError("no such project file: " + filename)
     # verify directory exists:
     directory = os.path.dirname(os.path.abspath(filename))
     if not os.path.exists(directory):
         raise RuntimeError("directory of project file does not exist: " \
             + directory)
     # finally, initialise:
     self.name = name
     self.filename = os.path.abspath(filename)
     self.directory = os.path.dirname(self.filename)
     super(Project, self).__init__(filename, "blitwizardProject",
         "project", max_size=(1024 * 50))
     if not new_project:
         self.name = self.settings["info"]["name"]
     if not hasattr(self, "settings"):
         self.settings = dict()
         self.settings["info"] = dict()
         self.settings["info"]["name"] = name
         self.settings["info"]["creation"] = datetime.datetime.utcnow().\
             strftime("%Y/%m/%d %H:%M:%S UTC")
     self.get_file_info_dir()
     # add undo/redo tracker:
     self.changestracker = ProjectChangesTracker(self,\
         notify_on_change_func)
     # add empty game.lua if this is a new project:
     if new_project:
         self.add_new_file("game.lua", "text/lua")
     return
Example #2
0
class Project(SettingsFile):
    def __init__(self, name=None, filename=None, create_if_not_exists=False,
            notify_on_change_func=None):
        # verify file exists if we're not about to possibly create a new one:
        new_project = False
        if not os.path.exists(filename):
            new_project = True
            if not create_if_not_exists:
                raise RuntimeError("no such project file: " + filename)
        # verify directory exists:
        directory = os.path.dirname(os.path.abspath(filename))
        if not os.path.exists(directory):
            raise RuntimeError("directory of project file does not exist: " \
                + directory)
        # finally, initialise:
        self.name = name
        self.filename = os.path.abspath(filename)
        self.directory = os.path.dirname(self.filename)
        super(Project, self).__init__(filename, "blitwizardProject",
            "project", max_size=(1024 * 50))
        if not new_project:
            self.name = self.settings["info"]["name"]
        if not hasattr(self, "settings"):
            self.settings = dict()
            self.settings["info"] = dict()
            self.settings["info"]["name"] = name
            self.settings["info"]["creation"] = datetime.datetime.utcnow().\
                strftime("%Y/%m/%d %H:%M:%S UTC")
        self.get_file_info_dir()
        # add undo/redo tracker:
        self.changestracker = ProjectChangesTracker(self,\
            notify_on_change_func)
        # add empty game.lua if this is a new project:
        if new_project:
            self.add_new_file("game.lua", "text/lua")
        return
        
    def sanitize_path(self, path):
        if platform.system() == "Windows" or \
                platform.system().startswith("CYGWIN"):
            path = path.replace("/", "\\")
            while path.find("\\\\") >= 0:
                path = path.replace("\\\\", "\\")
        else:
            path = path.replace("\\", "/")
            while path.find("//") >= 0:
                path = path.replace("//", "/")
        return path
        
    def get_full_file_path(self, filename):
        filename = self.sanitize_path(filename)
        fullpath = self.sanitize_path(os.path.join(self.directory,
            filename))
        fullpath = os.path.abspath(fullpath)
        return fullpath
    
    def ensure_valid_file_name(self, filename, is_directory=False):
        if filename.find("(dir_separator)") >= 0:
            raise RuntimeError("filename must not contain " + \
                "\"(dir_separator)\"")
        if filename.find("(dir_colon)") >= 0:
            raise RuntimeError("filename must not contain " + \
                "\"(dir_colon)\"")
        filename = filename.strip()
        if dir == False:
            if filename.endswith("/") or filename.endswith("\\") or \
                    filename.endswith("/.") or filename.endswith("\\.") or \
                    filename.endswith("/..") or filename.endswith("\\.."):
                raise RuntimeError("specify file and not directory path")
        else:
            if filename.endswith("/.") or filename.endswith("\\.") or \
                    filename.endswith("/..") or filename.endswith("\\.."):
                raise RuntimeError("use of . or .. relative paths not " + \
                    "supported")
        if filename.find(":") >= 0 or filename.find("*") >= 0 or \
                filename.find("?") >= 0 or filename.find("\"") >= 0 or \
                filename.find("<") >= 0 or filename.find(">") >= 0 or \
                filename.find("|") >= 0:
            raise RuntimeError("filename must not contain \\ / : * " + \
                "? \" < > |")
        if filename == "." or filename == "..":
            raise RuntimeError("filename \".\" or \"..\" is not valid")
        return

    def add_new_file(self, filename, mime_type):
        """ Add a new empty file to the project.
            Returns the ProjectFileInfo of the added file.
        """
        self.ensure_valid_file_name(filename)
        filename = self.sanitize_path(filename)
        if os.path.exists(self.get_full_file_path(filename)):
            raise RuntimeError("file already exists")
        f = open(self.get_full_file_path(filename), "w")
        f.close()
        finfo = None
        try:
            finfo = self.add_existing_file(filename, mime_type)
        except Exception as e:
            os.remove(self.get_full_file_path(filename))
            raise e
        return finfo

    def add_existing_file(self, filename, mime_type):
        """ Add an existing file on disk to the project.
            Returns the ProjectFileInfo of the added file.
        """
        self.ensure_valid_file_name(filename)
        filename = self.sanitize_path(filename)
        if self.get_file_info(filename) != None:
            raise RuntimeError("file \"" + filename + "\" was already added")
        if not os.path.exists(self.get_full_file_path(filename)):
            raise RuntimeError("no such file found: \"" + filename + "\"")
        finfo = self.get_file_info(filename, mime_type=mime_type,
            create_if_not_exists=True)
        finfo.save()
        return finfo
        
    def get_file_info_dir(self):
        """ Get path of directory where file info XML is kept.
        """
        dir = os.path.dirname(self.filename)
        dir = os.path.join(dir, "File Info.settings")
        if not os.path.exists(dir):
            os.mkdir(dir)
        return dir
        
    def file_info_name_to_file_name(self, path):
        """ Turn the path to a file info file to the actual file's path which
            the file info is about.
        """
        finfopath = self.sanitize_path(path)
        basepath = self.sanitize_path(self.directory)
        if finfopath.startswith(basepath):
            finfopath = finfopath[len(basepath):]
            if finfopath.startswith("/") or finfopath.startswith("\\"):
                finfopath = finfopath[1:]
        if not finfopath.startswith("File Info.settings"):
            raise ValueError("file info needs to be inside " + \
                "\"File Info.settings\" folder")
        finfopath = finfopath[len("File Info.settings"):]
        if finfopath.startswith("/") or finfopath.startswith("\\"):
            finfopath = finfopath[1:]
        if not finfopath.endswith(".xml"):
            raise ValueError("file info needs to have .xml " + \
                "file extension")
        finfopath = finfopath[:-len(".xml")]
        return finfopath.replace("(dir_separator)", "/").\
            replace("(dir_colon)", ":")

    def get_project_files_tree(self, mime_types=[]):
        """ Returns a dictionary containing all files and folders added the
            project (and relevant to the given mime types) in the base folder
            of your project.
            The keys of the dictionary are the file or directory name strings.
            The value for a file is a ProjectFileInfo instance.
            The value for a directory is a recursive dictionary with all just
            described contents for the sub directory again.
            @param mime_types The mime types that we are interested in. An
                              empty list means any mime type is ok.
        """
        resulting_dict = dict()
        def add_to_dict(file, parents):
            current_parent_dict = resulting_dict
            # Climb along the list of given parents and create them in the
            # dictionary:
            for parent in parents:
                if not parent in current_parent_dict:
                    current_parent_dict[parent] = dict()
                current_parent_dict = current_parent_dict[parent]
            # Now current_parent_dict points to the most inner parent.
            # Add the file at this point:
            current_parent_dict[file] = file
        for (filename, parents) in self.get_project_files_with_folders(\
                mime_types):
            add_to_dict(filename, parents)
        return resulting_dict

    def get_project_files_with_folders(self, mime_types=[]):
        """ Returns a list of tuples where the first value is
            the file name string, and the second one a list of
            the names of the parent folders (with first one the
            most outer one, and the last one the most inner
            one).
            Folders won't be listed.
            Example output for:
               2 files: source/my_source_code.lua, hello.lua
            [
             ("my_source_code.lua", ["source"]),
             ("hello.lua", []),
            ]
        """
        resulting_list = list()
        for path in self.list_project_files():
            # verify mime type:
            if len(mime_types) > 0:
                finfo = self.get_file_info(path)
                mime_found = False
                for mime in mime_types:
                    if mime.lower() == finfo.mime_type.lower():
                        mime_found = True
                        break
                if not mime_found:
                    # skip over this
                    continue
            # extract all parent folders one by one:
            current_parents = list()
            while path.find("/") >= 0 or path.find("\\") >= 0:
                pos = path.find("/")
                if path.find("\\") >= 0 and path.find("\\") < pos:
                    pos = path.find("\\")
                pre = path[pos:]
                path = path[pos + 1:]
                if len(pre) > 0:
                    current_parents.append(pre)
            resulting_list.append((path, current_parents))
        return resulting_list
    
    def list_project_folders(self):
        """ Lists all folders that have been added to the project.
        """
        flist = []
        for pfile in os.listdir(self.get_file_info_dir()):
            if pfile.endswith(".xml"):
                actual_path = self.file_info_name_to_file_name(
                    os.path.join(self.get_file_info_dir(), pfile))
                if actual_path[-1] != "/" and actual_path[-1] != "\\":
                    continue
                flist.append(
                    self.sanitize_path(
                        actual_path
                    )
                )
        return flist
    
    def list_project_files(self):
        """ Lists all files that have been added to the project.
            Folders won't be listed.
        """
        # cache handling:
        if hasattr(self, "list_project_files_cache"):
            if self.list_project_files_cache != None:
                if (datetime.datetime.now() - \
                        self.list_project_files_cache_time\
                        ).total_seconds() > 2:
                    self.list_project_files_cache = None
        else:
            self.list_project_files_cache = None
        if self.list_project_files_cache != None:
            return self.list_project_files_cache
        # compile actual file list:
        flist = []
        for pfile in os.listdir(self.get_file_info_dir()):
            if pfile.endswith(".xml"):
                actual_path = self.file_info_name_to_file_name(
                    os.path.join(self.get_file_info_dir(), pfile))
                if actual_path[-1] == "/" or actual_path[-1] == "\\":
                    continue
                flist.append(
                    self.sanitize_path(
                        actual_path
                    )
                )
        self.list_project_files_cache = flist
        self.list_project_files_cache_time = datetime.datetime.now()
        return flist

    def open_file_info(self, finfopath):
        """ Open up a file info xml directly from the XML path.
            Returns the resulting ProjectFileInfo.
        """
        filepath = self.file_info_name_to_file_name(finfopath)
        return get_file_info(filepath, create_if_not_exist=False)

    def get_file_info(self, filename, create_if_not_exists=False,
            mime_type=None):
        """ Get ProjectFileInfo instance for the given file.
        """
        filename = self.sanitize_path(filename)
        if filename.find("(dir_separator)") >= 0:
            raise RuntimeError("filename must not contain " + \
                "\"(dir_separator)\"")
        if filename.find("(dir_colon)") >= 0:
            raise RuntimeError("filename must not contain " + \
                "\"(dir_colon)\"")
        try:
            return ProjectFileInfo(self, filename,
                create_new_if_none=create_if_not_exists,
                mime_type=mime_type)
        except IOError:
            return None
            
    def has_unsaved_changes(self):
        return self.changestracker.project_has_changes()