截图使用session,wda挂了之后重建nkAgent卡死
							parent
							
								
									f5dda0f46c
								
							
						
					
					
						commit
						e5d1e6e68b
					
				|  | @ -27,6 +27,14 @@ public abstract class IosDeviceInitThread extends Thread { | ||||||
| 
 | 
 | ||||||
|     protected AppleDevice appleDevice; |     protected AppleDevice appleDevice; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * wda进程信息 | ||||||
|  |      */ | ||||||
|  |     protected Process process; | ||||||
|  | 
 | ||||||
|  |     protected boolean isFBServerReady = false; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     private int frame = 12; |     private int frame = 12; | ||||||
| 
 | 
 | ||||||
|     public IosDeviceInitThread(PhoneEntity phone, AppleDevice appleDevice) { |     public IosDeviceInitThread(PhoneEntity phone, AppleDevice appleDevice) { | ||||||
|  | @ -36,22 +44,23 @@ public abstract class IosDeviceInitThread extends Thread { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     protected void createNKAgent() { |     protected synchronized void createNKAgent(String tag) { | ||||||
|  |         logger.debug("createNKAgent:{}",tag); | ||||||
|         nkAgent = new NKAgent(this.appleDevice); |         nkAgent = new NKAgent(this.appleDevice); | ||||||
|  |         nkAgent.setOnDisconnectListener(() -> { | ||||||
|  |             if(!isFBServerReady) return; | ||||||
|  |             logger.warn("设备【{}】的nkAgent失效了,需要重新创建...........", phone.getUdid()); | ||||||
|  |             createNKAgent("回调重试"); | ||||||
|  |             logger.warn("设备【{}】的nkAgent重新创建完成...........", phone.getUdid()); | ||||||
|  |         }); | ||||||
|         boolean nkAgentReady = false; |         boolean nkAgentReady = false; | ||||||
|         int reTryTime = 1; |         int reTryTime = 1; | ||||||
|         do { |         while (!nkAgentReady && reTryTime <= 5 && isFBServerReady) { | ||||||
|             logger.debug("尝试第{}次连接设备【{}】的nkAgent", reTryTime, phone.getUdid()); |             logger.debug("尝试第{}次连接设备【{}】的nkAgent", reTryTime, phone.getUdid()); | ||||||
|             try { |             try { | ||||||
|                 nkAgent.connect(); |                 nkAgent.connect(); | ||||||
|                 nkAgentReady = nkAgent.getStatus(); |                 nkAgentReady = nkAgent.getStatus(); | ||||||
|                 logger.debug("设备【{}】的nkAgent连接完成,结果:{}", phone.getUdid(), nkAgentReady); |                 logger.debug("设备【{}】的nkAgent连接完成,结果:{}", phone.getUdid(), nkAgentReady); | ||||||
|                 if (nkAgentReady) { |  | ||||||
|                     logger.debug("设置设备【{}】的帧数为:{}", phone.getUdid(), frame); |  | ||||||
|                     nkAgent.setMJPEGServerRate(frame); |  | ||||||
|                 } else { |  | ||||||
|                     logger.error("第{}次连接设备【{}】的agent不成功,等待下一次......",reTryTime, phone.getUdid()); |  | ||||||
|                 } |  | ||||||
|             } catch (IOException e) { |             } catch (IOException e) { | ||||||
|                 logger.error("第{}次连接设备【{}】的agent出错", reTryTime, phone.getUdid(), e); |                 logger.error("第{}次连接设备【{}】的agent出错", reTryTime, phone.getUdid(), e); | ||||||
|                 try { |                 try { | ||||||
|  | @ -61,25 +70,23 @@ public abstract class IosDeviceInitThread extends Thread { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             reTryTime++; |             reTryTime++; | ||||||
|         } while (!nkAgentReady && reTryTime <= 5); |         } | ||||||
|         if (!nkAgentReady) { |         if (!nkAgentReady) { | ||||||
|  |             nkAgent.setOnDisconnectListener(null); | ||||||
|  |             nkAgent = null; | ||||||
|  |             if (null != process) { | ||||||
|  |                 process.destroy(); | ||||||
|  |             } | ||||||
|  |             isFBServerReady = false; | ||||||
|             throw new ExecuteException(String.format("无法创建设备【%s】nkAgent", phone.getUdid())); |             throw new ExecuteException(String.format("无法创建设备【%s】nkAgent", phone.getUdid())); | ||||||
|         } |  | ||||||
|         logger.debug("设置设备【{}】的nkAgent掉线之后的监听器................", phone.getUdid()); |  | ||||||
|         nkAgent.setOnDisconnectListener(() -> { |  | ||||||
|             synchronized (nkAgent) { |  | ||||||
|                 if (!nkAgent.getStatus()) { |  | ||||||
|                     logger.warn("设备【{}】的nkAgent失效了,需要重新创建...........", phone.getUdid()); |  | ||||||
|                     IOSDeviceManager.getInstance().getIosInitThread(phone.getUdid()).createNKAgent(); |  | ||||||
|                     logger.warn("设备【{}】的nkAgent重新创建完成...........", phone.getUdid()); |  | ||||||
|         }else{ |         }else{ | ||||||
|                     logger.warn("设备【{}】的nkAgent失效了,但是已经创建好了,不再创建了...........", phone.getUdid()); |             logger.debug("设置设备【{}】的帧数为:{}", phone.getUdid(), frame); | ||||||
|  |             nkAgent.setMJPEGServerRate(frame); | ||||||
|         } |         } | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         logger.debug("设备【{}】的nkAgent连接完毕................", phone.getUdid()); |         logger.debug("设备【{}】的nkAgent连接完毕................", phone.getUdid()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     public NKAgent getNkAgent() { |     public NKAgent getNkAgent() { | ||||||
|         return nkAgent; |         return nkAgent; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import net.northking.cctp.upperComputer.driver.ios.NKAgent; | ||||||
| import net.northking.cctp.upperComputer.driver.ios.command.data.ScreenInfoData; | import net.northking.cctp.upperComputer.driver.ios.command.data.ScreenInfoData; | ||||||
| import net.northking.cctp.upperComputer.driver.usbmuxd.AppleDevice; | import net.northking.cctp.upperComputer.driver.usbmuxd.AppleDevice; | ||||||
| import net.northking.cctp.upperComputer.entity.PhoneEntity; | import net.northking.cctp.upperComputer.entity.PhoneEntity; | ||||||
|  | import net.northking.cctp.upperComputer.exception.ExecuteException; | ||||||
| import net.northking.cctp.upperComputer.utils.SpringUtils; | import net.northking.cctp.upperComputer.utils.SpringUtils; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  | @ -31,11 +32,6 @@ public class MacIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|      */ |      */ | ||||||
|     public static final int IOS_CORE_DEVICE_VERSION = 17; |     public static final int IOS_CORE_DEVICE_VERSION = 17; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * xcodebuild进程信息 |  | ||||||
|      */ |  | ||||||
|     private Process process = null; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * xcodebuild的退出码 |      * xcodebuild的退出码 | ||||||
|      */ |      */ | ||||||
|  | @ -47,10 +43,6 @@ public class MacIosDeviceInitThread extends IosDeviceInitThread { | ||||||
| 
 | 
 | ||||||
|     private OutputLineCallback outputLineCallback; |     private OutputLineCallback outputLineCallback; | ||||||
| 
 | 
 | ||||||
|     private boolean isFBServerReady = false; |  | ||||||
| 
 |  | ||||||
|     private final Object waitFbServerReadyLock = new Object(); |  | ||||||
| 
 |  | ||||||
|     public MacIosDeviceInitThread(PhoneEntity phoneEntity, AppleDevice device) { |     public MacIosDeviceInitThread(PhoneEntity phoneEntity, AppleDevice device) { | ||||||
|         super(phoneEntity, device); |         super(phoneEntity, device); | ||||||
|         init(); |         init(); | ||||||
|  | @ -112,17 +104,19 @@ public class MacIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|                     if (wdaConfig.isPrintWdaOutput()) { |                     if (wdaConfig.isPrintWdaOutput()) { | ||||||
|                         logger.debug("WDA-" + appleDevice.getConnectionDetail().serialNumber + "->" + line); |                         logger.debug("WDA-" + appleDevice.getConnectionDetail().serialNumber + "->" + line); | ||||||
|                     } |                     } | ||||||
|                     if (!isFBServerReady && line.contains("ServerURLHere")) { |  | ||||||
|                         isFBServerReady = true; |  | ||||||
|                         synchronized (waitFbServerReadyLock) { |  | ||||||
|                             waitFbServerReadyLock.notifyAll(); |  | ||||||
|                         } |  | ||||||
|                         createNKAgent(); |  | ||||||
|                         apiPortReady(); |  | ||||||
|                     } |  | ||||||
|                     if (logOutput != null) { |  | ||||||
|                     logOutput.write(line.getBytes()); |                     logOutput.write(line.getBytes()); | ||||||
|                     logOutput.write(10); |                     logOutput.write(10); | ||||||
|  |                     if (!isFBServerReady && line.contains("ServerURLHere")) { | ||||||
|  |                         logger.debug("发现WDA启动完成信号"); | ||||||
|  |                         isFBServerReady = true; | ||||||
|  |                         try { | ||||||
|  |                             createNKAgent("WDA启动"); | ||||||
|  |                         } catch (ExecuteException e) { | ||||||
|  |                             logger.error("WDA启动后创建NKAgent失败", e); | ||||||
|  |                             process.destroy(); | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                         apiPortReady(); | ||||||
|                     } |                     } | ||||||
|                     if (outputLineCallback != null) { |                     if (outputLineCallback != null) { | ||||||
|                         try { |                         try { | ||||||
|  | @ -150,20 +144,22 @@ public class MacIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             isFBServerReady = false; | ||||||
|  |             if (nkAgent != null) { | ||||||
|  |                 nkAgent.setOnDisconnectListener(null); | ||||||
|  |             } | ||||||
|             try { |             try { | ||||||
|                 exitCode = process.waitFor(); |                 exitCode = process.waitFor(); | ||||||
|             } catch (InterruptedException ignored) { |             } catch (InterruptedException ignored) { | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             if (logOutput != null) { |  | ||||||
|             try { |             try { | ||||||
|                 logOutput.close(); |                 logOutput.close(); | ||||||
|             } catch (IOException e) { |             } catch (IOException e) { | ||||||
|                 logger.warn("关闭WDA独立日志流时发生IO异常,路径:{}", logFileName, e); |                 logger.warn("关闭WDA独立日志流时发生IO异常,路径:{}", logFileName, e); | ||||||
|             } |             } | ||||||
|             } |             IOSDeviceManager.getInstance().publishDeviceAgentDead(phone.getUdid()); | ||||||
|             logger.debug("设备【{}】的wda退出了,重启一次......................", appleDevice.getConnectionDetail().serialNumber); |             logger.debug("设备【{}】的wda退出了,重启一次......................", appleDevice.getConnectionDetail().serialNumber); | ||||||
|             isFBServerReady = false; |  | ||||||
|         } |         } | ||||||
|         logger.debug("设备【{}】的wda退出了,不在重启了......................", appleDevice.getConnectionDetail().serialNumber); |         logger.debug("设备【{}】的wda退出了,不在重启了......................", appleDevice.getConnectionDetail().serialNumber); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -35,8 +35,6 @@ public class WindowsAndLinuxIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|         super(phone,device); |         super(phone,device); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private Process wdaProcess; |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public void run() { |     public void run() { | ||||||
|         logger.debug("不是mac作为上位机,用tidevice启动wda"); |         logger.debug("不是mac作为上位机,用tidevice启动wda"); | ||||||
|  | @ -91,13 +89,12 @@ public class WindowsAndLinuxIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|             FileOutputStream fileOutputStream = null; |             FileOutputStream fileOutputStream = null; | ||||||
|             try { |             try { | ||||||
|                 fileOutputStream = new FileOutputStream(wdaLogFile); |                 fileOutputStream = new FileOutputStream(wdaLogFile); | ||||||
|                 this.wdaProcess = processBuilder.start(); |                 this.process = processBuilder.start(); | ||||||
|                 if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Windows")) { |                 if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Windows")) { | ||||||
|                     input = new LineNumberReader(new InputStreamReader(this.wdaProcess.getInputStream(),"GBK")); |                     input = new LineNumberReader(new InputStreamReader(this.process.getInputStream(),"GBK")); | ||||||
|                 } else if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Linux")) { |                 } else if (UpperComputerManager.getInstance().getOperatingSystem().startsWith("Linux")) { | ||||||
|                     input = new LineNumberReader(new InputStreamReader(this.wdaProcess.getInputStream(),"UTF-8")); |                     input = new LineNumberReader(new InputStreamReader(this.process.getInputStream(),"UTF-8")); | ||||||
|                 } |                 } | ||||||
|                 input = new LineNumberReader(new InputStreamReader(this.wdaProcess.getInputStream())); |  | ||||||
|                 String line = null; |                 String line = null; | ||||||
|                 while (null != (line = input.readLine())) { |                 while (null != (line = input.readLine())) { | ||||||
|                     if (line.contains("NKLog:")) { |                     if (line.contains("NKLog:")) { | ||||||
|  | @ -106,7 +103,8 @@ public class WindowsAndLinuxIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|                     } |                     } | ||||||
|                     if (line.contains(SUCCESS_FLAG)) { |                     if (line.contains(SUCCESS_FLAG)) { | ||||||
|                         logger.info("设备【{}】的wda程序启动成功,开启nkAgent。。。。。。。。。", phone.getUdid()); |                         logger.info("设备【{}】的wda程序启动成功,开启nkAgent。。。。。。。。。", phone.getUdid()); | ||||||
|                         createNKAgent(); |                         isFBServerReady = true; | ||||||
|  |                         createNKAgent("非macOS启动wda第一创建NKAgent"); | ||||||
|                         logger.info("设备【{}】即将启动8100端口代理。。。。。。。。。", phone.getUdid()); |                         logger.info("设备【{}】即将启动8100端口代理。。。。。。。。。", phone.getUdid()); | ||||||
|                         apiProxyThread = new Device8100WatchThread(phone); |                         apiProxyThread = new Device8100WatchThread(phone); | ||||||
|                         apiProxyThread.start(); |                         apiProxyThread.start(); | ||||||
|  | @ -125,7 +123,7 @@ public class WindowsAndLinuxIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|             } |             } | ||||||
|             int code = 0; |             int code = 0; | ||||||
|             try { |             try { | ||||||
|                 code = this.wdaProcess.waitFor(); |                 code = this.process.waitFor(); | ||||||
|             } catch (InterruptedException e) { |             } catch (InterruptedException e) { | ||||||
|                 logger.warn("wda线程中断了。。。。"); |                 logger.warn("wda线程中断了。。。。"); | ||||||
|                 interrupt(); |                 interrupt(); | ||||||
|  | @ -254,7 +252,7 @@ public class WindowsAndLinuxIosDeviceInitThread extends IosDeviceInitThread { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void exitWdaAndProxy() { |     public void exitWdaAndProxy() { | ||||||
|         this.wdaProcess.destroyForcibly(); |         this.process.destroy(); | ||||||
|         interrupt(); |         interrupt(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -201,7 +201,8 @@ public class PacketTransfer { | ||||||
|         return connected; |         return connected; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void onDisconnected() { |     private synchronized void onDisconnected() { | ||||||
|  |         if (!connected) return; | ||||||
|         connected = false; |         connected = false; | ||||||
|         sendHandler.stop(); |         sendHandler.stop(); | ||||||
|         receiveHandler.stop(); |         receiveHandler.stop(); | ||||||
|  |  | ||||||
|  | @ -50,7 +50,7 @@ import java.net.MalformedURLException; | ||||||
| import java.net.URL; | import java.net.URL; | ||||||
| import java.util.*; | import java.util.*; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.*; | ||||||
| 
 | 
 | ||||||
| import static io.appium.java_client.remote.AndroidMobileCapabilityType.RESET_KEYBOARD; | import static io.appium.java_client.remote.AndroidMobileCapabilityType.RESET_KEYBOARD; | ||||||
| import static io.appium.java_client.remote.AndroidMobileCapabilityType.UNICODE_KEYBOARD; | import static io.appium.java_client.remote.AndroidMobileCapabilityType.UNICODE_KEYBOARD; | ||||||
|  | @ -74,6 +74,10 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService { | ||||||
| 
 | 
 | ||||||
|     private ConcurrentHashMap<String, AndroidAgent> screenShotAgentMap = new ConcurrentHashMap<>(); |     private ConcurrentHashMap<String, AndroidAgent> screenShotAgentMap = new ConcurrentHashMap<>(); | ||||||
| 
 | 
 | ||||||
|  |     private ConcurrentHashMap<String, AndroidAgentSession> screenShotAgentSessionMap = new ConcurrentHashMap<>(); | ||||||
|  | 
 | ||||||
|  |     private ExecutorService service = Executors.newFixedThreadPool(10); | ||||||
|  | 
 | ||||||
|     private Adb adb; |     private Adb adb; | ||||||
| 
 | 
 | ||||||
|     @Autowired |     @Autowired | ||||||
|  | @ -1018,30 +1022,57 @@ public class AndroidDebuggerServiceImpl extends AbstractDebuggerService { | ||||||
| 
 | 
 | ||||||
|     private File getScreenShotInCommand(String deviceId) { |     private File getScreenShotInCommand(String deviceId) { | ||||||
|         File tmpPicFile = null; |         File tmpPicFile = null; | ||||||
|         try { |         byte[] screenShotData = null; | ||||||
|             AndroidAgent androidAgent = screenShotAgentMap.get(deviceId); |         Future<byte[]> submit = service.submit(new Callable<byte[]>() { | ||||||
|             if (null == androidAgent) { |             @Override | ||||||
|                 AdbDevice currentDevice = null; |             public byte[] call() throws Exception { | ||||||
|                 ArrayList<AdbDevice> adbDevices = adb.listDevices(); |                 AndroidAgentSession agentSession = screenShotAgentSessionMap.get(deviceId); | ||||||
|                 if (!CollectionUtils.isEmpty(adbDevices)) { |                 if (null == agentSession || !agentSession.isState()) { | ||||||
|                     for (AdbDevice adbDevice : adbDevices) { |                     AdbDevice currentDevice = AndroidDeviceManager.getInstance().getCurrentDevice(deviceId); | ||||||
|                         if (deviceId.equals(adbDevice.getSerial())) { |  | ||||||
|                             currentDevice = adbDevice; |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                     if (null == currentDevice) { |                     if (null == currentDevice) { | ||||||
|                     logger.warn("设备【{}】不在线。。。。。",deviceId); |                         throw new ExecuteException("当前设备不在线"); | ||||||
|                     throw new ExecuteException("截图失败,当前设备不在线"); |  | ||||||
|                     } |                     } | ||||||
|                 logger.warn("设备【{}】没有androidAgent创建一个。。。。。",deviceId); |                     agentSession = new AndroidAgentSession(currentDevice, false); | ||||||
|                 androidAgent = new AndroidAgent(adb, currentDevice); |                     agentSession.start(); | ||||||
|                 screenShotAgentMap.put(deviceId, androidAgent); |                     screenShotAgentSessionMap.put(deviceId, agentSession); | ||||||
|                 } else { |                 } else { | ||||||
|                 logger.warn("设备【{}】已经存在androidAgent。。。。。",deviceId); |                     String send = agentSession.send(new EchoCommand("hello"));    //检查
 | ||||||
|  |                     if (!"hello".equals(send)) { | ||||||
|  |                         logger.warn("设备【{}】当前使用的截图的session失效了,重新创建一个。。。。。", deviceId); | ||||||
|  |                         agentSession.closeSilence(); | ||||||
|  |                         AdbDevice currentDevice = AndroidDeviceManager.getInstance().getCurrentDevice(deviceId); | ||||||
|  |                         if (null == currentDevice) { | ||||||
|  |                             return null; | ||||||
|                         } |                         } | ||||||
|             byte[] screenShotData = androidAgent.takeScreenshot(); |                         agentSession = new AndroidAgentSession(currentDevice, false); | ||||||
|  |                         agentSession.start(); | ||||||
|  |                         screenShotAgentSessionMap.put(deviceId, agentSession); | ||||||
|  |                     } else { | ||||||
|  |                         logger.debug("设备【{}】当前使用的截图的session还能用,继续使用。。。。。", deviceId); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 byte[] temData = agentSession.send(TakeScreenshotCommand.getInstance()); | ||||||
|  |                 return temData; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         try { | ||||||
|  |             screenShotData = submit.get(5, TimeUnit.SECONDS); | ||||||
|  |         } catch (InterruptedException e) { | ||||||
|  |             logger.debug("设备【{}】截图的时候被中断了。。。。。", deviceId); | ||||||
|  |             return null; | ||||||
|  |         } catch (ExecutionException e) { | ||||||
|  |             logger.error("设备【{}】截图的时候报错了。。。。。", deviceId,e); | ||||||
|  |             return null; | ||||||
|  |         } catch (TimeoutException e) { | ||||||
|  |             logger.debug("设备【{}】截图的时候超时了。。。。。", deviceId,e); | ||||||
|  |             submit.cancel(true); | ||||||
|  |             AndroidAgentSession agentSession = screenShotAgentSessionMap.remove(deviceId); | ||||||
|  |             if (null != agentSession) { | ||||||
|  |                 agentSession.closeSilence(); | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|             if (null != screenShotData && screenShotData.length > 0) { |             if (null != screenShotData && screenShotData.length > 0) { | ||||||
|                 logger.debug("收到手机【{}】截图,大小:{}",deviceId,screenShotData.length); |                 logger.debug("收到手机【{}】截图,大小:{}",deviceId,screenShotData.length); | ||||||
|                 File tmpPicDir = new File(System.getProperty("user.dir")+"/tempPic"); |                 File tmpPicDir = new File(System.getProperty("user.dir")+"/tempPic"); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue