Modifying Info.plist

I wanted to be able to add a dependency to another project using code and I came up with this:

-- Setting Dependencies Test

-- Use this function to perform your initial setup
function setup()
    PROJECTNAME = "Setting Dependencies Test"
    getDependencies()
    addDependency(PROJECTNAME, "Briarfox Scores")
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
end

function getDependencies()
    local projectName = PROJECTNAME
    
    local path = os.getenv("HOME") .. "/Documents/"
    local file = io.open(path .. projectName .. ".codea/Info.plist","r")
    
    if (file) then
        local plist = file:read("*all"):gsub("[\\t\
]","")
        file:close()
        
        if plist:match("Dependencies</key><array>(.-)</array>") then
            for dep in plist:match("Dependencies</key><array>(.-)</array>"):gmatch("<string>(.-)</string>") do
                print("Found Dependency: "..dep)
            end
        end
    end
end

function addDependency(projectName, dependency)
    local path = os.getenv("HOME") .. "/Documents/"
    local fileName = path .. projectName .. ".codea/Info.plist"
    local read = io.open(fileName, "r")
    
    if (read) then
        print("Adding " .. dependency .. " to " .. projectName)
    
        local plist = read:read("*all"):gsub("[\\t\
]","")
        read:close()
        
        local needToAdd
        
        local a, start = plist:find("<key>Dependencies</key><array>")
        local send, b = plist:find("</array>")
        local mid = plist:match("<key>Dependencies</key><array>(.-)</array>")
        
        if start == nil then
            a, start = plist:find("<key>Dependencies</key>")
            send, b = plist:find("<array/>")
            mid = ""
            needToAdd = true
        end
        
        local before = plist:sub(1, start)
        local after = plist:sub(send, plist:len())
        
        if needToAdd then
            before = before .. "<array>"
            after = after:gsub("<array/>", "</array>")
        end
            
        local new = before .. "<string>" .. dependency .. "</string>" .. mid .. after
            
        local write = io.open(fileName, "w")
        write:write(new)
        write:close()
        
        getDependencies()
    end
end

It works in that when I check again it finds the one I added, but when I return to the code, the dependency is not added. I am wondering, is it not possible to save changes to Info.plist? If so, why?

I had completely forgotten that you could do this type of stuff in Codea, despite the fact that I discovered it (more like what you can access with Lua file io and unboxing Codea). Nice example.
Thanks!

It was my understanding that .plist files were in binary format and had to be converted to XML. Does Codea do this on the fly? or, this may be your issue…

@syntonica - I’m not quite sure what you mean, but the .plist files (which are XML, with Apple’s standard for storing dictionary-like-variabels (if you don’t know what those are, don’t work, it’s not Codea related)) in Codea projects hold things like the name, author, dependencies, and any other metadata that you want to hold. I probably just told you something that you’re not asking, but I might as well say it. :slight_smile:
Thanks!

@syntonica, perhaps that is it.

@Zoyt. Think he means that the string has to be converted to XML before saving. Though I’m not sure that is the issue.

@JakAttak - Have you tried restarting Codea? If not, see if the file is actually affected with iExplorer. (I didn’t see you had an issue with the code.)

@Zoyt, no luck. If you run it twice, you’ll see it doesn’t seem to save.

@JakAttak i have noticed that when i make / modify a file in whatever folder different from ‘documents’, then
-1/ it seem ok as long as the program runs.
-2/ when i re-run it the changes were not saved.
I thinks this is the Apple security system. Only the ‘document’ folder is ‘sandboxed’ so we can do what we want in it.

I’m going to look into it a bit more when I get back to my iPad. No I’m curious how Codea deals with this.

@Jmv38, but it is in the Documents folder…

I use the info.plist in a backup program that I posted a while back. I use the dependency information to determine which files to backup. One odd thing I noticed about the dependencies is that if a project is deleted, you can’t remove it from the dependency list. You have to recreate a dummy project with that name, remove it from the dependency list, then delete the project.

@JakAttack apparently only creating files in the top level of document folder is authorized. Not modifying sub folders. From my experience only, not read that anywhere.

@Jmv38 if this limitations is imposed by Apple then how does Codea edit it to save dependencies? And if it is actually imposed by Codea/TLL then perhaps @Simeon or @John can explain why.

@JakAttak i dont know.

@Zoyt My mistake! I seem to remember that .plist files were stored in a special binary format, but that’s probably Android. LOL!

