#fix lib <processLibFiles>
This commit is contained in:
parent
c6ed600982
commit
ae2c5a1365
@ -68,8 +68,7 @@
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"local",
|
||||
"remote",
|
||||
"unknown"
|
||||
"remote"
|
||||
]
|
||||
},
|
||||
"hardware": {
|
||||
|
@ -59,11 +59,6 @@ async function launch(context: vscode.ExtensionContext) {
|
||||
hdlMonitor.start();
|
||||
// await vlogFast("e:/Project/Digial-IDE/TestWs/simulate/user/sim/tb_file/scc018ug_hd_rvt.v");
|
||||
|
||||
console.log(hdlParam);
|
||||
console.log(opeParam.prjInfo.library.hardware.common);
|
||||
console.log(opeParam.prjInfo.library.hardware.custom);
|
||||
|
||||
|
||||
MainOutput.report('Digital-IDE has launched, Version: 0.3.0');
|
||||
MainOutput.report('OS: ' + opeParam.os);
|
||||
}
|
||||
|
@ -158,8 +158,6 @@ class VlogHoverProvider implements vscode.HoverProvider {
|
||||
const normalResult = util.matchNormalSymbol(targetWord, scopeSymbols.symbols);
|
||||
if (normalResult) {
|
||||
const normalComment = await util.searchCommentAround(filePath, normalResult.range);
|
||||
console.log(normalResult);
|
||||
|
||||
const normalDesc = util.makeNormalDesc(normalResult);
|
||||
|
||||
content.appendCodeblock(normalDesc, HdlLangID.Verilog);
|
||||
|
@ -73,7 +73,7 @@ const PrjInfoDefaults: PrjInfoMeta = {
|
||||
|
||||
get library() {
|
||||
return {
|
||||
state: LibraryState.Unknown,
|
||||
state: LibraryState.Remote,
|
||||
hardware: {
|
||||
common: [],
|
||||
custom: []
|
||||
@ -240,7 +240,7 @@ class PrjInfo implements PrjInfoMeta {
|
||||
uniformPath = fspath.resolve(rootPath, path);
|
||||
}
|
||||
|
||||
uniformPath = toSlash(uniformPath);
|
||||
uniformPath = this.uniformisePath(uniformPath);
|
||||
|
||||
if (check) {
|
||||
if (fs.existsSync(uniformPath)) {
|
||||
@ -366,6 +366,9 @@ class PrjInfo implements PrjInfoMeta {
|
||||
}
|
||||
|
||||
private checkDirExist(dir: AbsPath) {
|
||||
if (dir === '') {
|
||||
return;
|
||||
}
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
@ -391,17 +394,17 @@ class PrjInfo implements PrjInfoMeta {
|
||||
const socCore = this._soc.core;
|
||||
if (socCore && socCore !== 'none') {
|
||||
hardwarePath = join(hardwarePath, 'Hardware');
|
||||
this.arch.software.src = join(softwarePath, 'src');
|
||||
this.arch.software.data = join(softwarePath, 'data');
|
||||
}
|
||||
|
||||
this.arch.prjPath = join(workspacePath, 'prj');
|
||||
this.arch.hardware.src = join(hardwarePath, 'src');
|
||||
this.arch.hardware.sim = join(hardwarePath, 'sim');
|
||||
this.arch.hardware.data = join(hardwarePath, 'data');
|
||||
|
||||
this.arch.software.src = join(softwarePath, 'src');
|
||||
this.arch.software.data = join(softwarePath, 'data');
|
||||
}
|
||||
|
||||
// // if path is '', set as workspace
|
||||
// if path is '', set as workspace
|
||||
this.setDefaultValue(this.arch.hardware, 'src', workspacePath);
|
||||
this.setDefaultValue(this.arch.hardware, 'sim', this.arch.hardware.src);
|
||||
this.setDefaultValue(this.arch.hardware, 'data', workspacePath);
|
||||
@ -409,7 +412,7 @@ class PrjInfo implements PrjInfoMeta {
|
||||
this.setDefaultValue(this.arch.software, 'data', workspacePath);
|
||||
this.setDefaultValue(this.arch, 'prjPath', workspacePath);
|
||||
|
||||
// check existence
|
||||
// check existence & make dir
|
||||
this.checkDirExist(this.arch.hardware.sim);
|
||||
this.checkDirExist(this.arch.hardware.src);
|
||||
this.checkDirExist(this.arch.hardware.data);
|
||||
|
@ -38,7 +38,7 @@ class HdlParam {
|
||||
* used only in initialization stage
|
||||
* @param hdlFile
|
||||
*/
|
||||
public addHdlFile(hdlFile: HdlFile) {
|
||||
public setHdlFile(hdlFile: HdlFile) {
|
||||
const path = hdlFile.path;
|
||||
this.pathToHdlFiles.set(path, hdlFile);
|
||||
}
|
||||
@ -300,6 +300,21 @@ class HdlParam {
|
||||
this.pathToHdlFiles.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
public addHdlFile(path: AbsPath) {
|
||||
path = hdlPath.toSlash(path);
|
||||
this.initHdlFiles([path]);
|
||||
|
||||
const moduleFile = this.getHdlFile(path);
|
||||
if (!moduleFile) {
|
||||
MainOutput.report('error happen when create moduleFile ' + path, ReportType.Warn);
|
||||
} else {
|
||||
moduleFile.makeInstance();
|
||||
for (const module of moduleFile.getAllHdlModules()) {
|
||||
module.solveUnhandleInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const hdlParam = new HdlParam();
|
||||
@ -672,7 +687,7 @@ class HdlFile {
|
||||
this.type = hdlFile.getHdlFileType(path);
|
||||
|
||||
// add to global hdlParam
|
||||
hdlParam.addHdlFile(this);
|
||||
hdlParam.setHdlFile(this);
|
||||
|
||||
// make nameToModule
|
||||
this.nameToModule = new Map<string, HdlModule>();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
|
||||
import { AbsPath, opeParam } from '../global';
|
||||
import { hdlDir, hdlFile, hdlPath } from '../hdlFs';
|
||||
@ -8,6 +9,8 @@ import { Path } from '../../resources/hdlParser';
|
||||
import { LibraryState } from '../global/enum';
|
||||
import { PathSet } from '../global/util';
|
||||
import { hdlIgnore } from './ignore';
|
||||
import { hdlParam } from '../hdlParser';
|
||||
import { refreshArchTree } from '../function/treeView';
|
||||
|
||||
interface LibFileChange {
|
||||
add: AbsPath[],
|
||||
@ -70,7 +73,7 @@ class LibManage {
|
||||
return hdlPath.join(opeParam.extensionPath, 'lib');
|
||||
}
|
||||
|
||||
public processLibFiles(library: Library): LibFileChange {
|
||||
public async processLibFiles(library: Library): Promise<LibFileChange> {
|
||||
this.next.list = this.getLibFiles();
|
||||
if (library.state === LibraryState.Local) {
|
||||
this.next.state = LibraryState.Local;
|
||||
@ -101,14 +104,16 @@ class LibManage {
|
||||
// copy file from remote to local
|
||||
const remotePathList = this.getLibFiles(LibraryState.Remote);
|
||||
this.remote2Local(remotePathList, (src, dist) => {
|
||||
hdlParam.deleteHdlFile(src);
|
||||
hdlFile.copyFile(src, dist);
|
||||
});
|
||||
|
||||
break;
|
||||
case 'local-remote':
|
||||
add.push(...this.next.list);
|
||||
|
||||
// delete local files (async)
|
||||
this.deleteLocalFiles();
|
||||
// delete local files & data structure in hdlParam (async)
|
||||
await this.deleteLocalFiles();
|
||||
|
||||
break;
|
||||
case 'local-local':
|
||||
@ -150,16 +155,32 @@ class LibManage {
|
||||
if (fs.existsSync(this.localLibPath)) {
|
||||
const needNotice = vscode.workspace.getConfiguration('prj.file.structure.notice');
|
||||
if (needNotice) {
|
||||
let select = await vscode.window.showWarningMessage(`Local Lib (${this.localLibPath}) will be removed.`, 'Yes', 'Cancel');
|
||||
if (select === "Yes") {
|
||||
hdlDir.rmdir(this.localLibPath);
|
||||
const res = await vscode.window.showWarningMessage(
|
||||
`Local Lib (${this.localLibPath}) will be removed.`,
|
||||
{ modal: true },
|
||||
{ title: 'Yes', value: true },
|
||||
{ title: 'No', value: false }
|
||||
);
|
||||
if (res?.value) {
|
||||
this.deleteLocalLib();
|
||||
}
|
||||
} else {
|
||||
hdlDir.rmdir(this.localLibPath);
|
||||
this.deleteLocalLib();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public deleteLocalLib() {
|
||||
const ignores = hdlIgnore.getIgnoreFiles();
|
||||
const hdlFileList = hdlFile.getHDLFiles([this.localLibPath], ignores);
|
||||
for (const path of hdlFileList) {
|
||||
hdlParam.deleteHdlFile(path);
|
||||
}
|
||||
|
||||
refreshArchTree();
|
||||
hdlDir.rmdir(this.localLibPath);
|
||||
}
|
||||
|
||||
public remote2Local(remotes: Path[], callback: (src: AbsPath, dist: AbsPath) => void) {
|
||||
const localLibPath = this.localLibPath;
|
||||
const sourceLibPath = this.sourceLibPath;
|
||||
|
@ -5,7 +5,7 @@ import * as fs from 'fs';
|
||||
import { AbsPath, MainOutput, opeParam, ReportType } from '../global';
|
||||
import { PathSet } from '../global/util';
|
||||
import { RawPrjInfo } from '../global/prjInfo';
|
||||
import { hdlFile, hdlPath } from '../hdlFs';
|
||||
import { hdlDir, hdlFile, hdlPath } from '../hdlFs';
|
||||
import { libManage } from './lib';
|
||||
import { hdlParam } from '../hdlParser';
|
||||
import { PlManage } from './PL';
|
||||
@ -73,12 +73,12 @@ class PrjManage {
|
||||
const rawPrjInfo = hdlFile.readJSON(propertyJsonPath) as RawPrjInfo;
|
||||
opeParam.mergePrjInfo(rawPrjInfo);
|
||||
} else {
|
||||
const createProperty = await vscode.window.showInformationMessage(
|
||||
const res = await vscode.window.showInformationMessage(
|
||||
"property.json is not detected, do you want to create one ?",
|
||||
{ title: 'Yes', value: true },
|
||||
{ title: 'No', value: false }
|
||||
);
|
||||
if (createProperty?.value) {
|
||||
if (res?.value) {
|
||||
vscode.commands.executeCommand('digital-ide.property-json.generate');
|
||||
}
|
||||
}
|
||||
@ -88,13 +88,13 @@ class PrjManage {
|
||||
* get all the hdl files that to be parsed in the project
|
||||
* @returns
|
||||
*/
|
||||
public getPrjHardwareFiles(): AbsPath[] {
|
||||
public async getPrjHardwareFiles(): Promise<AbsPath[]> {
|
||||
const searchPathSet = new PathSet();
|
||||
const prjInfo = opeParam.prjInfo;
|
||||
const hardwareInfo = prjInfo.arch.hardware;
|
||||
|
||||
// handle library first
|
||||
const fileChange = libManage.processLibFiles(prjInfo.library);
|
||||
const fileChange = await libManage.processLibFiles(prjInfo.library);
|
||||
MainOutput.report(`libManage finish process, add ${fileChange.add.length} files, del ${fileChange.del.length} files`, ReportType.Info);
|
||||
|
||||
// add possible folder to search
|
||||
@ -126,7 +126,7 @@ class PrjManage {
|
||||
await this.initOpeParam(context);
|
||||
MainOutput.report('finish initialise opeParam', ReportType.Info);
|
||||
|
||||
const hdlFiles = this.getPrjHardwareFiles();
|
||||
const hdlFiles = await this.getPrjHardwareFiles();
|
||||
MainOutput.report(`finish collect ${hdlFiles.length} hdl files`, ReportType.Info);
|
||||
|
||||
await hdlParam.initialize(hdlFiles);
|
||||
@ -142,6 +142,105 @@ class PrjManage {
|
||||
console.timeLog('launch');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async refreshPrjFolder() {
|
||||
// TODO : finish this
|
||||
// 无工程配置文件则直接退出
|
||||
if (!opeParam.prjInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prjInfo = opeParam.prjInfo;
|
||||
|
||||
// 如果是用户配置文件结构,检查并生成相关文件夹
|
||||
if (prjInfo.arch) {
|
||||
hdlDir.mkdir(prjInfo.arch.prjPath);
|
||||
const hardware = prjInfo.arch.hardware;
|
||||
const software = prjInfo.arch.software;
|
||||
|
||||
if (hardware) {
|
||||
hdlDir.mkdir(hardware.src);
|
||||
hdlDir.mkdir(hardware.sim);
|
||||
hdlDir.mkdir(hardware.data);
|
||||
}
|
||||
|
||||
if (software) {
|
||||
hdlDir.mkdir(software.src);
|
||||
hdlDir.mkdir(software.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 先直接创建工程文件夹
|
||||
hdlDir.mkdir(`${opeParam.workspacePath}/prj`);
|
||||
|
||||
// 初始化文件结构的路径
|
||||
const userPath = `${opeParam.workspacePath}/user`;
|
||||
const softwarePath = `${opeParam.workspacePath}/user/Software`;
|
||||
const hardwarePath = `${opeParam.workspacePath}/user/Hardware`;
|
||||
|
||||
let nextmode = "PL";
|
||||
// 再对源文件结构进行创建
|
||||
if (prjInfo.soc.core !== 'none') {
|
||||
nextmode = "LS";
|
||||
}
|
||||
|
||||
let currmode = "PL";
|
||||
if (fs.existsSync(softwarePath) || fs.existsSync(hardwarePath)) {
|
||||
currmode = "LS";
|
||||
}
|
||||
|
||||
if (currmode === nextmode) {
|
||||
const hardware = opeParam.prjInfo.ARCH.Hardware;
|
||||
const software = opeParam.prjInfo.ARCH.Software;
|
||||
|
||||
hdlDir.mkdir(hardware.src);
|
||||
hdlDir.mkdir(hardware.sim);
|
||||
hdlDir.mkdir(hardware.data);
|
||||
if (currmode === 'LS') {
|
||||
hdlDir.mkdir(software.src);
|
||||
hdlDir.mkdir(software.data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currmode === "PL" && nextmode === "LS") {
|
||||
hdlDir.mkdir(hardwarePath);
|
||||
hdlDir.readdir(userPath, true, (folder) => {
|
||||
if (folder !== "Hardware") {
|
||||
hdlDir.mvdir(folder, hardwarePath);
|
||||
}
|
||||
});
|
||||
|
||||
hdlDir.mkdir(`${softwarePath}/data`);
|
||||
hdlDir.mkdir(`${softwarePath}/src`);
|
||||
}
|
||||
else if (currmode === "LS" && nextmode === "PL") {
|
||||
const needNotice = vscode.workspace.getConfiguration().get('PRJ.file.structure.notice', true);
|
||||
if (needNotice) {
|
||||
let select = await vscode.window.showWarningMessage("Software will be deleted.", 'Yes', 'No');
|
||||
if (select === "Yes") {
|
||||
hdlDir.rmdir(softwarePath);
|
||||
}
|
||||
} else {
|
||||
hdlDir.rmdir(softwarePath);
|
||||
}
|
||||
|
||||
if (hdlFile.isExist(hardwarePath)) {
|
||||
hdlDir.readdir(hardwarePath, true, (folder) => {
|
||||
hdlDir.mvdir(folder, userPath);
|
||||
})
|
||||
|
||||
hdlDir.rmdir(hardwarePath);
|
||||
}
|
||||
|
||||
hdlDir.mkdir(`${userPath}/src`);
|
||||
hdlDir.mkdir(`${userPath}/sim`);
|
||||
hdlDir.mkdir(`${userPath}/data`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prjManage = new PrjManage();
|
||||
|
@ -2,15 +2,15 @@
|
||||
import assert = require('assert');
|
||||
import * as chokidar from 'chokidar';
|
||||
import * as vscode from 'vscode';
|
||||
import { refreshArchTree } from '../function/treeView';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { refreshArchTree } from '../function/treeView';
|
||||
import { AbsPath, MainOutput, opeParam, RelPath, ReportType } from '../global';
|
||||
import { isSameSet } from '../global/util';
|
||||
import { hdlFile, hdlPath } from '../hdlFs';
|
||||
import { hdlParam, HdlSymbol } from '../hdlParser';
|
||||
import { prjManage } from '../manager';
|
||||
import { libManage } from '../manager/lib';
|
||||
|
||||
import type { HdlMonitor } from './index';
|
||||
|
||||
enum Event {
|
||||
@ -53,31 +53,10 @@ abstract class BaseAction {
|
||||
}
|
||||
fSWatcher.on(Event.Unlink, path => this.unlink(path, m));
|
||||
}
|
||||
|
||||
public listenUnlinkDir(m: HdlMonitor) {
|
||||
const fSWatcher = this.selectFSWatcher(m);
|
||||
if (!fSWatcher) {
|
||||
MainOutput.report("FSWatcher hasn't been made!", ReportType.Error);
|
||||
return;
|
||||
}
|
||||
fSWatcher.on(Event.UnlinkDir, path => this.unlinkDir(path, m));
|
||||
}
|
||||
|
||||
// public listenAddDir(m: HdlMonitor) {
|
||||
// const fSWatcher = this.selectFSWatcher(m);
|
||||
// if (!fSWatcher) {
|
||||
// MainOutput.report("FSWatcher hasn't been made!", ReportType.Error);
|
||||
// return;
|
||||
// }
|
||||
// fSWatcher.on(Event.UnlinkDir, path => this.unlinkDir(path, m));
|
||||
// }
|
||||
|
||||
abstract selectFSWatcher(m: HdlMonitor): chokidar.FSWatcher | undefined;
|
||||
abstract change(path: AbsPath, m: HdlMonitor): Promise<void>;
|
||||
abstract add(path: AbsPath, m: HdlMonitor): Promise<void>;
|
||||
// abstract addDir(path: AbsPath, m: HdlMonitor): Promise<void>;
|
||||
abstract unlink(path: AbsPath, m: HdlMonitor): Promise<void>;
|
||||
abstract unlinkDir(path: AbsPath, m: HdlMonitor): Promise<void>;
|
||||
}
|
||||
|
||||
class HdlAction extends BaseAction {
|
||||
@ -86,30 +65,31 @@ class HdlAction extends BaseAction {
|
||||
}
|
||||
|
||||
async add(path: string, m: HdlMonitor): Promise<void> {
|
||||
console.log('HdlAction add');
|
||||
console.log('HdlAction add', path);
|
||||
|
||||
path = hdlPath.toSlash(path);
|
||||
// create corresponding moduleFile
|
||||
hdlParam.initHdlFiles([path]);
|
||||
|
||||
const moduleFile = hdlParam.getHdlFile(path);
|
||||
if (!moduleFile) {
|
||||
console.log('error happen when create moduleFile', path);
|
||||
} else {
|
||||
moduleFile.makeInstance();
|
||||
for (const module of moduleFile.getAllHdlModules()) {
|
||||
module.solveUnhandleInstance();
|
||||
}
|
||||
// check if it has been created
|
||||
if (hdlParam.hasHdlFile(path)) {
|
||||
MainOutput.report('<HdlAction Add Event> HdlFile ' + path + 'has been created', ReportType.Warn);
|
||||
return;
|
||||
}
|
||||
|
||||
// create corresponding moduleFile
|
||||
hdlParam.addHdlFile(path);
|
||||
|
||||
refreshArchTree();
|
||||
}
|
||||
|
||||
async unlink(path: string, m: HdlMonitor): Promise<void> {
|
||||
console.log('HdlAction unlink', path);
|
||||
|
||||
path = hdlPath.toSlash(path);
|
||||
hdlParam.deleteHdlFile(path);
|
||||
refreshArchTree();
|
||||
// operation to process unlink of hdl files can be deleted in <processLibFiles>
|
||||
if (fs.existsSync(path)) {
|
||||
path = hdlPath.toSlash(path);
|
||||
hdlParam.deleteHdlFile(path);
|
||||
refreshArchTree();
|
||||
}
|
||||
}
|
||||
|
||||
async unlinkDir(path: string, m: HdlMonitor): Promise<void> {
|
||||
@ -117,6 +97,11 @@ class HdlAction extends BaseAction {
|
||||
|
||||
}
|
||||
|
||||
async addDir(path: string, m: HdlMonitor): Promise<void> {
|
||||
console.log('HdlAction addDir', path);
|
||||
|
||||
}
|
||||
|
||||
async change(path: string, m: HdlMonitor): Promise<void> {
|
||||
console.log('HdlAction change');
|
||||
|
||||
@ -184,10 +169,6 @@ class PpyAction extends BaseAction {
|
||||
this.updateProperty(m);
|
||||
}
|
||||
|
||||
async unlinkDir(path: string, m: HdlMonitor): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
async change(path: string, m: HdlMonitor): Promise<void> {
|
||||
console.log('PpyAction change');
|
||||
assert.equal(hdlPath.toSlash(path), opeParam.propertyJsonPath);
|
||||
@ -210,7 +191,7 @@ class PpyAction extends BaseAction {
|
||||
|
||||
public async updateProperty(m: HdlMonitor) {
|
||||
const originalPathSet = this.getImportantPathSet();
|
||||
const originalHdlFiles = prjManage.getPrjHardwareFiles();
|
||||
const originalHdlFiles = await prjManage.getPrjHardwareFiles();
|
||||
const originalLibState = opeParam.prjInfo.library.state;
|
||||
|
||||
const rawPrjInfo = opeParam.getRawUserPrjInfo();
|
||||
@ -229,19 +210,17 @@ class PpyAction extends BaseAction {
|
||||
} else {
|
||||
// update hdl monitor
|
||||
const options: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, title: 'modify the project' };
|
||||
vscode.window.withProgress(options, async () => await this.refreshHdlMonitor(m, originalHdlFiles));
|
||||
await vscode.window.withProgress(options, async () => await this.refreshHdlMonitor(m, originalHdlFiles));
|
||||
}
|
||||
refreshArchTree();
|
||||
}
|
||||
|
||||
public async refreshHdlMonitor(m: HdlMonitor, originalHdlFiles: AbsPath[]) {
|
||||
m.remakeHdlMonitor();
|
||||
|
||||
// update pl
|
||||
console.log('current lib state', opeParam.prjInfo.library.state);
|
||||
const currentHdlFiles = prjManage.getPrjHardwareFiles();
|
||||
const currentHdlFiles = await prjManage.getPrjHardwareFiles();
|
||||
await this.updatePL(originalHdlFiles, currentHdlFiles);
|
||||
|
||||
refreshArchTree();
|
||||
}
|
||||
|
||||
public async updatePL(oldFiles: AbsPath[], newFiles: AbsPath[]) {
|
||||
|
@ -16,7 +16,7 @@ class HdlMonitor{
|
||||
this.monitorConfig = {
|
||||
persistent: true,
|
||||
usePolling: false,
|
||||
ignoreInitial: true,
|
||||
ignoreInitial: true
|
||||
};
|
||||
}
|
||||
|
||||
@ -85,6 +85,9 @@ class HdlMonitor{
|
||||
Event.hdlAction.listenAdd(this);
|
||||
Event.hdlAction.listenChange(this);
|
||||
Event.hdlAction.listenUnlink(this);
|
||||
|
||||
// Event.hdlAction.listenAddDir(this);
|
||||
// Event.hdlAction.listenUnlinkDir(this);
|
||||
}
|
||||
|
||||
public registerPpyMonitorListener() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user