Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

# -*- coding: utf-8 -*- 

"""Personnal Game Archive module. Handle local database of user's games.""" 

 

import os 

import logging 

 

from lutris.util.strings import slugify 

from lutris.util.log import logger 

from lutris.util import sql 

from lutris import settings 

 

PGA_DB = settings.PGA_DB 

LOGGER = logging.getLogger(__name__) 

 

 

def get_schema(tablename): 

    """ 

    Fields: 

        - position 

        - name 

        - type 

        - not null 

        - default 

        - indexed 

    """ 

    tables = [] 

    query = "pragma table_info('%s')" % tablename 

    with sql.db_cursor(PGA_DB) as cursor: 

        for row in cursor.execute(query).fetchall(): 

            field = { 

                'name': row[1], 

                'type': row[2], 

                'not_null': row[3], 

                'default': row[4], 

                'indexed': row[5] 

            } 

            tables.append(field) 

    return tables 

 

 

def field_to_string( 

    name="", type="", not_null=False, default=None, indexed=False 

): 

    field_query = "%s %s" % (name, type) 

    if indexed: 

        field_query += " PRIMARY KEY" 

    return field_query 

 

 

def add_field(tablename, field): 

    query = "ALTER TABLE %s ADD COLUMN %s %s" % ( 

        tablename, field['name'], field['type'] 

    ) 

    with sql.db_cursor(PGA_DB) as cursor: 

        cursor.execute(query) 

 

 

def create_table(name, schema): 

    fields = ", ".join([field_to_string(**f) for f in schema]) 

    fields = "(%s)" % fields 

    query = "CREATE TABLE IF NOT EXISTS %s %s" % (name, fields) 

    LOGGER.debug("[PGAQuery] %s", query) 

    with sql.db_cursor(PGA_DB) as cursor: 

        cursor.execute(query) 

 

 

def migrate(table, schema): 

    existing_schema = get_schema(table) 

    migrated_fields = [] 

    if existing_schema: 

        columns = [col['name'] for col in existing_schema] 

        for field in schema: 

            if field['name'] not in columns: 

                migrated_fields.append(field['name']) 

                add_field(table, field) 

    else: 

        create_table(table, schema) 

    return migrated_fields 

 

 

def migrate_games(): 

    schema = [ 

        {'name': 'id', 'type': 'INTEGER', 'indexed': True}, 

        {'name': 'name', 'type': 'TEXT'}, 

        {'name': 'slug', 'type': 'TEXT'}, 

        {'name': 'platform', 'type': 'TEXT'}, 

        {'name': 'runner', 'type': 'TEXT'}, 

        {'name': 'executable', 'type': 'TEXT'}, 

        {'name': 'directory', 'type': 'TEXT'}, 

        {'name': 'lastplayed', 'type': 'INTEGER'}, 

        {'name': 'installed', 'type': 'INTEGER'}, 

        {'name': 'installer_slug', 'type': 'TEXT'}, 

    ] 

    return migrate('games', schema) 

 

 

def migrate_sources(): 

    schema = [ 

        {'name': 'id', 'type': 'INTEGER', 'indexed': True}, 

        {'name': 'uri', 'type': 'TEXT UNIQUE'}, 

    ] 

    return migrate('sources', schema) 

 

 

def syncdb(): 

    migrated = migrate_games() 

    if 'installed' in migrated: 

        set_installed_games() 

    migrate_sources() 

 

 

def set_installed_games(): 

    games = get_games() 

    for game in games: 

        if game['directory'] and os.path.exists(game['directory']): 

            sql.db_update(PGA_DB, 'games', 

                          {'installed': 1}, ('slug', game['slug'])) 

 

 

def get_games(name_filter=None, filter_installed=False): 

    """Get the list of every game in database.""" 

    with sql.db_cursor(PGA_DB) as cursor: 

        query = "select * from games" 

        params = () 

        filters = [] 

        if name_filter: 

            params = (name_filter, ) 

            filters.append("name LIKE ?") 

        if filter_installed: 

            filters.append("installed = 1") 

        if filters: 

            query += " WHERE " + " AND ".join([f for f in filters]) 

        rows = cursor.execute(query, params) 

        results = rows.fetchall() 

        column_names = [column[0] for column in cursor.description] 

    game_list = [] 

    for row in results: 

        game_info = {} 

        for index, column in enumerate(column_names): 

            game_info[column] = row[index] 

        game_list.append(game_info) 

    return game_list 

 

 

def get_game_by_slug(slug, field='slug'): 

    if field not in ('slug', 'installer_slug'): 

        raise ValueError("Invalid field name: %s", field) 

    game_result = sql.db_select(PGA_DB, "games", condition=(field, slug)) 

    if game_result: 

        return game_result[0] 

    return {} 

 

 

def add_game(name, **game_data): 

    """Adds a game to the PGA database.""" 

    game_data['name'] = name 

    if not 'slug' in game_data: 

        game_data['slug'] = slugify(name) 

    sql.db_insert(PGA_DB, "games", game_data) 

 

 

def add_or_update(name, runner, slug=None, **kwargs): 

    if not slug: 

        slug = slugify(name) 

    game = get_game_by_slug(slug) 

    kwargs['name'] = name 

    kwargs['runner'] = runner 

    kwargs['slug'] = slug 

    if game: 

        sql.db_update(PGA_DB, "games", kwargs, ('slug', slug)) 

    else: 

        add_game(**kwargs) 

 

 

def delete_game(slug): 

    """Deletes a game from the PGA""" 

    sql.db_delete(PGA_DB, "games", 'slug', slug) 

 

 

def set_uninstalled(slug): 

    sql.db_update(PGA_DB, 'games', {'installed': 0}, ('slug', slug)) 

 

 

def add_source(uri): 

    sql.db_insert(PGA_DB, "sources", {"uri": uri}) 

 

 

def delete_source(uri): 

    sql.db_delete(PGA_DB, "sources", 'uri', uri) 

 

 

def read_sources(): 

    with sql.db_cursor(PGA_DB) as cursor: 

        rows = cursor.execute("select uri from sources") 

        results = rows.fetchall() 

    return [row[0] for row in results] 

 

 

def write_sources(sources): 

    db_sources = read_sources() 

    for uri in db_sources: 

        if uri not in sources: 

            sql.db_delete(PGA_DB, "sources", 'uri', uri) 

    for uri in sources: 

        if uri not in db_sources: 

            sql.db_insert(PGA_DB, "sources", {'uri': uri}) 

 

 

def check_for_file(game, file_id): 

    for source in read_sources(): 

        if source.startswith("file://"): 

            source = source[7:] 

        else: 

            protocol = source[:7] 

            logger.warn( 

                "PGA source protocol {} not implemented".format(protocol) 

            ) 

            continue 

        if not os.path.exists(source): 

            logger.info("PGA source {} unavailable".format(source)) 

            continue 

        game_dir = os.path.join(source, game) 

        if not os.path.exists(game_dir): 

            continue 

        game_files = os.listdir(game_dir) 

        for game_file in game_files: 

            game_base, _ext = os.path.splitext(game_file) 

            if game_base == file_id: 

                return os.path.join(game_dir, game_file) 

    return False