I had to pull a .plist out of Preview and open it in a hex editor to be sure. Sometimes, there’s translation going on in the background. However, the file is pure text with no header.

Can’t figure out what’s happening. See next comment.

Am I missing something or maybe not reading this right. I created 2 test projects, test1 and test2. I took the above program and added the dependency test1 into project test2. I went into project test2 and it showed that project test1 was checked as a dependency. So it looks like the above program is working.

EDIT: I tried it again, but now It doesn’t seem to work. I have to figure out why.

EDIT: It looks like the above program is dropping the newline characters. I don’t know why it worked the first time.

What the plist should look like

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Author</key>
    <string>Unknown</string>
    <key>Buffer Order</key>
    <array>
        <string>Main</string>
    </array>
    <key>Created</key>
    <string>2014-01-16 23:01:43 +0000</string>
    <key>Dependencies</key>
    <array>
        <string>test1</string>
    </array>
    <key>Description</key>
    <string>No description available</string>
</dict>
</plist>

What the plist looks like when it doesn’t work.

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Author</key><string>Unknown</string><key>Buffer Order</key><array><string>Main</string></array><key>Created</key><string>2014-01-16 23:01:43 +0000</string><key>Dependencies</key><array><string>test1</string></array><key>Description</key><string>No description available</string></dict></plist>

EDIT: This is crazy. I tried it again and now it worked. The plist was formatted correctly.

@dave1707, you are right! It only doesn’t work on the project it is in, any other project works fine. I updated the code to allow adding multiple, before it screwed it up. Here is the working code:

-- Setting Dependencies Test

-- Use this function to perform your initial setup
function setup()
    PROJECTNAME = "Test2"
    getDependencies(PROJECTNAME)

    addDependency(PROJECTNAME, "Briarfox Scores")
    addDependency(PROJECTNAME, "Test3")
    addDependency(PROJECTNAME, "Test4")
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
end

function getDependencies(projectName)
    local path = os.getenv("HOME") .. "/Documents/"
    local file = io.open(path .. projectName .. ".codea/Info.plist","r")
    
    if (file) then
        local plist = file:read("*all"):gsub("[\\t\
]","")
        file:close()
        
        if plist:match("Dependencies</key><array>(.-)</array>") then
            for dep in plist:match("Dependencies</key><array>(.-)</array>"):gmatch("<string>(.-)</string>") do
                print("Found Dependency: "..dep)
            end
        end
    end
end

function addDependency(projectName, dependency)
    local path = os.getenv("HOME") .. "/Documents/"
    local fileName = path .. projectName .. ".codea/Info.plist"
    local read = io.open(fileName, "r")
    
    if (read) then
        print("Adding " .. dependency .. " to " .. projectName)
    
        local plist = read:read("*all"):gsub("[\\t\
]","")
        read:close()
        
        local needToAdd
        
        local a, start = plist:find("<key>Dependencies</key><array>")
        local s 
        if start ~= nil then s = start else s = 1 end
        local send, b = plist:find("</array>", s)
        local mid = plist:match("<key>Dependencies</key><array>(.-)</array>")
        
        if start == nil then
            a, start = plist:find("<key>Dependencies</key>")
            local s 
            if start ~= nil then s = start else s = 1 end
            send, b = plist:find("<array/>", s)
            mid = ""
            needToAdd = true
        end
        
        if start == nil then
            a, start = plist:find("<key>Created</key><string>(.-)</string>")
            send, b = plist:find("<key>Description</key>", start)
            mid = ""
            needToAddFull = true
        end
        
        local before = plist:sub(1, start)
        local after = plist:sub(send, plist:len())
        
        if needToAdd and not needToAddFull then
            before = before .. "<array>"
            after = after:gsub("<array/>", "</array>")
        elseif needToAddFull then
            before = before .. "<key>Dependencies</key><array>"
            after = "</array>" .. after
        end
        
        local new = before .. "<string>" .. dependency .. "</string>" .. mid .. after
        
        if plist:find("<string>" .. dependency .. "</string>") == nil then
            local write = io.open(fileName, "w")
            write:write(new)
            write:close()
        end
        
        getDependencies(projectName)
    end
end

@Briarfox, @toffer. What about using something like this to allow a user to give CC a list of projects and have it add itself to all of them?

@JakAttak - I’m not sure to understand, adding projects as dependencies to CC and have CC maintaining backup states of those ones when launched ?