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

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

#!/usr/bin/env python 

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

 

############################################################################### 

#  Copyright 2013 Kitware Inc. 

# 

#  Licensed under the Apache License, Version 2.0 ( the "License" ); 

#  you may not use this file except in compliance with the License. 

#  You may obtain a copy of the License at 

# 

#    http://www.apache.org/licenses/LICENSE-2.0 

# 

#  Unless required by applicable law or agreed to in writing, software 

#  distributed under the License is distributed on an "AS IS" BASIS, 

#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

#  See the License for the specific language governing permissions and 

#  limitations under the License. 

############################################################################### 

 

import datetime 

 

from .model_base import Model, ValidationException, AccessException 

from girder.constants import AccessType 

 

 

class Item(Model): 

    """ 

    Items are leaves in the data hierarchy. They can contain 0 or more 

    files within them, and can also contain arbitrary metadata. 

    """ 

 

    def initialize(self): 

        self.name = 'item' 

        self.ensureIndices(('folderId', 'name', 'lowerName')) 

        self.ensureTextIndex({ 

            'name': 10, 

            'description': 1 

        }) 

 

    def filter(self, item): 

        """ 

        Filter an item document for display to the user. 

        """ 

        keys = ['_id', 'size', 'updated', 'description', 'created', 

                'meta', 'creatorId', 'folderId', 'name', 'baseParentType', 

                'baseParentId'] 

 

        filtered = self.filterDocument(item, allow=keys) 

 

        return filtered 

 

    def validate(self, doc): 

        doc['name'] = doc['name'].strip() 

        doc['lowerName'] = doc['name'].lower() 

        doc['description'] = doc['description'].strip() 

 

        if not doc['name']: 

            raise ValidationException('Item name must not be empty.', 'name') 

 

        # Ensure unique name among sibling items and folders. If the desired 

        # name collides with an existing item or folder, we will append (n) 

        # onto the end of the name, incrementing n until the name is unique. 

        name = doc['name'] 

        n = 0 

        while True: 

            q = { 

                'name': name, 

                'folderId': doc['folderId'] 

            } 

            if '_id' in doc: 

                q['_id'] = {'$ne': doc['_id']} 

            dupItems = self.find(q, limit=1, fields=['_id']) 

 

            q = { 

                'parentId': doc['folderId'], 

                'name': name, 

                'parentCollection': 'folder' 

            } 

            dupFolders = self.model('folder').find(q, limit=1, fields=['_id']) 

            if dupItems.count() + dupFolders.count() == 0: 

                doc['name'] = name 

                break 

            else: 

                n += 1 

                name = '%s (%d)' % (doc['name'], n) 

 

        return doc 

 

    def load(self, id, level=AccessType.ADMIN, user=None, objectId=True, 

             force=False, fields=None, exc=False): 

        """ 

        We override Model.load to also do permission checking. 

 

        :param id: The id of the resource. 

        :type id: string or ObjectId 

        :param user: The user to check access against. 

        :type user: dict or None 

        :param level: The required access type for the object. 

        :type level: AccessType 

        :param force: If you explicity want to circumvent access 

                      checking on this resource, set this to True. 

        :type force: bool 

        """ 

        doc = Model.load(self, id=id, objectId=objectId, fields=fields, 

                         exc=exc) 

 

        if not force and doc is not None: 

            self.model('folder').load(doc['folderId'], level, user, objectId, 

                                      force, fields) 

 

        if doc is not None and 'baseParentType' not in doc: 

            pathFromRoot = self.parentsToRoot(doc, user=user, force=force) 

            baseParent = pathFromRoot[0] 

            doc['baseParentId'] = baseParent['object']['_id'] 

            doc['baseParentType'] = baseParent['type'] 

            self.save(doc) 

        if doc is not None and 'lowerName' not in doc: 

            self.save(doc) 

 

        return doc 

 

    def childFiles(self, item, limit=50, offset=0, sort=None): 

        """ 

        Generator function that yields child files in the item. 

 

        :param item: The parent item. 

        :param limit: Result limit. 

        :param offset: Result offset. 

        :param sort: The sort structure to pass to pymongo. 

        """ 

        q = { 

            'itemId': item['_id'] 

        } 

 

        cursor = self.model('file').find( 

            q, limit=limit, offset=offset, sort=sort) 

        for file in cursor: 

            yield file 

 

    def remove(self, item): 

        """ 

        Delete an item, and all references to it in the database. 

 

        :param item: The item document to delete. 

        :type item: dict 

        """ 

 

        # Delete all files in this item 

        files = self.model('file').find({ 

            'itemId': item['_id'] 

        }, limit=0) 

        for file in files: 

            self.model('file').remove(file) 

 

        # Delete pending uploads into this item 

        uploads = self.model('upload').find({ 

            'parentId': item['_id'], 

            'parentType': 'item' 

        }, limit=0) 

        for upload in uploads: 

            self.model('upload').remove(upload) 

 

        # Delete the item itself 

        Model.remove(self, item) 

 

    def textSearch(self, query, project, user=None, limit=20): 

        """ 

        Custom override of Model.textSearch to filter items by permissions 

        of the parent folder. 

        """ 

 

        # get the non-filtered search result from Model.textSearch 

        project['folderId'] = 1 

        results = Model.textSearch(self, query=query, project=project) 

 

        # list where we will store the filtered results 

        filtered = [] 

 

        # cache dictionary mapping folderId's to read permission 

        folderCache = {} 

 

        # loop through all results in the non-filtered list 

        for result in results: 

            # check if the folderId is cached 

            folderId = result['obj'].pop('folderId') 

 

            if folderId not in folderCache: 

                # if the folderId is not cached check for read permission 

                # and set the cache 

                folder = self.model('folder').load(folderId, force=True) 

                folderCache[folderId] = self.model('folder').hasAccess( 

                    folder, user=user, level=AccessType.READ) 

 

            if folderCache[folderId] is True: 

                filtered.append({ 

                    'name': result['obj']['name'], 

                    '_id': result['obj']['_id'] 

                }) 

 

            # once we have hit the requested limit, return 

            if len(filtered) >= limit: 

                break 

 

        return filtered 

 

    def filterResultsByPermission(self, cursor, user, level, limit, offset, 

                                  removeKeys=()): 

        """ 

        This method is provided as a convenience for filtering a result cursor 

        of items by permissions, based on the parent folder. The results in 

        the cursor must contain the folderId field. 

        """ 

        # Cache mapping folderIds -> access granted (bool) 

        folderCache = {} 

        count = skipped = 0 

        for result in cursor: 

            folderId = result['folderId'] 

 

            if folderId not in folderCache: 

                folder = self.model('folder').load(folderId, force=True) 

                folderCache[folderId] = self.model('folder').hasAccess( 

                    folder, user=user, level=level) 

 

            if folderCache[folderId] is True: 

                if skipped < offset: 

                    skipped += 1 

                else: 

                    yield result 

                    count += 1 

            if count == limit: 

                    break 

 

    def createItem(self, name, creator, folder, description=''): 

        """ 

        Create a new item. The creator will be given admin access to it. 

 

        :param name: The name of the item. 

        :type name: str 

        :param description: Description for the folder. 

        :type description: str 

        :param folder: The parent folder of the item. 

        :param creator: User document representing the creator of the group. 

        :type creator: dict 

        :returns: The item document that was created. 

        """ 

        now = datetime.datetime.now() 

 

        if not type(creator) is dict or '_id' not in creator: 

            # Internal error -- this shouldn't be called without a user. 

            raise Exception('Creator must be a user.') 

 

        if 'baseParentType' not in folder: 

            pathFromRoot = self.parentsToRoot({'folderId': folder['_id']}, 

                                              creator) 

            folder['baseParentType'] = pathFromRoot[0]['type'] 

            folder['baseParentId'] = pathFromRoot[0]['object']['_id'] 

 

        return self.save({ 

            'name': name, 

            'description': description, 

            'folderId': folder['_id'], 

            'creatorId': creator['_id'], 

            'baseParentType': folder['baseParentType'], 

            'baseParentId': folder['baseParentId'], 

            'created': now, 

            'updated': now, 

            'size': 0 

        }) 

 

    def updateItem(self, item): 

        """ 

        Updates an item. 

 

        :param item: The item document to update 

        :type item: dict 

        :returns: The item document that was edited. 

        """ 

        item['updated'] = datetime.datetime.now() 

 

        # Validate and save the collection 

        return self.save(item) 

 

    def setMetadata(self, item, metadata): 

        """ 

        Set metadata on an item.  A rest exception is thrown in the cases where 

        the metadata json object is badly formed, or if any of the metadata 

        keys contains a period ('.'). 

 

        :param item: The item to set the metadata on. 

        :type item: dict 

        :param metadata: A dictionary containing key-value pairs to add to 

                     the items meta field 

        :type metadata: dict 

        :returns: the item document 

        """ 

        if 'meta' not in item: 

            item['meta'] = dict() 

 

        # Add new metadata to existing metadata 

        item['meta'].update(metadata.items()) 

 

        # Remove metadata fields that were set to null (use items in py3) 

        toDelete = [k for k, v in item['meta'].iteritems() if v is None] 

        for key in toDelete: 

            del item['meta'][key] 

 

        item['updated'] = datetime.datetime.now() 

 

        # Validate and save the item 

        return self.save(item) 

 

    def parentsToRoot(self, item, user=None, force=False): 

        """ 

        Get the path to traverse to a root of the hierarchy. 

 

        :param item: The item whose root to find 

        :type item: dict 

        :returns: an ordered list of dictionaries from root to the current item 

        """ 

        curFolder = self.model('folder').load( 

            item['folderId'], user=user, level=AccessType.READ, force=force) 

        folderIdsToRoot = self.model('folder').parentsToRoot( 

            curFolder, user=user, level=AccessType.READ, force=force) 

        filteredFolder = self.model('folder').filter(curFolder, user) 

        folderIdsToRoot.append({'type': 'folder', 'object': filteredFolder}) 

        return folderIdsToRoot