Skip to content

Commit

Permalink
v1.1: switch to single-process
Browse files Browse the repository at this point in the history
also, ensure iCloud file directory exists before writing to file
  • Loading branch information
Carlos Liam committed Apr 27, 2015
1 parent 843579f commit 07aed82
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 136 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
.Trashes
ehthumbs.db
Thumbs.db
bin_win/*
bin_win32/*
bin_win64/*
bin_osx/*
bin_lnx/*
bin_lnx32/*
bin_lnx64
*.pyc
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ Airship is a Python-based program to synchronize game saves between clouds, such

Before installing and running Airship, you must install these dependencies.

- All functionality depends on having Python version 2.7 or above installed and accessible by typing `python` into your terminal or command line. Your operating system may have Python installed already; to check, type `python -V` into your terminal or command line. Otherwise, [download Python.](https://www.python.org/downloads)
- Steam Cloud functionality depends on having Steam installed, logged in, and running while Airship is running. [Download Steam.](https://store.steampowered.com/about)
- All functionality depends on having Python version 2.7 or above installed and accessible by typing `python` into your terminal or command line. Your operating system may have Python installed already; to check, type `python -V` into your terminal or command line. Otherwise, [download Python](https://www.python.org/downloads).
- Steam Cloud functionality depends on having Steam installed, logged in, and running while Airship is running. [Download Steam](https://store.steampowered.com/about).
- iCloud functionality depends on running Airship on OS X 10.10 Yosemite or above, being logged into iCloud, and having iCloud Drive synchronization enabled in System Preferences.

Download the latest release by going to 'releases' at the top of the page and going to the most recent release.

## Using
To use, simply run the `airship.py` script. Note that you must be logged in to your iCloud and Steam accounts and that Steam must be running.

For instructions on how to run Airship on a schedule, see the wiki page [Automatically running Airship](https://github.com/aarzee/airship/wiki/Automatically-running-Airship).

## Supported games
+ The Banner Saga ([Steam Cloud](http://store.steampowered.com/app/237990/), [iCloud](https://itunes.apple.com/us/app/banner-saga/id911006986))
75 changes: 67 additions & 8 deletions airship.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import sys
import subprocess
import importlib
import re
import time

os.chdir(os.path.dirname(os.path.abspath(__file__)))

Expand All @@ -11,13 +12,71 @@
'icloudid': 'MQ92743Y4D~com~stoicstudio~BannerSaga'
}]

void = open(os.devnull, 'w')
modules = {'steamcloud': None, 'icloud': None}

for modulename in modules:
module = importlib.import_module(modulename)
if module.init():
modules[modulename] = module

for game in games:
arguments = ['python', 'propeller.py']
gamemodules = []

for modulename in modules:
if modules[modulename] is not None and modulename + 'id' in game:
module = modules[modulename]

if modulename + 'folder' in game or 'folder' in game:
module.set_folder(game['folder'] if not modulename + 'folder' in game else game[modulename + 'folder'])

module.set_id(game[modulename + 'id'])

if module.will_work():
gamemodules.append(module)
else:
module.shutdown()

if len(gamemodules) > 1:
filetimestamps = {}
for moduleindex in range(len(gamemodules)):
filenames = gamemodules[moduleindex].get_file_names()
for filename in filenames:
if re.match(game['regex'], filename):
if not filename in filetimestamps:
filetimestamps[filename] = [0] * len(gamemodules)
filetimestamps[filename][moduleindex] = gamemodules[moduleindex].get_file_timestamp(filename)

for filename in filetimestamps:
newerfilesmayexist = True
highestlowtimestamp = -1
while newerfilesmayexist:
newerfilesmayexist = False
lowesttimestamp = int(time.time())
lowesttimestampindex = -1
for moduleindex in range(len(gamemodules)):
if highestlowtimestamp < filetimestamps[filename][moduleindex] < lowesttimestamp and filetimestamps[filename][moduleindex] > 0:
lowesttimestamp = filetimestamps[filename][moduleindex]
lowesttimestampindex = moduleindex
if lowesttimestampindex != -1:
newerfilesmayexist = True
highestlowtimestamp = lowesttimestamp
originaldata = gamemodules[lowesttimestampindex].read_file(filename)
if originaldata is not None:
for moduleindex in range(len(gamemodules)):
if moduleindex != lowesttimestampindex and filetimestamps[filename][moduleindex] > 0 and gamemodules[moduleindex].read_file(filename) == originaldata:
filetimestamps[filename][moduleindex] = lowesttimestamp

for property in game:
arguments.append('--' + property)
arguments.append(game[property])
highesttimestamp = -1
highesttimestampindex = -1
for moduleindex in range(len(gamemodules)):
if filetimestamps[filename][moduleindex] > highesttimestamp:
highesttimestamp = filetimestamps[filename][moduleindex]
highesttimestampindex = moduleindex
highesttimestampdata = gamemodules[highesttimestampindex].read_file(filename)
if highesttimestampdata is not None:
for moduleindex in range(len(gamemodules)):
if moduleindex != highesttimestampindex and filetimestamps[filename][moduleindex] < highesttimestamp:
gamemodules[moduleindex].write_file(filename, highesttimestampdata)

subprocess.call(arguments, stdout=void, stderr=void)
for module in gamemodules:
module.shutdown()
51 changes: 38 additions & 13 deletions icloud.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import os
import platform
import distutils.version
import os

def init():
global icloudbundleid
icloudbundleid = None
global icloudfolder
icloudfolder = None
global icloudpath
icloudpath = None

if platform.system() == 'Darwin':
version, _, machine = platform.mac_ver()
version = distutils.version.StrictVersion(version)
return (machine.startswith('iP') and version > distutils.version.StrictVersion('8')) or version > distutils.version.StrictVersion('10.10') and os.path.isdir(os.path.expanduser('~/Library/Mobile Documents'))

return False

def set_id(bundleid):
global icloudbundleid
Expand All @@ -10,21 +26,18 @@ def set_folder(folder):
icloudfolder = folder

def will_work():
if platform.mac_ver()[0].startswith('10.10') and os.path.isdir(os.path.expanduser('~/Library/Mobile Documents')):
global icloudpath
if 'icloudfolder' in globals():
icloudpath = os.path.expanduser('~/Library/Mobile Documents/' + icloudbundleid + '/' + icloudfolder)
else:
icloudpath = os.path.expanduser('~/Library/Mobile Documents/' + icloudbundleid)
if not os.path.isdir(icloudpath):
os.path.mkdirs(icloudpath)
return True
return False
global icloudpath
icloudpath = os.path.expanduser('~/Library/Mobile Documents/' + icloudbundleid + ('' if icloudfolder is None else '/' + icloudfolder))

if not os.path.isdir(icloudpath):
os.makedirs(icloudpath)

return True

def get_file_names():
filenames = []
def recursive_dir_contents(dir):
dircontents = os.listdir(icloudpath if dir is None else icloudpath + '/' + dir )
dircontents = os.listdir(icloudpath if dir is None else icloudpath + '/' + dir)
for item in dircontents:
if os.path.isdir(icloudpath + '/' + item if dir is None else icloudpath + '/' + dir + '/' + item):
recursive_dir_contents(item if dir is None else dir + '/' + item)
Expand All @@ -43,5 +56,17 @@ def read_file(filename):
return data

def write_file(filename, data):
with open(icloudpath + '/' + filename, 'w') as fileobject:
path = icloudpath + '/' + filename
dir = path[:path.rfind('/')]

if not os.path.isdir(dir):
os.makedirs(dir)

with open(path, 'w') as fileobject:
fileobject.write(data)

def shutdown():
global icloudbundleid
icloudbundleid = None
global icloudfolder
icloudfolder = None
83 changes: 0 additions & 83 deletions propeller.py

This file was deleted.

61 changes: 33 additions & 28 deletions steamcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@
import platform
import ctypes

def set_id(appid):
os.environ['SteamAppId'] = appid

def set_folder(folder):
global steamfolder
steamfolder = folder

def will_work():
def init():
try:
system = platform.system()
bits = platform.architecture()[0][:2]

global steamapi
if system == 'Windows':
steamapi = ctypes.CDLL('bin_win/CSteamworks.dll')
steamapi = ctypes.CDLL('bin_win' + bits + '/CSteamworks.dll')
elif system == 'Darwin':
steamapi = ctypes.CDLL('bin_osx/CSteamworks.dylib')
else:
steamapi = ctypes.CDLL('bin_lnx/CSteamworks.so')
steamapi = ctypes.CDLL('bin_lnx' + bits + '/CSteamworks.so')

global steamapi_init
steamapi_init = steamapi.InitSafe
Expand All @@ -37,46 +32,56 @@ def will_work():
steamapi_file_write = steamapi.ISteamRemoteStorage_FileWrite
global steamapi_file_read
steamapi_file_read = steamapi.ISteamRemoteStorage_FileRead
global steamapi_shutdown
steamapi_shutdown = steamapi.Shutdown

global steamfolder
steamfolder = None

return steamapi_init()
return True

except:
return False


def set_id(appid):
os.environ['SteamAppId'] = appid

def set_folder(folder):
global steamfolder
steamfolder = folder

def will_work():
return steamapi_init()

def get_file_names():
filenames = []

for fileindex in range(steamapi_get_file_count()):
filename = steamapi_get_file_name_size(fileindex, None)
if 'steamfolder' in globals():
if steamfolder is not None:
if filename.startswith(steamfolder + '/'):
filenames.append(filename[len(steamfolder) + 1:])
else:
filenames.append(filename)
return filenames

def get_file_timestamp(filename):
if 'steamfolder' in globals():
return steamapi_get_file_timestamp(steamfolder + '/' + filename)
else:
return steamapi_get_file_timestamp(filename)
return steamapi_get_file_timestamp(('' if steamfolder is None else steamfolder + '/') + filename)

def read_file(filename):
if 'steamfolder' in globals():
size = steamapi_get_file_size(steamfolder + '/' + filename)
else:
size = steamapi_get_file_size(filename)
size = steamapi_get_file_size(('' if steamfolder is None else steamfolder + '/') + filename)
buffer = ctypes.create_string_buffer(size)
if 'steamfolder' in globals():
steamapi_file_read(steamfolder + '/' + filename, buffer, size)
else:
steamapi_file_read(filename, buffer, size)
steamapi_file_read(('' if steamfolder is None else steamfolder + '/') + filename, buffer, size)
return buffer.value

def write_file(filename, data):
size = len(data)
buffer = ctypes.create_string_buffer(size)
buffer.value = data
if 'steamfolder' in globals():
steamapi_file_write(steamfolder + '/' + filename, buffer, size)
else:
steamapi_file_write(filename, buffer, size)
steamapi_file_write(('' if steamfolder is None else steamfolder + '/') + filename, buffer, size)

def shutdown():
global steamfolder
steamfolder = None
steamapi_shutdown()

0 comments on commit 07aed82

Please sign in to comment.