1/* 2 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { Log } from '../utils/Log'; 17import { ViewType } from '../models/ViewType'; 18import { userFileModel } from './UserFileModel'; 19import { MediaConstants } from '../constants/MediaConstants'; 20import { selectManager } from '../common/SelectManager'; 21import photoAccessHelper from '@ohos.file.photoAccessHelper'; 22import fs from '@ohos.file.fs'; 23import { screenManager } from '../common/ScreenManager'; 24import image from '@ohos.multimedia.image'; 25 26const TAG = 'UserFileDataItem'; 27const STATUS_UNDEFINED = -1; 28const STATUS_FALSE = 0; 29const STATUS_TRUE = 1; 30 31export interface DateAdded { 32 dateAdded: number; 33 viewType: ViewType; 34} 35 36export class UserFileDataItem implements DateAdded { 37 viewType: ViewType = ViewType.ITEM; 38 readonly hashIndex: number; 39 index: number; 40 dateAdded: number = 0; 41 dateModified: number; 42 dateTaken: number; 43 status: number = MediaConstants.UNDEFINED; 44 isSelect: boolean = false; 45 uri: string; 46 orientation: number; 47 duration: number; 48 size: number; 49 width: number; // width changed by orientation 50 height: number; // height changed by orientation 51 imgWidth: number; // may be smaller than width, as width is too large 52 imgHeight: number; // may be smaller than height, as height is too large 53 path: string = ''; 54 title: string; 55 displayName: string; 56 mediaType: photoAccessHelper.PhotoType; 57 favouriteStatus: number = STATUS_UNDEFINED; 58 canRotate: number = STATUS_UNDEFINED; 59 selections: string = ''; 60 selectionArgs: string[] = []; 61 deviceId: string = ''; 62 longitude: string = ''; 63 latitude: string = ''; 64 shootingParams: string = ''; 65 fileAsset: photoAccessHelper.PhotoAsset = undefined; 66 defaultThumbnail: PixelMap = undefined; 67 thumbnailArray: Map<string, PixelMap> = new Map<string, PixelMap>(); 68 69 constructor(selections: string, selectionArgs: string[], deviceId: string, index: number) { 70 this.selections = selections; 71 this.selectionArgs = selectionArgs; 72 this.deviceId = deviceId; 73 this.hashIndex = index; 74 this.index = index; 75 } 76 77 getHashCode(): string { 78 // 时间线界面角度,收藏状态变更,都需要刷新界面;大图浏览界面角度变更,需要刷新界面 79 return this.status === MediaConstants.UNDEFINED ? 80 '' + this.hashIndex : 81 this.uri + this.favouriteStatus + ' ' + this.orientation + ' ' + this.isSelect; 82 } 83 84 async loadFileAsset(): Promise<photoAccessHelper.PhotoAsset> { 85 return await userFileModel.getMediaItemByUri(this.uri); 86 } 87 88 isLoad(): boolean { 89 if (this.status > MediaConstants.UNDEFINED) { 90 return true; 91 } 92 return false; 93 } 94 95 async load(isForce: boolean): Promise<void> { 96 Log.info(TAG, 'load ' + this.status); 97 if (this.status > (isForce ? MediaConstants.PART_LOADED : MediaConstants.UNDEFINED)) { 98 return; 99 } 100 let fileAsset = await this.loadFileAsset(); 101 if (fileAsset != null) { 102 this.update(fileAsset); 103 } 104 return; 105 } 106 107 async update(fileAsset: photoAccessHelper.PhotoAsset): Promise<void> { 108 this.fileAsset = fileAsset; 109 this.uri = fileAsset.uri; 110 this.displayName = fileAsset.displayName; 111 this.mediaType = fileAsset.photoType; 112 this.width = screenManager.getWinWidth(); 113 this.height = screenManager.getWinHeight(); 114 this.orientation = MediaConstants.ROTATE_NONE; 115 try { 116 this.orientation = fileAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION.toString()) as number; 117 Log.info(TAG, 'orientation ' + this.orientation); 118 } catch (err) { 119 Log.error(TAG, 'get orientation ' + JSON.stringify(err)); 120 } 121 try { 122 this.duration = fileAsset.get(photoAccessHelper.PhotoKeys.DURATION.toString()) as number; 123 Log.info(TAG, 'duration ' + this.duration); 124 } catch (err) { 125 Log.error(TAG, 'get duration ' + JSON.stringify(err)); 126 } 127 try { 128 if (this.orientation === MediaConstants.ROTATE_ONCE || this.orientation === MediaConstants.ROTATE_THIRD) { 129 this.width = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT.toString()) as number; 130 this.height = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH.toString()) as number; 131 } else { 132 this.width = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH.toString()) as number; 133 this.height = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT.toString()) as number; 134 } 135 Log.info(TAG, 'width ' + this.width); 136 Log.info(TAG, 'height ' + this.height); 137 } catch (err) { 138 Log.error(TAG, 'get width height ' + JSON.stringify(err)); 139 } 140 try { 141 this.title = fileAsset.get(photoAccessHelper.PhotoKeys.TITLE.toString()) as string; 142 Log.info(TAG, 'title ' + this.title); 143 } catch (err) { 144 Log.error(TAG, 'get title ' + JSON.stringify(err)); 145 } 146 try { 147 this.dateAdded = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_ADDED.toString()) as number * 1000; 148 this.dateModified = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_MODIFIED.toString()) as number * 1000; 149 this.dateTaken = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_TAKEN.toString()) as number * 1000; 150 Log.info(TAG, 'dateAdded ' + this.dateAdded); 151 } catch (err) { 152 Log.error(TAG, 'get date ' + JSON.stringify(err)); 153 } 154 try { 155 this.favouriteStatus = fileAsset.get(photoAccessHelper.PhotoKeys.FAVORITE.toString()) as boolean ? STATUS_TRUE : STATUS_FALSE 156 Log.info(TAG, 'favouriteStatus ' + this.favouriteStatus); 157 } catch (err) { 158 Log.error(TAG, 'get favouriteStatus ' + JSON.stringify(err)); 159 } 160 try { 161 this.size = fileAsset.get(photoAccessHelper.PhotoKeys.SIZE.toString()) as number; 162 Log.info(TAG, 'size ' + this.size); 163 } catch (err) { 164 Log.error(TAG, 'get favouriteStatus ' + JSON.stringify(err)); 165 } 166 let size = { width: MediaConstants.DEFAULT_SIZE, height: MediaConstants.DEFAULT_SIZE }; 167 if (fileAsset != null && this.defaultThumbnail == undefined) { 168 try { 169 this.defaultThumbnail = await this.fileAsset.getThumbnail(size); 170 } catch (err) { 171 Log.error(TAG, 'getThumbnail error: ' + JSON.stringify(err)); 172 } 173 } 174 this.isSelect = selectManager.isSelect(this.uri, this.isSelect); 175 this.imgWidth = this.width; 176 this.imgHeight = this.height; 177 if (this.width > 0 && this.height > 0) { 178 this.status = MediaConstants.LOADED; 179 } else { 180 this.status = MediaConstants.PART_LOADED; 181 } 182 this.getExif(fileAsset); 183 } 184 185 async getExif(fileAsset: photoAccessHelper.PhotoAsset): Promise<void> { 186 let file = await fs.open(fileAsset.uri); 187 let imageSourceApi: image.ImageSource = image.createImageSource(file.fd); 188 let light = ''; 189 let fNumber = ''; 190 let photographicSensitivity = ''; 191 192 await Promise.all([ 193 imageSourceApi.getImageProperty(image.PropertyKey.GPS_LATITUDE).then(data => this.latitude = data) 194 .catch((err) => Log.error(TAG, 'getImageProperty latitude error: ' + JSON.stringify(err))), 195 imageSourceApi.getImageProperty(image.PropertyKey.GPS_LONGITUDE).then(data => this.longitude = data) 196 .catch((err) => Log.error(TAG, 'getImageProperty longitude error: ' + JSON.stringify(err))), 197 imageSourceApi.getImageProperty(image.PropertyKey.EXPOSURE_TIME).then(data => light = data) 198 .catch((err) => Log.error(TAG, 'getImageProperty light error: ' + JSON.stringify(err))), 199 imageSourceApi.getImageProperty(image.PropertyKey.F_NUMBER).then(data => fNumber = data) 200 .catch((err) => Log.error(TAG, 'getImageProperty fNumber error: ' + JSON.stringify(err))), 201 imageSourceApi.getImageProperty(image.PropertyKey.PHOTOGRAPHIC_SENSITIVITY) 202 .then(data => photographicSensitivity = data) 203 .catch((err) => Log.error(TAG, 'getImageProperty photographicSensitivity error: ' + JSON.stringify(err))), 204 ]); 205 206 this.shootingParams = 'F_NUMBER ' + fNumber + 207 ' PHOTOGRAPHIC_SENSITIVITY ' + photographicSensitivity + 208 ' EXPOSURE_TIME ' + light; 209 fs.closeSync(file.fd); 210 Log.info(TAG, `getExif end, latitude: ${this.latitude}, longitude: ${this.longitude}, light: ${light}, 211 fNumber: ${fNumber}, photographicSensitivity: ${photographicSensitivity}`); 212 } 213 214 async getThumbnail(width: number, height: number): Promise<PixelMap> { 215 Log.debug(TAG, 'getThumbnail ' + this.status); 216 if (this.status !== MediaConstants.LOADED && this.status !== MediaConstants.PART_LOADED) { 217 Log.warn(TAG, 'getThumbnail fail as status: ' + this.status); 218 return undefined; 219 } 220 if (width === MediaConstants.DEFAULT_SIZE && height === MediaConstants.DEFAULT_SIZE) { 221 return this.defaultThumbnail; 222 } 223 let newThumbnail: PixelMap = undefined; 224 let size = { width: width, height: height }; 225 let cacheThumbnail = this.thumbnailArray.get(width.toString() + height.toString()); 226 if (cacheThumbnail != null) { 227 return cacheThumbnail; 228 } 229 if (this.fileAsset != undefined) { 230 try { 231 newThumbnail = await this.fileAsset.getThumbnail(size); 232 this.thumbnailArray.set(width.toString() + height.toString(), newThumbnail); 233 } catch (err) { 234 Log.error(TAG, 'getThumbnail error: ' + JSON.stringify(err)); 235 } 236 } 237 return newThumbnail; 238 } 239 240 getAlt(): Resource { 241 if (this.mediaType === photoAccessHelper.PhotoType.VIDEO) { 242 return $r('app.media.alt_video_placeholder'); 243 } else { 244 return $r('app.media.alt_placeholder'); 245 } 246 } 247 248 setSelect(isSelect: boolean): void { 249 this.isSelect = isSelect; 250 selectManager.setSelect(this.uri, this.isSelect); 251 } 252 253 async onDelete(): Promise<boolean> { 254 try { 255 await userFileModel.deleteOne(this.uri); 256 selectManager.deleteSelect(this.uri); 257 this.status = MediaConstants.TRASHED; 258 return true; 259 } catch (err) { 260 Log.error(TAG, 'onDelete ' + this.index + ' error: ' + JSON.stringify(err)); 261 return false; 262 } 263 } 264 265 async addToAlbum(albumUri:string): Promise<boolean> { 266 try { 267 await userFileModel.addPhotoToAlbumByUserFileMgr(albumUri, this.uri); 268 selectManager.deleteSelect(this.uri); 269 this.status = MediaConstants.TRASHED; 270 return true; 271 } catch (err) { 272 Log.error(TAG, 'addToAlbum ' + this.index + ' error: ' + JSON.stringify(err)); 273 return false; 274 } 275 } 276 277 isDeleted(): boolean { 278 return this.status === MediaConstants.TRASHED; 279 } 280 281 async isFavor(): Promise<boolean> { 282 if (this.favouriteStatus === STATUS_UNDEFINED) { 283 let fileAsset = await this.loadFileAsset(); 284 try { 285 this.favouriteStatus = (fileAsset.get(photoAccessHelper.PhotoKeys.FAVORITE.toString()) as boolean) ? STATUS_TRUE : STATUS_FALSE; 286 } catch (err) { 287 Log.error(TAG, 'isFavor error: ' + JSON.stringify(err)); 288 } 289 } 290 return this.favouriteStatus === STATUS_TRUE; 291 } 292 293 async isVideo(): Promise<boolean> { 294 let fileAsset = await this.loadFileAsset(); 295 this.mediaType = fileAsset.photoType; 296 if (this.mediaType === photoAccessHelper.PhotoType.VIDEO) { 297 return true; 298 } 299 return false; 300 } 301 302 async setFavor(): Promise<boolean> { 303 let status = !(await this.isFavor()); 304 try { 305 let fileAsset = await this.loadFileAsset(); 306 await fileAsset.setFavorite(status); 307 this.favouriteStatus = status ? STATUS_TRUE : STATUS_FALSE; 308 return true; 309 } catch (err) { 310 return false; 311 } 312 } 313 314 async setName(name: string): Promise<void> { 315 let fileAsset = await this.loadFileAsset(); 316 let displayName = fileAsset.displayName; 317 let index = displayName.lastIndexOf('.'); 318 displayName = name + displayName.slice(index); 319 this.displayName = displayName; 320 this.title = name; 321 try { 322 fileAsset.set(photoAccessHelper.PhotoKeys.TITLE.toString(), name); 323 await fileAsset.commitModify(); 324 } catch (err) { 325 Log.error(TAG, 'setName error: ' + JSON.stringify(err)); 326 } 327 } 328} 329