Skip to content

Commit a1496ae

Browse files
authored
Merge pull request #11 from sbsos/feature/Replotting
Adding support for replotting
2 parents e44d2bf + 9f5874d commit a1496ae

File tree

4 files changed

+146
-13
lines changed

4 files changed

+146
-13
lines changed

hotplotterclient/controller.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import requests
66
import json
77
import time
8+
from features import features
89

910

1011
class plotProcessDto(object):
@@ -26,13 +27,15 @@ def __init__(self, processId, phase, step, plotNumber, destinationDrive, plottin
2627

2728

2829
class payload(object):
29-
30+
3031
def __init__(self):
3132
self.plottingStatuses = []
3233
self.hardDrives = []
3334
self.username = ""
3435
self.plotterKey = ""
35-
36+
self.features = []
37+
self.updateHardDrives = False
38+
3639
def addHardDrives(self, hardDrives):
3740
self.hardDrives = hardDrives
3841

@@ -47,6 +50,9 @@ def addAuth(self, username, plotterKey):
4750
self.username = username
4851
self.plotterKey = plotterKey
4952

53+
def addFeatures(self):
54+
self.features = features().getFeatures()
55+
5056
def toJSON(self):
5157
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
5258

@@ -55,10 +61,11 @@ class master_controller(object):
5561
plot_controllers = []
5662
plot_controller = plot_process_controller()
5763
hd_controller = hard_drive_controller()
64+
lastHardDriveHash = ''
5865
username = ''
5966
plotterKey = ''
6067
api_url = 'https://api.hotplotter.com/api/Plotter/UpdateStatus'
61-
68+
6269
def __init__(self, username, plotterKey):
6370
self.username = username
6471
self.plotterKey = plotterKey
@@ -86,16 +93,25 @@ def transformToPlotConfig(self, queuedPlot):
8693
config['plotter_type'] = queuedPlot['plotterType']
8794
config['pool_type'] = queuedPlot['poolType']
8895
config['client_identifier'] = queuedPlot['clientIdentifier']
96+
config['replot'] = queuedPlot['replot']
8997

9098
return config
9199

92100
def heart_beat(self):
93101
heartbeat_interval = 120.0
94102

95103
postData = payload()
96-
postData.addHardDrives(self.hd_controller.get_hard_drives())
104+
hardDriveStatuses = self.hd_controller.get_hard_drives()
105+
updateHardDrives = hash(tuple(hardDriveStatuses)) != self.lastHardDriveHash
106+
if(updateHardDrives):
107+
postData.addHardDrives(hardDriveStatuses)
108+
self.lastHardDriveHash = hash(tuple(hardDriveStatuses))
109+
postData.updateHardDrives = True
110+
97111
postData.addPlots(self.plot_controller)
98112
postData.addAuth(self.username, self.plotterKey)
113+
postData.addFeatures()
114+
99115
try:
100116
x = requests.post(self.api_url, data=postData.toJSON(), headers={"Content-Type": "application/json"})
101117
if(x.ok):

hotplotterclient/features.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class features(object):
2+
3+
def getFeatures(self):
4+
return ['replot']
5+

hotplotterclient/plotter.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import os
55
import shlex
6+
from replotting import replotting
67

78
class plot_process_monitor(object):
89
proc = None
@@ -16,19 +17,19 @@ class plot_process_monitor(object):
1617
file_name = ""
1718
step = ""
1819
plot_number = 0
19-
number_of_iterations = 0
2020
client_identifier = ""
21+
replotting = None
2122

22-
def __init__(self, proc, temp_drive, temp_drive_two, destination_drive, number_of_iterations, client_identifier):
23+
def __init__(self, proc, temp_drive, temp_drive_two, destination_drive, client_identifier, replotting):
2324
self.proc = proc
2425
self.temp_drive = temp_drive
2526
self.temp_drive_two = temp_drive_two
2627
self.destination_drive = destination_drive
2728
self.file_name = str(self.proc.pid) + ".txt"
2829
self.start_time = datetime.datetime.now()
29-
self.number_of_iterations = number_of_iterations
3030
self.client_identifier = client_identifier
31-
31+
self.replotting = replotting
32+
3233
@property
3334
def plotting_drives(self):
3435
joined_drive_names = self.temp_drive
@@ -47,6 +48,13 @@ def start_monitoring(self):
4748

4849
if "Starting phase 1/4".lower() in line.lower() or "plot name" in line.lower():
4950
self.plot_number += 1
51+
#client_identifier has significance. If a user has multiple plot processes running to same destination drive, not all want might want to replot OG. client_identifier is correlated to the individual plot kickoff, which contains if replotting is enabled or not
52+
deletedPlot = self.replotting.deletePlot(self.destination_drive, self.client_identifier)
53+
replottingEnabled = deletedPlot == None
54+
if replottingEnabled:
55+
if deletedPlot == False:
56+
#might want to check HD space at this point. If none, we kick out. If there is still enough for the size of the plot let it continue.
57+
placeHolder = "placeHolder"
5058

5159
parsed_phase = self.get_phase(line)
5260
if parsed_phase != 0:
@@ -88,7 +96,8 @@ class settings_config(object):
8896
plotter_type = ""
8997
pool_type = "OriginalPlot"
9098
client_identifier = ""
91-
99+
replot = False
100+
92101
def __init__(self, dictionary_config):
93102
self.drives = dictionary_config["drives"]
94103
self.stagger_type = dictionary_config['stagger_type']
@@ -105,13 +114,15 @@ def __init__(self, dictionary_config):
105114
self.buckets = dictionary_config['buckets']
106115
self.pool_type = dictionary_config['pool_type']
107116
self.plotter_type = dictionary_config['plotter_type']
117+
self.replot = dictionary_config['replot']
108118

109119

110120
class plot_process_controller(object):
111121
list_of_plotting_processes = []
112122
log_file_name = "log_file_"
113123
active_plot_monitors = []
114-
124+
replotting = replotting()
125+
115126
def add_monitor(self, monitor):
116127
self.active_plot_monitors.add(monitor)
117128

@@ -163,7 +174,9 @@ def start_plot(self, config, plot_config, iteration):
163174

164175
plot_config["parallel_plots"] -= 1
165176

166-
monitor = plot_process_monitor(proc, plot_config["temp_drive"], plot_config["temp_drive_two"], plot_config["destination_drive"], iteration, plot_config["client_identifier"])
177+
#we check if enabled inside
178+
self.replotting.addDrivePlots(plot_config["destination_drive"], plot_config["client_identifier"], config.replot)
179+
monitor = plot_process_monitor(proc, plot_config["temp_drive"], plot_config["temp_drive_two"], plot_config["destination_drive"], plot_config["client_identifier"], self.replotting)
167180

168181
thread = threading.Thread(target = monitor.start_monitoring)
169182
thread.daemon = True
@@ -219,12 +232,18 @@ def begin_mad_max_plot(self):
219232
command = self.build_mad_max_command_safe()
220233
print('#Beginning Temp Plot: ' + self.plotting_drives + ' Plotting to ' + self.final_drive + " using Chia Plotter")
221234
print("Count: " + str(self.config.count) + " Threads: " + str(self.config.threads) + " Buckets: " + str(self.config.buckets) + " TempDrive: " + str(self.plotting_drives) + " Final Drive: " + str(self.final_drive) + " Farm Key: " + self.config.farm_key + " Pool Key: " + self.config.pool_key)
222-
proc = subprocess.Popen(command, stdout=subprocess.PIPE, text=True)
235+
try:
236+
proc = subprocess.Popen(command, stdout=subprocess.PIPE, text=True)
237+
except OSError as e:
238+
raise Exception('Please make sure you have permission to plot in the assigned directory, and that you have chia_plot in your PATH')
223239
return proc
224240

225241
def begin_chia_plot(self):
226242
command = self.build_chia_plot_command_safe()
227243
print('#Beginning Temp Plot: ' + self.temp_drive + ' Plotting to ' + self.final_drive + " using Chia Plotter")
228244
print("Count: " + str(self.config.count) + " Ram: " + str(self.config.ram) + " Threads: " + str(self.config.threads) + " Size: " + str(self.config.size) + " TempDrive: " + str(self.temp_drive) + " Final Drive: " + str(self.final_drive) + " Farm Key: " + self.config.farm_key + " Pool Key: " + self.config.pool_key)
229-
proc = subprocess.Popen(command, stdout=subprocess.PIPE, text=True)
245+
try:
246+
proc = subprocess.Popen(command, stdout=subprocess.PIPE, text=True)
247+
except OSError as e:
248+
raise Exception('Please make sure you have permission to plot in the assigned directory, and that you have chia in your PATH')
230249
return proc

hotplotterclient/replotting.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
import threading
3+
4+
class drivePlots(object):
5+
driveName = ""
6+
plots = []
7+
plotTypes = ""
8+
offset = 58
9+
def __init__(self, driveName, plotTypes):
10+
self.driveName = driveName
11+
self.populatePlots(PlotTypes.OgPlots)
12+
13+
def filterPlotsInDirectory(self, files):
14+
plots = filter(lambda c: 'plot' in c, files)
15+
return plots
16+
17+
def populatePlots(self, filterPlotType):
18+
files = os.listdir(self.driveName)
19+
plots = self.filterPlotsInDirectory(files)
20+
for plot in plots:
21+
plotWithFullPath = ''
22+
if self.driveName[-1] != '/':
23+
plotWithFullPath = self.driveName + '/' + plot
24+
else:
25+
plotWithFullPath = self.driveName + plot
26+
27+
with open(plotWithFullPath, "rb") as plotFile:
28+
plotFile.read(self.offset)
29+
res = plotFile.read(2)
30+
31+
#Two Byte to int, w/ big to achieve their Utility.TwoByteToInt
32+
length = res[1] >> res[0]
33+
34+
plotType = self.getPlotType(length)
35+
if plotType == filterPlotType:
36+
self.plots.append(plotWithFullPath)
37+
38+
def getPlotType(self, memoLength):
39+
if memoLength == 112:
40+
return PlotTypes.NftPlots
41+
42+
elif memoLength == 128:
43+
return PlotTypes.OgPlots
44+
45+
#I did not understand this as well as I thought
46+
return None
47+
48+
def getPlot(self):
49+
plotName = self.plots.pop()
50+
return plotName
51+
52+
53+
class replotting(object):
54+
drivePlotsLookup = {}
55+
shouldReplot = False
56+
lock = threading.Lock()
57+
58+
def __init__(self):
59+
return None
60+
61+
#Only call upon new plot starting
62+
def addDrivePlots(self, drivePath, clientIdentifier, enabled):
63+
if enabled:
64+
dp = drivePlots(drivePath, PlotTypes.OgPlots)
65+
self.drivePlotsLookup[drivePath + clientIdentifier] = dp
66+
67+
68+
def getPlotToDelete(self, drive, clientIdentifier):
69+
with self.lock:
70+
plotLookUp = self.drivePlotsLookup[drive + clientIdentifier]
71+
plotName = plotLookUp.getPlot()
72+
return plotName
73+
74+
def deletePlot(self, drive, clientIdentifier):
75+
if drive + clientIdentifier in self.drivePlotsLookup:
76+
plotWithFullPath = self.getPlotToDelete(drive, clientIdentifier)
77+
if plotWithFullPath is not None:
78+
os.remove(plotWithFullPath)
79+
return True
80+
else:
81+
return False
82+
return None
83+
84+
class PlotTypes(object):
85+
OgPlots = "OgPlots"
86+
NftPlots = "NftPlots"
87+
88+
89+
if __name__ == '__main__':
90+
replotting = replotting()
91+
replotting.addDrivePlots('A:/Plots', '1', True)
92+
plotName = replotting.deletePlot('A:/Plots', '1')
93+
print(plotName)

0 commit comments

Comments
 (0)